From 3a4e33258eb1b51bcad7922517884f67bf5e5e16 Mon Sep 17 00:00:00 2001 From: "Laszlo Boszormenyi (GCS)" Date: Sun, 27 Apr 2025 08:10:01 +0200 Subject: [PATCH] Import fuse3_3.17.2.orig.tar.gz [dgit import orig fuse3_3.17.2.orig.tar.gz] --- .ackrc | 2 + .clang-format | 119 + .codespellrc | 13 + .dir-locals.el | 47 + AUTHORS | 277 + ChangeLog.rst | 891 ++ GPL2.txt | 339 + LGPL2.txt | 502 ++ LICENSE | 13 + README.md | 162 + SECURITY.md | 20 + checkpatch.pl | 7820 +++++++++++++++++ dev-docs/extend-authors.sh | 35 + dev-docs/release-process.md | 61 + doc/Doxyfile | 2695 ++++++ doc/README.NFS | 44 + doc/fast17-vangoor.pdf | Bin 0 -> 599157 bytes doc/fusermount3.1 | 44 + doc/html/annotated.html | 73 + doc/html/bc_s.png | Bin 0 -> 676 bytes doc/html/bc_sd.png | Bin 0 -> 635 bytes doc/html/bdwn.png | Bin 0 -> 147 bytes doc/html/buffer_8c_source.html | 410 + doc/html/build-debian_2config_8h_source.html | 96 + ...meson-private_2sanitycheckc_8c_source.html | 56 + doc/html/build-rhel8_2config_8h_source.html | 96 + ...meson-private_2sanitycheckc_8c_source.html | 56 + doc/html/build-ubuntu_2config_8h_source.html | 96 + .../build-ubuntu_2fuse__config_8h_source.html | 105 + ...ild-ubuntu_2libfuse__config_8h_source.html | 77 + ...meson-private_2sanitycheckc_8c_source.html | 61 + doc/html/classes.html | 60 + doc/html/closed.png | Bin 0 -> 132 bytes doc/html/compat_8c_source.html | 148 + doc/html/cuse_8c.html | 411 + doc/html/cuse_8c_source.html | 396 + doc/html/cuse__client_8c.html | 215 + doc/html/cuse__client_8c_source.html | 188 + doc/html/cuse__lowlevel_8c_source.html | 452 + doc/html/cuse__lowlevel_8h_source.html | 152 + .../dir_0dc60d031bb972b0c2e26fefb85d65c3.html | 75 + .../dir_13e138d54eb8818da29c3992edef070a.html | 77 + .../dir_1dfeff730c7aaf81ae73022af75d725f.html | 102 + .../dir_23ec12649285f9fabf3a6b7380226c28.html | 63 + .../dir_2ee0fcdc098dd79f81f9944140524a5f.html | 68 + .../dir_398a6172cfdb51678e55b54be73d5fa5.html | 102 + .../dir_3bd2abc0f0eca4df2097a8b21b48c13b.html | 63 + .../dir_3f3840dea793d3e59c45e9730587a31c.html | 55 + .../dir_71fd611d49b15b397aa830250b7fdc25.html | 63 + .../dir_75ac0186611fae93a0a2599fafee7b59.html | 55 + .../dir_7c10f509f528fa84faee6538d3d65d31.html | 69 + .../dir_812ebf512ecd184ed1d39ebdcddff7ee.html | 68 + .../dir_8e18dfdf7be85b959157088a64d07c40.html | 99 + .../dir_8e4bcc66b5040e6cd82b8756da72da10.html | 75 + .../dir_97aefd0d527b934f1d99a682da8fe6a9.html | 102 + .../dir_9bd412e6c6ed29227c4172ad8b62b221.html | 61 + .../dir_9ce3b66b67a1b1b09c830f7629696f5b.html | 102 + .../dir_9e890e4e051b7ad89260eb605ee3a3c0.html | 55 + .../dir_a320cda2ad42f6d84fc7a0968b7fa58c.html | 63 + .../dir_a58186fda3c587e14871c11a193bc78f.html | 61 + .../dir_a8feaf0c066680727663edb1c44e1229.html | 63 + .../dir_a9479a6a9e1f41fd821cf4117f966d1e.html | 99 + .../dir_acab6aac461847b02973b7a47d1d14e5.html | 73 + .../dir_afd1a5f89d59cae8c3bf3e89c0f7b45e.html | 68 + .../dir_b103db43dffcdee3de8bd6a6d79f8618.html | 63 + .../dir_bc297b8ee9a3081b188fc7e3fa93504a.html | 55 + .../dir_bf10854db571cf772d4a6211be1232f2.html | 55 + .../dir_cb081b49593532e6f6dadd15ccd82ab4.html | 75 + .../dir_cd1412f659f324388a4b25bf5c39d8d8.html | 59 + .../dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html | 99 + .../dir_d0f078c01bc3ce273c081eaac57e44d3.html | 71 + .../dir_d44c64559bbebec7f509842c48db8b23.html | 75 + .../dir_d66497626fcb891eefdcc285752aeb54.html | 99 + .../dir_d709b210fbffdc23d8da2b53f1d32b88.html | 59 + .../dir_d726b7599710abf1b6039c9dcf7cf687.html | 71 + .../dir_d7aafa31aa39c71b1ecb09f780e2341f.html | 63 + .../dir_db9b1b9045937e68e72f268df7b71cec.html | 75 + .../dir_de142c35e6fa30e4531b188c66688c85.html | 61 + .../dir_e0b70151b30581f59638c2f3a5122668.html | 73 + .../dir_e1dbc8ba94a86723d4c32227b7c46099.html | 63 + .../dir_e68e8157741866f444e17edd764ebbae.html | 55 + doc/html/doc.png | Bin 0 -> 746 bytes doc/html/doc.svg | 12 + doc/html/docd.svg | 12 + doc/html/doxygen.css | 2045 +++++ doc/html/doxygen.svg | 28 + doc/html/dynsections.js | 199 + doc/html/example_2cuse_8c.html | 409 + doc/html/example_2cuse_8c_source.html | 395 + doc/html/example_2cuse__client_8c.html | 214 + doc/html/example_2cuse__client_8c_source.html | 188 + doc/html/example_2hello_8c.html | 264 + doc/html/example_2hello_8c_source.html | 253 + doc/html/example_2hello__ll_8c.html | 388 + doc/html/example_2hello__ll_8c_source.html | 376 + doc/html/example_2hello__ll__uds_8c.html | 391 + .../example_2hello__ll__uds_8c_source.html | 442 + doc/html/example_2invalidate__path_8c.html | 390 + .../example_2invalidate__path_8c_source.html | 370 + doc/html/example_2ioctl_8c.html | 294 + doc/html/example_2ioctl_8c_source.html | 283 + doc/html/example_2ioctl_8h.html | 96 + doc/html/example_2ioctl_8h_source.html | 92 + doc/html/example_2ioctl__client_8c.html | 138 + .../example_2ioctl__client_8c_source.html | 125 + .../example_2notify__inval__entry_8c.html | 474 + ...ample_2notify__inval__entry_8c_source.html | 424 + .../example_2notify__inval__inode_8c.html | 483 + ...ample_2notify__inval__inode_8c_source.html | 443 + .../example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ doc/html/example_2null_8c.html | 763 ++ doc/html/example_2null_8c_source.html | 196 + doc/html/example_2passthrough_8c.html | 663 ++ doc/html/example_2passthrough_8c_source.html | 649 ++ doc/html/example_2passthrough__fh_8c.html | 766 ++ .../example_2passthrough__fh_8c_source.html | 751 ++ ...ample_2passthrough__helpers_8h_source.html | 136 + doc/html/example_2passthrough__ll_8c.html | 1529 ++++ .../example_2passthrough__ll_8c_source.html | 1508 ++++ doc/html/example_2poll_8c.html | 379 + doc/html/example_2poll_8c_source.html | 364 + doc/html/example_2poll__client_8c.html | 145 + doc/html/example_2poll__client_8c_source.html | 131 + doc/html/example_2printcap_8c.html | 239 + doc/html/example_2printcap_8c_source.html | 230 + doc/html/fast17-vangoor.pdf | Bin 0 -> 599157 bytes doc/html/files.html | 126 + doc/html/folderclosed.png | Bin 0 -> 616 bytes doc/html/folderclosed.svg | 11 + doc/html/folderclosedd.svg | 11 + doc/html/folderopen.png | Bin 0 -> 597 bytes doc/html/folderopen.svg | 17 + doc/html/folderopend.svg | 12 + doc/html/functions.html | 256 + doc/html/functions_vars.html | 256 + ...81-rc0_2build_2fuse__config_8h_source.html | 103 + ...rc0_2build_2libfuse__config_8h_source.html | 77 + ...meson-private_2sanitycheckc_8c_source.html | 61 + .../fuse-3_817_81-rc0_2example_2cuse_8c.html | 409 + ...3_817_81-rc0_2example_2cuse_8c_source.html | 395 + ..._817_81-rc0_2example_2cuse__client_8c.html | 214 + ...-rc0_2example_2cuse__client_8c_source.html | 188 + .../fuse-3_817_81-rc0_2example_2hello_8c.html | 264 + ..._817_81-rc0_2example_2hello_8c_source.html | 246 + ...e-3_817_81-rc0_2example_2hello__ll_8c.html | 388 + ..._81-rc0_2example_2hello__ll_8c_source.html | 370 + ...17_81-rc0_2example_2hello__ll__uds_8c.html | 391 + ...c0_2example_2hello__ll__uds_8c_source.html | 442 + ..._81-rc0_2example_2invalidate__path_8c.html | 390 + ..._2example_2invalidate__path_8c_source.html | 370 + .../fuse-3_817_81-rc0_2example_2ioctl_8c.html | 294 + ..._817_81-rc0_2example_2ioctl_8c_source.html | 283 + .../fuse-3_817_81-rc0_2example_2ioctl_8h.html | 96 + ..._817_81-rc0_2example_2ioctl_8h_source.html | 92 + ...817_81-rc0_2example_2ioctl__client_8c.html | 138 + ...rc0_2example_2ioctl__client_8c_source.html | 125 + ...rc0_2example_2notify__inval__entry_8c.html | 474 + ...ample_2notify__inval__entry_8c_source.html | 424 + ...rc0_2example_2notify__inval__inode_8c.html | 483 + ...ample_2notify__inval__inode_8c_source.html | 443 + ..._2example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ .../fuse-3_817_81-rc0_2example_2null_8c.html | 763 ++ ...3_817_81-rc0_2example_2null_8c_source.html | 196 + ...3_817_81-rc0_2example_2passthrough_8c.html | 663 ++ ...1-rc0_2example_2passthrough_8c_source.html | 649 ++ ...7_81-rc0_2example_2passthrough__fh_8c.html | 766 ++ ...0_2example_2passthrough__fh_8c_source.html | 751 ++ ...ample_2passthrough__helpers_8h_source.html | 136 + ...7_81-rc0_2example_2passthrough__ll_8c.html | 1529 ++++ ...0_2example_2passthrough__ll_8c_source.html | 1508 ++++ .../fuse-3_817_81-rc0_2example_2poll_8c.html | 379 + ...3_817_81-rc0_2example_2poll_8c_source.html | 364 + ..._817_81-rc0_2example_2poll__client_8c.html | 145 + ...-rc0_2example_2poll__client_8c_source.html | 131 + ...se-3_817_81-rc0_2example_2printcap_8c.html | 239 + ...7_81-rc0_2example_2printcap_8c_source.html | 230 + ...c0_2include_2cuse__lowlevel_8h_source.html | 152 + .../fuse-3_817_81-rc0_2include_2fuse_8h.html | 780 ++ ...3_817_81-rc0_2include_2fuse_8h_source.html | 666 ++ ..._817_81-rc0_2include_2fuse__common_8h.html | 723 ++ ...-rc0_2include_2fuse__common_8h_source.html | 554 ++ ...-rc0_2include_2fuse__kernel_8h_source.html | 1097 +++ ...e-3_817_81-rc0_2include_2fuse__log_8h.html | 268 + ..._81-rc0_2include_2fuse__log_8h_source.html | 112 + ...17_81-rc0_2include_2fuse__lowlevel_8h.html | 2362 +++++ ...c0_2include_2fuse__lowlevel_8h_source.html | 683 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + ...e-3_817_81-rc0_2include_2fuse__opt_8h.html | 587 ++ ..._81-rc0_2include_2fuse__opt_8h_source.html | 149 + ...e-3_817_81-rc0_2lib_2buffer_8c_source.html | 410 + ...e-3_817_81-rc0_2lib_2compat_8c_source.html | 148 + ...81-rc0_2lib_2cuse__lowlevel_8c_source.html | 451 + ...use-3_817_81-rc0_2lib_2fuse_8c_source.html | 5436 ++++++++++++ ...-3_817_81-rc0_2lib_2fuse__i_8h_source.html | 251 + ..._817_81-rc0_2lib_2fuse__log_8c_source.html | 170 + ...817_81-rc0_2lib_2fuse__loop_8c_source.html | 114 + ...81-rc0_2lib_2fuse__loop__mt_8c_source.html | 599 ++ ...81-rc0_2lib_2fuse__lowlevel_8c_source.html | 3841 ++++++++ ...817_81-rc0_2lib_2fuse__misc_8h_source.html | 111 + ..._817_81-rc0_2lib_2fuse__opt_8c_source.html | 516 ++ ..._81-rc0_2lib_2fuse__signals_8c_source.html | 269 + ...e-3_817_81-rc0_2lib_2helper_8c_source.html | 616 ++ ...81-rc0_2lib_2modules_2iconv_8c_source.html | 816 ++ ...1-rc0_2lib_2modules_2subdir_8c_source.html | 767 ++ ...se-3_817_81-rc0_2lib_2mount_8c_source.html | 791 ++ ...817_81-rc0_2lib_2mount__bsd_8c_source.html | 357 + ...17_81-rc0_2lib_2mount__util_8c_source.html | 433 + ...17_81-rc0_2lib_2mount__util_8h_source.html | 78 + ...use-3_817_81-rc0_2lib_2util_8c_source.html | 87 + ...use-3_817_81-rc0_2lib_2util_8h_source.html | 63 + ...1-rc0_2test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + ..._81-rc0_2test_2stracedecode_8c_source.html | 259 + ...81-rc0_2test_2test__setattr_8c_source.html | 272 + ...1-rc0_2test_2test__syscalls_8c_source.html | 2260 +++++ ...0_2test_2test__write__cache_8c_source.html | 403 + ...1-rc0_2test_2wrong__command_8c_source.html | 76 + ...17_81-rc0_2util_2fusermount_8c_source.html | 1767 ++++ ...7_81-rc0_2util_2mount_8fuse_8c_source.html | 515 ++ .../fuse-3_817_81-rc1_2example_2cuse_8c.html | 409 + ...3_817_81-rc1_2example_2cuse_8c_source.html | 395 + ..._817_81-rc1_2example_2cuse__client_8c.html | 214 + ...-rc1_2example_2cuse__client_8c_source.html | 188 + .../fuse-3_817_81-rc1_2example_2hello_8c.html | 257 + ..._817_81-rc1_2example_2hello_8c_source.html | 246 + ...e-3_817_81-rc1_2example_2hello__ll_8c.html | 382 + ..._81-rc1_2example_2hello__ll_8c_source.html | 370 + ...17_81-rc1_2example_2hello__ll__uds_8c.html | 385 + ...c1_2example_2hello__ll__uds_8c_source.html | 442 + ..._81-rc1_2example_2invalidate__path_8c.html | 390 + ..._2example_2invalidate__path_8c_source.html | 370 + .../fuse-3_817_81-rc1_2example_2ioctl_8c.html | 294 + ..._817_81-rc1_2example_2ioctl_8c_source.html | 283 + .../fuse-3_817_81-rc1_2example_2ioctl_8h.html | 96 + ..._817_81-rc1_2example_2ioctl_8h_source.html | 92 + ...817_81-rc1_2example_2ioctl__client_8c.html | 138 + ...rc1_2example_2ioctl__client_8c_source.html | 125 + ...rc1_2example_2notify__inval__entry_8c.html | 474 + ...ample_2notify__inval__entry_8c_source.html | 424 + ...rc1_2example_2notify__inval__inode_8c.html | 483 + ...ample_2notify__inval__inode_8c_source.html | 443 + ..._2example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ .../fuse-3_817_81-rc1_2example_2null_8c.html | 763 ++ ...3_817_81-rc1_2example_2null_8c_source.html | 196 + ...3_817_81-rc1_2example_2passthrough_8c.html | 663 ++ ...1-rc1_2example_2passthrough_8c_source.html | 649 ++ ...7_81-rc1_2example_2passthrough__fh_8c.html | 766 ++ ...1_2example_2passthrough__fh_8c_source.html | 751 ++ ...ample_2passthrough__helpers_8h_source.html | 136 + ...7_81-rc1_2example_2passthrough__ll_8c.html | 1529 ++++ ...1_2example_2passthrough__ll_8c_source.html | 1508 ++++ .../fuse-3_817_81-rc1_2example_2poll_8c.html | 379 + ...3_817_81-rc1_2example_2poll_8c_source.html | 364 + ..._817_81-rc1_2example_2poll__client_8c.html | 145 + ...-rc1_2example_2poll__client_8c_source.html | 131 + ...se-3_817_81-rc1_2example_2printcap_8c.html | 239 + ...7_81-rc1_2example_2printcap_8c_source.html | 230 + ...c1_2include_2cuse__lowlevel_8h_source.html | 152 + .../fuse-3_817_81-rc1_2include_2fuse_8h.html | 864 ++ ...3_817_81-rc1_2include_2fuse_8h_source.html | 643 ++ ..._817_81-rc1_2include_2fuse__common_8h.html | 723 ++ ...-rc1_2include_2fuse__common_8h_source.html | 519 ++ ...-rc1_2include_2fuse__kernel_8h_source.html | 1093 +++ ...e-3_817_81-rc1_2include_2fuse__log_8h.html | 268 + ..._81-rc1_2include_2fuse__log_8h_source.html | 112 + ...17_81-rc1_2include_2fuse__lowlevel_8h.html | 2373 +++++ ...c1_2include_2fuse__lowlevel_8h_source.html | 673 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + ...e-3_817_81-rc1_2include_2fuse__opt_8h.html | 587 ++ ..._81-rc1_2include_2fuse__opt_8h_source.html | 145 + ...e-3_817_81-rc1_2lib_2buffer_8c_source.html | 406 + ...e-3_817_81-rc1_2lib_2compat_8c_source.html | 148 + ...81-rc1_2lib_2cuse__lowlevel_8c_source.html | 451 + ...use-3_817_81-rc1_2lib_2fuse_8c_source.html | 5393 ++++++++++++ ...-3_817_81-rc1_2lib_2fuse__i_8h_source.html | 249 + ..._817_81-rc1_2lib_2fuse__log_8c_source.html | 162 + ...817_81-rc1_2lib_2fuse__loop_8c_source.html | 112 + ...81-rc1_2lib_2fuse__loop__mt_8c_source.html | 599 ++ ...81-rc1_2lib_2fuse__lowlevel_8c_source.html | 3749 ++++++++ ...817_81-rc1_2lib_2fuse__misc_8h_source.html | 111 + ..._817_81-rc1_2lib_2fuse__opt_8c_source.html | 504 ++ ..._81-rc1_2lib_2fuse__signals_8c_source.html | 263 + ...e-3_817_81-rc1_2lib_2helper_8c_source.html | 598 ++ ...81-rc1_2lib_2modules_2iconv_8c_source.html | 816 ++ ...1-rc1_2lib_2modules_2subdir_8c_source.html | 767 ++ ...se-3_817_81-rc1_2lib_2mount_8c_source.html | 791 ++ ...817_81-rc1_2lib_2mount__bsd_8c_source.html | 333 + ...17_81-rc1_2lib_2mount__util_8c_source.html | 433 + ...17_81-rc1_2lib_2mount__util_8h_source.html | 78 + ...use-3_817_81-rc1_2lib_2util_8c_source.html | 87 + ...use-3_817_81-rc1_2lib_2util_8h_source.html | 63 + ...1-rc1_2test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + ..._81-rc1_2test_2stracedecode_8c_source.html | 259 + ...81-rc1_2test_2test__setattr_8c_source.html | 272 + ...1-rc1_2test_2test__syscalls_8c_source.html | 2258 +++++ ...1_2test_2test__write__cache_8c_source.html | 403 + ...1-rc1_2test_2wrong__command_8c_source.html | 76 + ...17_81-rc1_2util_2fusermount_8c_source.html | 1769 ++++ ...7_81-rc1_2util_2mount_8fuse_8c_source.html | 515 ++ ...1_8dir_2build_2fuse__config_8h_source.html | 105 + ...dir_2build_2libfuse__config_8h_source.html | 77 + ...meson-private_2sanitycheckc_8c_source.html | 61 + .../fuse-3_817_81_8dir_2example_2cuse_8c.html | 409 + ..._817_81_8dir_2example_2cuse_8c_source.html | 395 + ...817_81_8dir_2example_2cuse__client_8c.html | 214 + ...8dir_2example_2cuse__client_8c_source.html | 188 + ...fuse-3_817_81_8dir_2example_2hello_8c.html | 263 + ...817_81_8dir_2example_2hello_8c_source.html | 253 + ...-3_817_81_8dir_2example_2hello__ll_8c.html | 388 + ...81_8dir_2example_2hello__ll_8c_source.html | 376 + ...7_81_8dir_2example_2hello__ll__uds_8c.html | 391 + ...ir_2example_2hello__ll__uds_8c_source.html | 442 + ...81_8dir_2example_2invalidate__path_8c.html | 390 + ..._2example_2invalidate__path_8c_source.html | 370 + ...fuse-3_817_81_8dir_2example_2ioctl_8c.html | 294 + ...817_81_8dir_2example_2ioctl_8c_source.html | 283 + ...fuse-3_817_81_8dir_2example_2ioctl_8h.html | 96 + ...817_81_8dir_2example_2ioctl_8h_source.html | 92 + ...17_81_8dir_2example_2ioctl__client_8c.html | 138 + ...dir_2example_2ioctl__client_8c_source.html | 125 + ...dir_2example_2notify__inval__entry_8c.html | 474 + ...ample_2notify__inval__entry_8c_source.html | 424 + ...dir_2example_2notify__inval__inode_8c.html | 483 + ...ample_2notify__inval__inode_8c_source.html | 443 + ..._2example_2notify__store__retrieve_8c.html | 560 ++ ...le_2notify__store__retrieve_8c_source.html | 522 ++ .../fuse-3_817_81_8dir_2example_2null_8c.html | 763 ++ ..._817_81_8dir_2example_2null_8c_source.html | 196 + ..._817_81_8dir_2example_2passthrough_8c.html | 663 ++ ..._8dir_2example_2passthrough_8c_source.html | 649 ++ ..._81_8dir_2example_2passthrough__fh_8c.html | 766 ++ ...r_2example_2passthrough__fh_8c_source.html | 751 ++ ...ample_2passthrough__helpers_8h_source.html | 136 + ..._81_8dir_2example_2passthrough__ll_8c.html | 1529 ++++ ...r_2example_2passthrough__ll_8c_source.html | 1508 ++++ .../fuse-3_817_81_8dir_2example_2poll_8c.html | 379 + ..._817_81_8dir_2example_2poll_8c_source.html | 364 + ...817_81_8dir_2example_2poll__client_8c.html | 145 + ...8dir_2example_2poll__client_8c_source.html | 131 + ...e-3_817_81_8dir_2example_2printcap_8c.html | 239 + ..._81_8dir_2example_2printcap_8c_source.html | 230 + ...ir_2include_2cuse__lowlevel_8h_source.html | 152 + .../fuse-3_817_81_8dir_2include_2fuse_8h.html | 864 ++ ..._817_81_8dir_2include_2fuse_8h_source.html | 650 ++ ...817_81_8dir_2include_2fuse__common_8h.html | 1139 +++ ...8dir_2include_2fuse__common_8h_source.html | 518 ++ ...8dir_2include_2fuse__kernel_8h_source.html | 1097 +++ ...-3_817_81_8dir_2include_2fuse__log_8h.html | 268 + ...81_8dir_2include_2fuse__log_8h_source.html | 112 + ...7_81_8dir_2include_2fuse__lowlevel_8h.html | 2363 +++++ ...ir_2include_2fuse__lowlevel_8h_source.html | 683 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + ...-3_817_81_8dir_2include_2fuse__opt_8h.html | 587 ++ ...81_8dir_2include_2fuse__opt_8h_source.html | 149 + ...-3_817_81_8dir_2lib_2buffer_8c_source.html | 410 + ...-3_817_81_8dir_2lib_2compat_8c_source.html | 148 + ...1_8dir_2lib_2cuse__lowlevel_8c_source.html | 451 + ...se-3_817_81_8dir_2lib_2fuse_8c_source.html | 5456 ++++++++++++ ...3_817_81_8dir_2lib_2fuse__i_8h_source.html | 285 + ...817_81_8dir_2lib_2fuse__log_8c_source.html | 170 + ...17_81_8dir_2lib_2fuse__loop_8c_source.html | 114 + ...1_8dir_2lib_2fuse__loop__mt_8c_source.html | 599 ++ ...1_8dir_2lib_2fuse__lowlevel_8c_source.html | 3821 ++++++++ ...17_81_8dir_2lib_2fuse__misc_8h_source.html | 111 + ...817_81_8dir_2lib_2fuse__opt_8c_source.html | 516 ++ ...81_8dir_2lib_2fuse__signals_8c_source.html | 269 + ...-3_817_81_8dir_2lib_2helper_8c_source.html | 613 ++ ...1_8dir_2lib_2modules_2iconv_8c_source.html | 816 ++ ..._8dir_2lib_2modules_2subdir_8c_source.html | 767 ++ ...e-3_817_81_8dir_2lib_2mount_8c_source.html | 792 ++ ...17_81_8dir_2lib_2mount__bsd_8c_source.html | 333 + ...7_81_8dir_2lib_2mount__util_8c_source.html | 433 + ...7_81_8dir_2lib_2mount__util_8h_source.html | 78 + ...se-3_817_81_8dir_2lib_2util_8c_source.html | 87 + ...se-3_817_81_8dir_2lib_2util_8h_source.html | 84 + ..._8dir_2test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + ...81_8dir_2test_2stracedecode_8c_source.html | 259 + ...1_8dir_2test_2test__setattr_8c_source.html | 272 + ..._8dir_2test_2test__syscalls_8c_source.html | 2258 +++++ ...est_2test__want__conversion_8c_source.html | 225 + ...r_2test_2test__write__cache_8c_source.html | 402 + ..._8dir_2test_2wrong__command_8c_source.html | 76 + ...7_81_8dir_2util_2fusermount_8c_source.html | 1796 ++++ ..._81_8dir_2util_2mount_8fuse_8c_source.html | 515 ++ doc/html/fuse_8c_source.html | 5460 ++++++++++++ doc/html/fuse_8h.html | 838 ++ doc/html/fuse_8h_source.html | 645 ++ doc/html/fuse__common_8h.html | 1139 +++ doc/html/fuse__common_8h_source.html | 515 ++ doc/html/fuse__config_8h_source.html | 105 + doc/html/fuse__i_8h_source.html | 292 + doc/html/fuse__kernel_8h_source.html | 1097 +++ doc/html/fuse__log_8c_source.html | 171 + doc/html/fuse__log_8h.html | 268 + doc/html/fuse__log_8h_source.html | 112 + doc/html/fuse__loop_8c_source.html | 115 + doc/html/fuse__loop__mt_8c_source.html | 602 ++ doc/html/fuse__lowlevel_8c_source.html | 3834 ++++++++ doc/html/fuse__lowlevel_8h.html | 2363 +++++ doc/html/fuse__lowlevel_8h_source.html | 681 ++ doc/html/fuse__misc_8h_source.html | 111 + doc/html/fuse__mount__compat_8h_source.html | 109 + doc/html/fuse__opt_8c_source.html | 517 ++ doc/html/fuse__opt_8h.html | 587 ++ doc/html/fuse__opt_8h_source.html | 149 + doc/html/fuse__signals_8c_source.html | 275 + doc/html/fusermount_8c_source.html | 1796 ++++ doc/html/globals.html | 201 + doc/html/globals_defs.html | 91 + doc/html/globals_enum.html | 56 + doc/html/globals_eval.html | 59 + doc/html/globals_func.html | 140 + doc/html/globals_type.html | 57 + doc/html/hello_8c.html | 258 + doc/html/hello_8c_source.html | 247 + doc/html/hello__ll_8c.html | 389 + doc/html/hello__ll_8c_source.html | 377 + doc/html/hello__ll__uds_8c.html | 392 + doc/html/hello__ll__uds_8c_source.html | 443 + doc/html/helper_8c_source.html | 615 ++ doc/html/iconv_8c_source.html | 817 ++ .../include_2cuse__lowlevel_8h_source.html | 152 + doc/html/include_2fuse_8h.html | 866 ++ doc/html/include_2fuse_8h_source.html | 641 ++ doc/html/include_2fuse__common_8h.html | 1139 +++ doc/html/include_2fuse__common_8h_source.html | 481 + doc/html/include_2fuse__kernel_8h_source.html | 1093 +++ doc/html/include_2fuse__log_8h.html | 268 + doc/html/include_2fuse__log_8h_source.html | 112 + doc/html/include_2fuse__lowlevel_8h.html | 2363 +++++ .../include_2fuse__lowlevel_8h_source.html | 673 ++ ...nclude_2fuse__mount__compat_8h_source.html | 109 + doc/html/include_2fuse__opt_8h.html | 587 ++ doc/html/include_2fuse__opt_8h_source.html | 145 + doc/html/index.html | 69 + doc/html/invalidate__path_8c.html | 392 + doc/html/invalidate__path_8c_source.html | 372 + doc/html/ioctl_8c.html | 296 + doc/html/ioctl_8c_source.html | 284 + doc/html/ioctl_8h.html | 96 + doc/html/ioctl_8h_source.html | 92 + doc/html/ioctl__client_8c.html | 139 + doc/html/ioctl__client_8c_source.html | 125 + doc/html/jquery.js | 34 + doc/html/lib_2buffer_8c_source.html | 406 + doc/html/lib_2compat_8c_source.html | 148 + doc/html/lib_2cuse__lowlevel_8c_source.html | 451 + doc/html/lib_2fuse_8c_source.html | 5424 ++++++++++++ doc/html/lib_2fuse__i_8h_source.html | 288 + doc/html/lib_2fuse__log_8c_source.html | 162 + doc/html/lib_2fuse__loop_8c_source.html | 112 + doc/html/lib_2fuse__loop__mt_8c_source.html | 601 ++ doc/html/lib_2fuse__lowlevel_8c_source.html | 3748 ++++++++ doc/html/lib_2fuse__misc_8h_source.html | 111 + doc/html/lib_2fuse__opt_8c_source.html | 504 ++ doc/html/lib_2fuse__signals_8c_source.html | 268 + doc/html/lib_2helper_8c_source.html | 599 ++ doc/html/lib_2modules_2iconv_8c_source.html | 816 ++ doc/html/lib_2modules_2subdir_8c_source.html | 767 ++ doc/html/lib_2mount_8c_source.html | 792 ++ doc/html/lib_2mount__bsd_8c_source.html | 333 + doc/html/lib_2mount__util_8c_source.html | 433 + doc/html/lib_2mount__util_8h_source.html | 78 + doc/html/lib_2util_8c_source.html | 87 + doc/html/lib_2util_8h_source.html | 87 + doc/html/libfuse__config_8h_source.html | 77 + doc/html/menu.js | 136 + doc/html/menudata.js | 85 + doc/html/minus.svg | 8 + doc/html/minusd.svg | 8 + doc/html/mount_8c_source.html | 793 ++ doc/html/mount_8fuse_8c_source.html | 516 ++ doc/html/mount__bsd_8c_source.html | 334 + doc/html/mount__util_8c_source.html | 433 + doc/html/mount__util_8h_source.html | 78 + doc/html/nav_f.png | Bin 0 -> 153 bytes doc/html/nav_fd.png | Bin 0 -> 169 bytes doc/html/nav_g.png | Bin 0 -> 95 bytes doc/html/nav_h.png | Bin 0 -> 98 bytes doc/html/nav_hd.png | Bin 0 -> 114 bytes doc/html/notify__inval__entry_8c.html | 475 + doc/html/notify__inval__entry_8c_source.html | 425 + doc/html/notify__inval__inode_8c.html | 484 + doc/html/notify__inval__inode_8c_source.html | 444 + doc/html/notify__store__retrieve_8c.html | 561 ++ .../notify__store__retrieve_8c_source.html | 523 ++ doc/html/null_8c.html | 764 ++ doc/html/null_8c_source.html | 198 + doc/html/open.png | Bin 0 -> 123 bytes doc/html/passthrough_8c.html | 664 ++ doc/html/passthrough_8c_source.html | 650 ++ doc/html/passthrough__fh_8c.html | 767 ++ doc/html/passthrough__fh_8c_source.html | 752 ++ doc/html/passthrough__helpers_8h_source.html | 136 + doc/html/passthrough__ll_8c.html | 1530 ++++ doc/html/passthrough__ll_8c_source.html | 1509 ++++ doc/html/plus.svg | 9 + doc/html/plusd.svg | 9 + doc/html/poll_8c.html | 380 + doc/html/poll_8c_source.html | 365 + doc/html/poll__client_8c.html | 145 + doc/html/poll__client_8c_source.html | 131 + doc/html/printcap_8c.html | 240 + doc/html/printcap_8c_source.html | 231 + doc/html/readdir__inode_8c_source.html | 117 + doc/html/release__unlink__race_8c_source.html | 184 + doc/html/sanitycheckc_8c_source.html | 61 + doc/html/splitbar.png | Bin 0 -> 314 bytes doc/html/splitbard.png | Bin 0 -> 282 bytes doc/html/stracedecode_8c_source.html | 259 + doc/html/structfuse__args.html | 124 + doc/html/structfuse__buf.html | 186 + doc/html/structfuse__bufvec.html | 145 + doc/html/structfuse__cmdline__opts.html | 60 + doc/html/structfuse__config.html | 500 ++ doc/html/structfuse__conn__info.html | 349 + doc/html/structfuse__context.html | 182 + doc/html/structfuse__ctx.html | 144 + doc/html/structfuse__entry__param.html | 165 + doc/html/structfuse__ext__header.html | 61 + doc/html/structfuse__file__info.html | 355 + doc/html/structfuse__loop__config.html | 148 + doc/html/structfuse__lowlevel__ops.html | 1476 ++++ doc/html/structfuse__module.html | 61 + doc/html/structfuse__operations.html | 946 ++ doc/html/structfuse__opt.html | 145 + doc/html/structfuse__supp__groups.html | 60 + doc/html/structlibfuse__version.html | 60 + doc/html/subdir_8c_source.html | 768 ++ doc/html/sync_off.png | Bin 0 -> 853 bytes doc/html/sync_on.png | Bin 0 -> 845 bytes doc/html/tab_a.png | Bin 0 -> 142 bytes doc/html/tab_ad.png | Bin 0 -> 135 bytes doc/html/tab_b.png | Bin 0 -> 169 bytes doc/html/tab_bd.png | Bin 0 -> 173 bytes doc/html/tab_h.png | Bin 0 -> 177 bytes doc/html/tab_hd.png | Bin 0 -> 180 bytes doc/html/tab_s.png | Bin 0 -> 184 bytes doc/html/tab_sd.png | Bin 0 -> 188 bytes doc/html/tabs.css | 1 + doc/html/test_2hello_8c.html | 264 + doc/html/test_2hello_8c_source.html | 251 + doc/html/test_2readdir__inode_8c_source.html | 117 + ...test_2release__unlink__race_8c_source.html | 183 + doc/html/test_2stracedecode_8c_source.html | 259 + doc/html/test_2test__setattr_8c_source.html | 272 + doc/html/test_2test__syscalls_8c_source.html | 2258 +++++ ...est_2test__want__conversion_8c_source.html | 237 + .../test_2test__write__cache_8c_source.html | 402 + doc/html/test_2wrong__command_8c_source.html | 76 + doc/html/test__setattr_8c_source.html | 274 + doc/html/test__syscalls_8c_source.html | 2258 +++++ .../test__want__conversion_8c_source.html | 237 + doc/html/test__write__cache_8c_source.html | 404 + doc/html/util_2fusermount_8c_source.html | 1796 ++++ doc/html/util_2mount_8fuse_8c_source.html | 515 ++ doc/html/util_8c_source.html | 87 + doc/html/util_8h_source.html | 87 + doc/html/wrong__command_8c_source.html | 76 + doc/kernel.txt | 380 + doc/libfuse-operations.txt | 418 + doc/mainpage.dox | 54 + doc/meson.build | 4 + doc/mount.fuse3.8 | 273 + example/cuse.c | 335 + example/cuse_client.c | 157 + example/cxxopts.hpp | 2114 +++++ example/hello.c | 184 + example/hello_ll.c | 293 + example/hello_ll_uds.c | 367 + example/invalidate_path.c | 292 + example/ioctl.c | 230 + example/ioctl.h | 42 + example/ioctl_client.c | 76 + example/memfs_ll.cc | 1157 +++ example/meson.build | 44 + example/notify_inval_entry.c | 400 + example/notify_inval_inode.c | 402 + example/notify_store_retrieve.c | 475 + example/null.c | 143 + example/passthrough.c | 588 ++ example/passthrough_fh.c | 676 ++ example/passthrough_helpers.h | 76 + example/passthrough_hp.cc | 1576 ++++ example/passthrough_ll.c | 1405 +++ example/poll.c | 304 + example/poll_client.c | 84 + example/printcap.c | 136 + include/cuse_lowlevel.h | 87 + include/fuse.h | 1415 +++ include/fuse_common.h | 1148 +++ include/fuse_kernel.h | 1189 +++ include/fuse_log.h | 94 + include/fuse_lowlevel.h | 2322 +++++ include/fuse_mount_compat.h | 49 + include/fuse_opt.h | 271 + include/meson.build | 4 + lib/buffer.c | 324 + lib/compat.c | 86 + lib/cuse_lowlevel.c | 368 + lib/fuse.c | 5239 +++++++++++ lib/fuse_i.h | 263 + lib/fuse_log.c | 96 + lib/fuse_loop.c | 46 + lib/fuse_loop_mt.c | 531 ++ lib/fuse_lowlevel.c | 3574 ++++++++ lib/fuse_misc.h | 51 + lib/fuse_opt.c | 423 + lib/fuse_signals.c | 203 + lib/fuse_versionscript | 207 + lib/helper.c | 491 ++ lib/meson.build | 58 + lib/modules/iconv.c | 737 ++ lib/modules/subdir.c | 689 ++ lib/mount.c | 723 ++ lib/mount_bsd.c | 266 + lib/mount_util.c | 373 + lib/mount_util.h | 18 + lib/util.c | 27 + lib/util.h | 33 + meson.build | 251 + meson_options.txt | 24 + requirements.txt | 7 + signify/fuse-3.15.pub | 2 + signify/fuse-3.16.pub | 2 + signify/fuse-3.17.pub | 2 + signify/fuse-3.18.pub | 2 + test/ci-build.sh | 155 + test/conftest.py | 97 + test/hello.c | 180 + test/lsan_suppress.txt | 1 + test/meson.build | 39 + test/pytest.ini | 6 + test/readdir_inode.c | 57 + test/release_unlink_race.c | 111 + test/stracedecode.c | 199 + test/test_ctests.py | 146 + test/test_custom_io.py | 81 + test/test_examples.py | 902 ++ test/test_setattr.c | 194 + test/test_syscalls.c | 2198 +++++ test/test_want_conversion.c | 164 + test/test_write_cache.c | 315 + test/util.py | 209 + test/wrong_command.c | 16 + tsan_suppressions.txt | 5 + util/fuse.conf | 17 + util/fusermount.c | 1736 ++++ util/init_script | 92 + util/install_helper.sh | 55 + util/meson.build | 34 + util/mount.fuse.c | 454 + util/parse-backtrace.sh | 117 + util/udev.rules | 1 + xfstests/README.md | 18 + xfstests/local.config | 21 + xfstests/mount.fuse.passthrough | 40 + 662 files changed, 305426 insertions(+) create mode 100644 .ackrc create mode 100644 .clang-format create mode 100644 .codespellrc create mode 100644 .dir-locals.el create mode 100644 AUTHORS create mode 100644 ChangeLog.rst create mode 100644 GPL2.txt create mode 100644 LGPL2.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100755 checkpatch.pl create mode 100755 dev-docs/extend-authors.sh create mode 100644 dev-docs/release-process.md create mode 100644 doc/Doxyfile create mode 100644 doc/README.NFS create mode 100644 doc/fast17-vangoor.pdf create mode 100644 doc/fusermount3.1 create mode 100644 doc/html/annotated.html create mode 100644 doc/html/bc_s.png create mode 100644 doc/html/bc_sd.png create mode 100644 doc/html/bdwn.png create mode 100644 doc/html/buffer_8c_source.html create mode 100644 doc/html/build-debian_2config_8h_source.html create mode 100644 doc/html/build-debian_2meson-private_2sanitycheckc_8c_source.html create mode 100644 doc/html/build-rhel8_2config_8h_source.html create mode 100644 doc/html/build-rhel8_2meson-private_2sanitycheckc_8c_source.html create mode 100644 doc/html/build-ubuntu_2config_8h_source.html create mode 100644 doc/html/build-ubuntu_2fuse__config_8h_source.html create mode 100644 doc/html/build-ubuntu_2libfuse__config_8h_source.html create mode 100644 doc/html/build-ubuntu_2meson-private_2sanitycheckc_8c_source.html create mode 100644 doc/html/classes.html create mode 100644 doc/html/closed.png create mode 100644 doc/html/compat_8c_source.html create mode 100644 doc/html/cuse_8c.html create mode 100644 doc/html/cuse_8c_source.html create mode 100644 doc/html/cuse__client_8c.html create mode 100644 doc/html/cuse__client_8c_source.html create mode 100644 doc/html/cuse__lowlevel_8c_source.html create mode 100644 doc/html/cuse__lowlevel_8h_source.html create mode 100644 doc/html/dir_0dc60d031bb972b0c2e26fefb85d65c3.html create mode 100644 doc/html/dir_13e138d54eb8818da29c3992edef070a.html create mode 100644 doc/html/dir_1dfeff730c7aaf81ae73022af75d725f.html create mode 100644 doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html create mode 100644 doc/html/dir_2ee0fcdc098dd79f81f9944140524a5f.html create mode 100644 doc/html/dir_398a6172cfdb51678e55b54be73d5fa5.html create mode 100644 doc/html/dir_3bd2abc0f0eca4df2097a8b21b48c13b.html create mode 100644 doc/html/dir_3f3840dea793d3e59c45e9730587a31c.html create mode 100644 doc/html/dir_71fd611d49b15b397aa830250b7fdc25.html create mode 100644 doc/html/dir_75ac0186611fae93a0a2599fafee7b59.html create mode 100644 doc/html/dir_7c10f509f528fa84faee6538d3d65d31.html create mode 100644 doc/html/dir_812ebf512ecd184ed1d39ebdcddff7ee.html create mode 100644 doc/html/dir_8e18dfdf7be85b959157088a64d07c40.html create mode 100644 doc/html/dir_8e4bcc66b5040e6cd82b8756da72da10.html create mode 100644 doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html create mode 100644 doc/html/dir_9bd412e6c6ed29227c4172ad8b62b221.html create mode 100644 doc/html/dir_9ce3b66b67a1b1b09c830f7629696f5b.html create mode 100644 doc/html/dir_9e890e4e051b7ad89260eb605ee3a3c0.html create mode 100644 doc/html/dir_a320cda2ad42f6d84fc7a0968b7fa58c.html create mode 100644 doc/html/dir_a58186fda3c587e14871c11a193bc78f.html create mode 100644 doc/html/dir_a8feaf0c066680727663edb1c44e1229.html create mode 100644 doc/html/dir_a9479a6a9e1f41fd821cf4117f966d1e.html create mode 100644 doc/html/dir_acab6aac461847b02973b7a47d1d14e5.html create mode 100644 doc/html/dir_afd1a5f89d59cae8c3bf3e89c0f7b45e.html create mode 100644 doc/html/dir_b103db43dffcdee3de8bd6a6d79f8618.html create mode 100644 doc/html/dir_bc297b8ee9a3081b188fc7e3fa93504a.html create mode 100644 doc/html/dir_bf10854db571cf772d4a6211be1232f2.html create mode 100644 doc/html/dir_cb081b49593532e6f6dadd15ccd82ab4.html create mode 100644 doc/html/dir_cd1412f659f324388a4b25bf5c39d8d8.html create mode 100644 doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html create mode 100644 doc/html/dir_d0f078c01bc3ce273c081eaac57e44d3.html create mode 100644 doc/html/dir_d44c64559bbebec7f509842c48db8b23.html create mode 100644 doc/html/dir_d66497626fcb891eefdcc285752aeb54.html create mode 100644 doc/html/dir_d709b210fbffdc23d8da2b53f1d32b88.html create mode 100644 doc/html/dir_d726b7599710abf1b6039c9dcf7cf687.html create mode 100644 doc/html/dir_d7aafa31aa39c71b1ecb09f780e2341f.html create mode 100644 doc/html/dir_db9b1b9045937e68e72f268df7b71cec.html create mode 100644 doc/html/dir_de142c35e6fa30e4531b188c66688c85.html create mode 100644 doc/html/dir_e0b70151b30581f59638c2f3a5122668.html create mode 100644 doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html create mode 100644 doc/html/dir_e68e8157741866f444e17edd764ebbae.html create mode 100644 doc/html/doc.png create mode 100644 doc/html/doc.svg create mode 100644 doc/html/docd.svg create mode 100644 doc/html/doxygen.css create mode 100644 doc/html/doxygen.svg create mode 100644 doc/html/dynsections.js create mode 100644 doc/html/example_2cuse_8c.html create mode 100644 doc/html/example_2cuse_8c_source.html create mode 100644 doc/html/example_2cuse__client_8c.html create mode 100644 doc/html/example_2cuse__client_8c_source.html create mode 100644 doc/html/example_2hello_8c.html create mode 100644 doc/html/example_2hello_8c_source.html create mode 100644 doc/html/example_2hello__ll_8c.html create mode 100644 doc/html/example_2hello__ll_8c_source.html create mode 100644 doc/html/example_2hello__ll__uds_8c.html create mode 100644 doc/html/example_2hello__ll__uds_8c_source.html create mode 100644 doc/html/example_2invalidate__path_8c.html create mode 100644 doc/html/example_2invalidate__path_8c_source.html create mode 100644 doc/html/example_2ioctl_8c.html create mode 100644 doc/html/example_2ioctl_8c_source.html create mode 100644 doc/html/example_2ioctl_8h.html create mode 100644 doc/html/example_2ioctl_8h_source.html create mode 100644 doc/html/example_2ioctl__client_8c.html create mode 100644 doc/html/example_2ioctl__client_8c_source.html create mode 100644 doc/html/example_2notify__inval__entry_8c.html create mode 100644 doc/html/example_2notify__inval__entry_8c_source.html create mode 100644 doc/html/example_2notify__inval__inode_8c.html create mode 100644 doc/html/example_2notify__inval__inode_8c_source.html create mode 100644 doc/html/example_2notify__store__retrieve_8c.html create mode 100644 doc/html/example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/example_2null_8c.html create mode 100644 doc/html/example_2null_8c_source.html create mode 100644 doc/html/example_2passthrough_8c.html create mode 100644 doc/html/example_2passthrough_8c_source.html create mode 100644 doc/html/example_2passthrough__fh_8c.html create mode 100644 doc/html/example_2passthrough__fh_8c_source.html create mode 100644 doc/html/example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/example_2passthrough__ll_8c.html create mode 100644 doc/html/example_2passthrough__ll_8c_source.html create mode 100644 doc/html/example_2poll_8c.html create mode 100644 doc/html/example_2poll_8c_source.html create mode 100644 doc/html/example_2poll__client_8c.html create mode 100644 doc/html/example_2poll__client_8c_source.html create mode 100644 doc/html/example_2printcap_8c.html create mode 100644 doc/html/example_2printcap_8c_source.html create mode 100644 doc/html/fast17-vangoor.pdf create mode 100644 doc/html/files.html create mode 100644 doc/html/folderclosed.png create mode 100644 doc/html/folderclosed.svg create mode 100644 doc/html/folderclosedd.svg create mode 100644 doc/html/folderopen.png create mode 100644 doc/html/folderopen.svg create mode 100644 doc/html/folderopend.svg create mode 100644 doc/html/functions.html create mode 100644 doc/html/functions_vars.html create mode 100644 doc/html/fuse-3_817_81-rc0_2build_2fuse__config_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2build_2libfuse__config_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2build_2meson-private_2sanitycheckc_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2cuse_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2cuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2hello_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2hello_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2null_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2null_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2poll_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2poll_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2printcap_8c.html create mode 100644 doc/html/fuse-3_817_81-rc0_2example_2printcap_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse_8h.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__kernel_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h.html create mode 100644 doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2buffer_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2compat_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__i_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__log_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__misc_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__opt_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2fuse__signals_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2helper_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2mount_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2mount__bsd_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2util_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2lib_2util_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2test_2readdir__inode_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2test_2release__unlink__race_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2test_2stracedecode_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2test_2test__setattr_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2test_2test__syscalls_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2test_2test__write__cache_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2test_2wrong__command_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2util_2fusermount_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc0_2util_2mount_8fuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2cuse_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2cuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2hello_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2hello_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2null_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2null_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2poll_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2poll_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2printcap_8c.html create mode 100644 doc/html/fuse-3_817_81-rc1_2example_2printcap_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse_8h.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__kernel_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h.html create mode 100644 doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2buffer_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2compat_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__i_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__log_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__misc_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__opt_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2fuse__signals_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2helper_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2mount_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2mount__bsd_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2util_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2lib_2util_8h_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2test_2readdir__inode_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2test_2release__unlink__race_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2test_2stracedecode_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2test_2test__setattr_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2test_2test__syscalls_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2test_2test__write__cache_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2test_2wrong__command_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2util_2fusermount_8c_source.html create mode 100644 doc/html/fuse-3_817_81-rc1_2util_2mount_8fuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2build_2fuse__config_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2build_2libfuse__config_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2build_2meson-private_2sanitycheckc_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2cuse_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2cuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2hello_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2hello_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2null_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2null_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2passthrough__helpers_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2poll_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2poll_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2printcap_8c.html create mode 100644 doc/html/fuse-3_817_81_8dir_2example_2printcap_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse_8h.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__kernel_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h.html create mode 100644 doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2buffer_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2compat_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__i_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__log_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__misc_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__opt_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2fuse__signals_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2helper_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2mount_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2mount__bsd_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2util_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2lib_2util_8h_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2readdir__inode_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2release__unlink__race_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2stracedecode_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2test__setattr_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2test__syscalls_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2test__want__conversion_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2test__write__cache_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2test_2wrong__command_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2util_2fusermount_8c_source.html create mode 100644 doc/html/fuse-3_817_81_8dir_2util_2mount_8fuse_8c_source.html create mode 100644 doc/html/fuse_8c_source.html create mode 100644 doc/html/fuse_8h.html create mode 100644 doc/html/fuse_8h_source.html create mode 100644 doc/html/fuse__common_8h.html create mode 100644 doc/html/fuse__common_8h_source.html create mode 100644 doc/html/fuse__config_8h_source.html create mode 100644 doc/html/fuse__i_8h_source.html create mode 100644 doc/html/fuse__kernel_8h_source.html create mode 100644 doc/html/fuse__log_8c_source.html create mode 100644 doc/html/fuse__log_8h.html create mode 100644 doc/html/fuse__log_8h_source.html create mode 100644 doc/html/fuse__loop_8c_source.html create mode 100644 doc/html/fuse__loop__mt_8c_source.html create mode 100644 doc/html/fuse__lowlevel_8c_source.html create mode 100644 doc/html/fuse__lowlevel_8h.html create mode 100644 doc/html/fuse__lowlevel_8h_source.html create mode 100644 doc/html/fuse__misc_8h_source.html create mode 100644 doc/html/fuse__mount__compat_8h_source.html create mode 100644 doc/html/fuse__opt_8c_source.html create mode 100644 doc/html/fuse__opt_8h.html create mode 100644 doc/html/fuse__opt_8h_source.html create mode 100644 doc/html/fuse__signals_8c_source.html create mode 100644 doc/html/fusermount_8c_source.html create mode 100644 doc/html/globals.html create mode 100644 doc/html/globals_defs.html create mode 100644 doc/html/globals_enum.html create mode 100644 doc/html/globals_eval.html create mode 100644 doc/html/globals_func.html create mode 100644 doc/html/globals_type.html create mode 100644 doc/html/hello_8c.html create mode 100644 doc/html/hello_8c_source.html create mode 100644 doc/html/hello__ll_8c.html create mode 100644 doc/html/hello__ll_8c_source.html create mode 100644 doc/html/hello__ll__uds_8c.html create mode 100644 doc/html/hello__ll__uds_8c_source.html create mode 100644 doc/html/helper_8c_source.html create mode 100644 doc/html/iconv_8c_source.html create mode 100644 doc/html/include_2cuse__lowlevel_8h_source.html create mode 100644 doc/html/include_2fuse_8h.html create mode 100644 doc/html/include_2fuse_8h_source.html create mode 100644 doc/html/include_2fuse__common_8h.html create mode 100644 doc/html/include_2fuse__common_8h_source.html create mode 100644 doc/html/include_2fuse__kernel_8h_source.html create mode 100644 doc/html/include_2fuse__log_8h.html create mode 100644 doc/html/include_2fuse__log_8h_source.html create mode 100644 doc/html/include_2fuse__lowlevel_8h.html create mode 100644 doc/html/include_2fuse__lowlevel_8h_source.html create mode 100644 doc/html/include_2fuse__mount__compat_8h_source.html create mode 100644 doc/html/include_2fuse__opt_8h.html create mode 100644 doc/html/include_2fuse__opt_8h_source.html create mode 100644 doc/html/index.html create mode 100644 doc/html/invalidate__path_8c.html create mode 100644 doc/html/invalidate__path_8c_source.html create mode 100644 doc/html/ioctl_8c.html create mode 100644 doc/html/ioctl_8c_source.html create mode 100644 doc/html/ioctl_8h.html create mode 100644 doc/html/ioctl_8h_source.html create mode 100644 doc/html/ioctl__client_8c.html create mode 100644 doc/html/ioctl__client_8c_source.html create mode 100644 doc/html/jquery.js create mode 100644 doc/html/lib_2buffer_8c_source.html create mode 100644 doc/html/lib_2compat_8c_source.html create mode 100644 doc/html/lib_2cuse__lowlevel_8c_source.html create mode 100644 doc/html/lib_2fuse_8c_source.html create mode 100644 doc/html/lib_2fuse__i_8h_source.html create mode 100644 doc/html/lib_2fuse__log_8c_source.html create mode 100644 doc/html/lib_2fuse__loop_8c_source.html create mode 100644 doc/html/lib_2fuse__loop__mt_8c_source.html create mode 100644 doc/html/lib_2fuse__lowlevel_8c_source.html create mode 100644 doc/html/lib_2fuse__misc_8h_source.html create mode 100644 doc/html/lib_2fuse__opt_8c_source.html create mode 100644 doc/html/lib_2fuse__signals_8c_source.html create mode 100644 doc/html/lib_2helper_8c_source.html create mode 100644 doc/html/lib_2modules_2iconv_8c_source.html create mode 100644 doc/html/lib_2modules_2subdir_8c_source.html create mode 100644 doc/html/lib_2mount_8c_source.html create mode 100644 doc/html/lib_2mount__bsd_8c_source.html create mode 100644 doc/html/lib_2mount__util_8c_source.html create mode 100644 doc/html/lib_2mount__util_8h_source.html create mode 100644 doc/html/lib_2util_8c_source.html create mode 100644 doc/html/lib_2util_8h_source.html create mode 100644 doc/html/libfuse__config_8h_source.html create mode 100644 doc/html/menu.js create mode 100644 doc/html/menudata.js create mode 100644 doc/html/minus.svg create mode 100644 doc/html/minusd.svg create mode 100644 doc/html/mount_8c_source.html create mode 100644 doc/html/mount_8fuse_8c_source.html create mode 100644 doc/html/mount__bsd_8c_source.html create mode 100644 doc/html/mount__util_8c_source.html create mode 100644 doc/html/mount__util_8h_source.html create mode 100644 doc/html/nav_f.png create mode 100644 doc/html/nav_fd.png create mode 100644 doc/html/nav_g.png create mode 100644 doc/html/nav_h.png create mode 100644 doc/html/nav_hd.png create mode 100644 doc/html/notify__inval__entry_8c.html create mode 100644 doc/html/notify__inval__entry_8c_source.html create mode 100644 doc/html/notify__inval__inode_8c.html create mode 100644 doc/html/notify__inval__inode_8c_source.html create mode 100644 doc/html/notify__store__retrieve_8c.html create mode 100644 doc/html/notify__store__retrieve_8c_source.html create mode 100644 doc/html/null_8c.html create mode 100644 doc/html/null_8c_source.html create mode 100644 doc/html/open.png create mode 100644 doc/html/passthrough_8c.html create mode 100644 doc/html/passthrough_8c_source.html create mode 100644 doc/html/passthrough__fh_8c.html create mode 100644 doc/html/passthrough__fh_8c_source.html create mode 100644 doc/html/passthrough__helpers_8h_source.html create mode 100644 doc/html/passthrough__ll_8c.html create mode 100644 doc/html/passthrough__ll_8c_source.html create mode 100644 doc/html/plus.svg create mode 100644 doc/html/plusd.svg create mode 100644 doc/html/poll_8c.html create mode 100644 doc/html/poll_8c_source.html create mode 100644 doc/html/poll__client_8c.html create mode 100644 doc/html/poll__client_8c_source.html create mode 100644 doc/html/printcap_8c.html create mode 100644 doc/html/printcap_8c_source.html create mode 100644 doc/html/readdir__inode_8c_source.html create mode 100644 doc/html/release__unlink__race_8c_source.html create mode 100644 doc/html/sanitycheckc_8c_source.html create mode 100644 doc/html/splitbar.png create mode 100644 doc/html/splitbard.png create mode 100644 doc/html/stracedecode_8c_source.html create mode 100644 doc/html/structfuse__args.html create mode 100644 doc/html/structfuse__buf.html create mode 100644 doc/html/structfuse__bufvec.html create mode 100644 doc/html/structfuse__cmdline__opts.html create mode 100644 doc/html/structfuse__config.html create mode 100644 doc/html/structfuse__conn__info.html create mode 100644 doc/html/structfuse__context.html create mode 100644 doc/html/structfuse__ctx.html create mode 100644 doc/html/structfuse__entry__param.html create mode 100644 doc/html/structfuse__ext__header.html create mode 100644 doc/html/structfuse__file__info.html create mode 100644 doc/html/structfuse__loop__config.html create mode 100644 doc/html/structfuse__lowlevel__ops.html create mode 100644 doc/html/structfuse__module.html create mode 100644 doc/html/structfuse__operations.html create mode 100644 doc/html/structfuse__opt.html create mode 100644 doc/html/structfuse__supp__groups.html create mode 100644 doc/html/structlibfuse__version.html create mode 100644 doc/html/subdir_8c_source.html create mode 100644 doc/html/sync_off.png create mode 100644 doc/html/sync_on.png create mode 100644 doc/html/tab_a.png create mode 100644 doc/html/tab_ad.png create mode 100644 doc/html/tab_b.png create mode 100644 doc/html/tab_bd.png create mode 100644 doc/html/tab_h.png create mode 100644 doc/html/tab_hd.png create mode 100644 doc/html/tab_s.png create mode 100644 doc/html/tab_sd.png create mode 100644 doc/html/tabs.css create mode 100644 doc/html/test_2hello_8c.html create mode 100644 doc/html/test_2hello_8c_source.html create mode 100644 doc/html/test_2readdir__inode_8c_source.html create mode 100644 doc/html/test_2release__unlink__race_8c_source.html create mode 100644 doc/html/test_2stracedecode_8c_source.html create mode 100644 doc/html/test_2test__setattr_8c_source.html create mode 100644 doc/html/test_2test__syscalls_8c_source.html create mode 100644 doc/html/test_2test__want__conversion_8c_source.html create mode 100644 doc/html/test_2test__write__cache_8c_source.html create mode 100644 doc/html/test_2wrong__command_8c_source.html create mode 100644 doc/html/test__setattr_8c_source.html create mode 100644 doc/html/test__syscalls_8c_source.html create mode 100644 doc/html/test__want__conversion_8c_source.html create mode 100644 doc/html/test__write__cache_8c_source.html create mode 100644 doc/html/util_2fusermount_8c_source.html create mode 100644 doc/html/util_2mount_8fuse_8c_source.html create mode 100644 doc/html/util_8c_source.html create mode 100644 doc/html/util_8h_source.html create mode 100644 doc/html/wrong__command_8c_source.html create mode 100644 doc/kernel.txt create mode 100644 doc/libfuse-operations.txt create mode 100644 doc/mainpage.dox create mode 100644 doc/meson.build create mode 100644 doc/mount.fuse3.8 create mode 100644 example/cuse.c create mode 100644 example/cuse_client.c create mode 100644 example/cxxopts.hpp create mode 100644 example/hello.c create mode 100644 example/hello_ll.c create mode 100644 example/hello_ll_uds.c create mode 100644 example/invalidate_path.c create mode 100644 example/ioctl.c create mode 100644 example/ioctl.h create mode 100644 example/ioctl_client.c create mode 100644 example/memfs_ll.cc create mode 100644 example/meson.build create mode 100644 example/notify_inval_entry.c create mode 100644 example/notify_inval_inode.c create mode 100644 example/notify_store_retrieve.c create mode 100644 example/null.c create mode 100644 example/passthrough.c create mode 100644 example/passthrough_fh.c create mode 100644 example/passthrough_helpers.h create mode 100644 example/passthrough_hp.cc create mode 100644 example/passthrough_ll.c create mode 100644 example/poll.c create mode 100644 example/poll_client.c create mode 100644 example/printcap.c create mode 100644 include/cuse_lowlevel.h create mode 100644 include/fuse.h create mode 100644 include/fuse_common.h create mode 100644 include/fuse_kernel.h create mode 100644 include/fuse_log.h create mode 100644 include/fuse_lowlevel.h create mode 100644 include/fuse_mount_compat.h create mode 100644 include/fuse_opt.h create mode 100644 include/meson.build create mode 100644 lib/buffer.c create mode 100644 lib/compat.c create mode 100644 lib/cuse_lowlevel.c create mode 100644 lib/fuse.c create mode 100644 lib/fuse_i.h create mode 100644 lib/fuse_log.c create mode 100644 lib/fuse_loop.c create mode 100644 lib/fuse_loop_mt.c create mode 100644 lib/fuse_lowlevel.c create mode 100644 lib/fuse_misc.h create mode 100644 lib/fuse_opt.c create mode 100644 lib/fuse_signals.c create mode 100644 lib/fuse_versionscript create mode 100644 lib/helper.c create mode 100644 lib/meson.build create mode 100644 lib/modules/iconv.c create mode 100644 lib/modules/subdir.c create mode 100644 lib/mount.c create mode 100644 lib/mount_bsd.c create mode 100644 lib/mount_util.c create mode 100644 lib/mount_util.h create mode 100644 lib/util.c create mode 100644 lib/util.h create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 requirements.txt create mode 100644 signify/fuse-3.15.pub create mode 100644 signify/fuse-3.16.pub create mode 100644 signify/fuse-3.17.pub create mode 100644 signify/fuse-3.18.pub create mode 100755 test/ci-build.sh create mode 100644 test/conftest.py create mode 100644 test/hello.c create mode 100644 test/lsan_suppress.txt create mode 100644 test/meson.build create mode 100644 test/pytest.ini create mode 100644 test/readdir_inode.c create mode 100644 test/release_unlink_race.c create mode 100644 test/stracedecode.c create mode 100644 test/test_ctests.py create mode 100644 test/test_custom_io.py create mode 100755 test/test_examples.py create mode 100644 test/test_setattr.c create mode 100644 test/test_syscalls.c create mode 100644 test/test_want_conversion.c create mode 100644 test/test_write_cache.c create mode 100644 test/util.py create mode 100644 test/wrong_command.c create mode 100644 tsan_suppressions.txt create mode 100644 util/fuse.conf create mode 100644 util/fusermount.c create mode 100755 util/init_script create mode 100755 util/install_helper.sh create mode 100644 util/meson.build create mode 100644 util/mount.fuse.c create mode 100755 util/parse-backtrace.sh create mode 100644 util/udev.rules create mode 100644 xfstests/README.md create mode 100644 xfstests/local.config create mode 100755 xfstests/mount.fuse.passthrough diff --git a/.ackrc b/.ackrc new file mode 100644 index 0000000..c8f5d0a --- /dev/null +++ b/.ackrc @@ -0,0 +1,2 @@ +--ignore-dir=build +--ignore-dir=doc/html diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3ebac7c --- /dev/null +++ b/.clang-format @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..cbd941f --- /dev/null +++ b/.codespellrc @@ -0,0 +1,13 @@ +[codespell] +skip = .git,*.pdf,*.svg,AUTHORS + +# The following strings shouldn't actually be accepted, but they're wrongly +# identified as words and there is currently no way to exclude them on +# a by-line basis (https://github.com/codespell-project/codespell/pull/2400). +# Therefore, pretend that they are correctly spelled words: +# - alse: used in regex +# - siz: wanted short +# - fiter: variable +# - re-used: intentional hyphenation +# - re-using: intentional hyphenation +ignore-words-list = alse,siz,fiter,re-used,re-using diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..628f512 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,47 @@ +((python-mode . ((indent-tabs-mode . nil))) + (autoconf-mode . ((indent-tabs-mode . t))) + (c++-mode . ((c-file-style . "k&r") + (indent-tabs-mode . nil) + (c-basic-offset . 4) + (c-file-offsets . + ((block-close . 0) + (brace-list-close . 0) + (brace-list-entry . 0) + (brace-list-intro . +) + (case-label . 0) + (class-close . 0) + (defun-block-intro . +) + (defun-close . 0) + (defun-open . 0) + (else-clause . 0) + (inclass . +) + (label . 0) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + (topmost-intro . 0))))) + (c-mode . ((c-file-style . "stroustrup") + (indent-tabs-mode . t) + (tab-width . 8) + (c-basic-offset . 8) + (c-file-offsets . + ((block-close . 0) + (brace-list-close . 0) + (brace-list-entry . 0) + (brace-list-intro . +) + (case-label . 0) + (class-close . 0) + (defun-block-intro . +) + (defun-close . 0) + (defun-open . 0) + (else-clause . 0) + (inclass . +) + (label . 0) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + (topmost-intro . 0)))))) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..c2ea83a --- /dev/null +++ b/AUTHORS @@ -0,0 +1,277 @@ +Current Maintainers +------------------ + +Bernd Schubert +Ashley Pittman +Antonio SJ Musumeci + + +Past Maintainers +---------------- + +Nikolaus Rath (until 02/2024) +Miklos Szeredi (until 12/2015) + + +Contributors +------------ + +CUSE has been written by Tejun Heo . Furthermore, the +following people have contributed patches (autogenerated list): + + +1c7718e7 +a1346054 <36859588+a1346054@users.noreply.github.com> +admorgan +Ahmed Masud +AKowshik +Alan Somers +Albert Chen <58009229+hselin-kalista-io@users.noreply.github.com> +Albert Chen +Aleksandr Mikhailov +Alexander +alex +Alex Richman +Amir Goldstein +amosonn +Anatol Pomozov +André Schröder +Andrew Gaul +Andrew Gaul +Angelo G. Del Regno +Anthony Rebello +Antonio SJ Musumeci +Arunav Sanyal +asafkahlon <35964924+asafkahlon@users.noreply.github.com> +Ashley Pittman +AsumFace +Banglang +Baptiste Daroussin +Benjamin Barenblat +Bernd Schubert +Bernd Schubert +Bill Zissimooulos +Bill Zissimopoulos +bobrofon +Brian Naylor +Carl Edquist +Carlos Maiolino +Chad Austin +Changli Gao +Christian Menges +Christopher Harrison +Ciaran +Consus +Craig Chi +Csaba Henk +Csaba Henk +cvs2git <> +Dalvik Khertel +Daniel Fullmer +Daniel Thau +David Galeano +David McNab +David Sheets +dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +Dharmendra singh +Dharmendra Singh +divinity76 +DrDaveD <2129743+DrDaveD@users.noreply.github.com> +Dr. David Alan Gilbert +Emily Herbert +Emmanuel Dreyfus +Enke Chen +Eric Engestrom +Eric Wong +Etienne Dublé +Fabian Vogt +Fabrice Bauzac +Fabrice Fontaine +Fedor Korotkov +Feng Shuo +ferivoz <72023087+ferivoz@users.noreply.github.com> +Feverfew +Fina Wilke +Florian Weimer +Forty-Bot +Frank Dinoff +Giulio Benetti +Giuseppe Scrivano +Goswin von Brederlow +guraga +HazelFZ +Heiko Becker +Hendrik Brueckner +HereThereBeDragons +Hookey +human +ikbenlike +Ikey Doherty +itsdeepak +Jan Blumschein +Jann Horn +Jay Hankins +Jean-Pierre André +Jean-Yves VET +Jérémie Galarneau +Joachim Schiele +Joachim Schiele +Joerg Thalheim +John Baber-Lucero +John Muir +Joseph Dodge +Josh Soref +Junichi Uekawa +Junichi Uekawa +Junichi Uekawa +Kangjing "Chaser" Huang +Ken Schalk +Kevin Vigor +Kirill Smelkov +Kyle Lippincott +Laszlo Boszormenyi (GCS) +Laszlo Papp +Laurent Bigonville +Lilo Huang +Liu Bo +Li-Wen Hsu +lixiaokeng <63774002+lixiaokeng@users.noreply.github.com> +lixiaokeng +Luis Henriques +Madan Valluri +Manuel Jacob +Marcin Sulikowski +Mark Glines +Martin Blanchard +Martin Pärtel +Mateusz Urbańczyk +Matthias Goergens +Matthias Görgens +Mattias Nissler +maxice8 <30738253+maxice8@users.noreply.github.com> +Maximilian Heinzler +Max Krasnyansky +Michael Forney +Michael Grigoriev +Mihail Konev +Miklos Szeredi +Miklos Szeredi +Miklos Szeredi +Miklos Szeredi +Misono Tomohiro +mkmm@gmx-topmail.de +mrdvdrm +Natanael Copa +Niels de Vos +Nikola Petrov <73067824+Petrov22Nikola@users.noreply.github.com> +Nikolaus Rath +Nozomi Miyamori <99280467+nm004@users.noreply.github.com> +Oded Arbel +Olivier Blin +pablomh +Pedro Kaj Kjellerup Nacht +Pedro Nacht +Peri +Peter Lemenkov +philmd +Pierre Labastie +Przemyslaw Pawelczyk +Przemysław Pawełczyk +psykose +Ratna_Bolla@dell.com +Rethan <359062468@qq.con> +Reuben Hawkins +rfjakob +richardweinberger +Richard W.M. Jones +Riku Voipio +Robo Shimmer +Roland Bauerschmidt +Roman Bogorodskiy +Rosen Penev +Rostislav +Rostislav Skudnov +Rudi Heitbaum +Sam Huffman <40582525+samh-sifive@users.noreply.github.com> +Sam James +Sam Stuewe +Sangwoo Moon +Sarath Lakshman +Sargun Dhillon +scosu +Scott Worley +Sebastian Pipping +Sergey Fedoseev +Seunghoon Yeon +Sławek Rudnicki +Stefan Hajnoczi +Stefan Hajnoczi +Stephen Kitt +Tej Chajed +tenzap <46226844+tenzap@users.noreply.github.com> +therealneworld@gmail.com +Tobias Nießen +Tofik Sonono +Tomasz Kulasek <34129113+tkulasek@users.noreply.github.com> +Tom Callaway +Tom Callaway +Tomohiro Kusumi +userwithuid +Valentin Plugaru +Vivek Goyal +Waldir Pimenta +wdlkmpx +William Woodruff +Winfried Koehler +winndows +Xiubo Li +Yaroslav Halchenko +y +Yuri Per +Zhansong Gao +Zhiqiang Liu +zsugabubus + +# New authors since fuse-3.16.2 +farlongsignal <141166749+farlongsignal@users.noreply.github.com> +yangyun50 <149988609+yangyun50@users.noreply.github.com> +bigbrotherwei <1965867461@qq.com> +Caian Benedicto <2220062+Caian@users.noreply.github.com> +desertwitch <24509509+desertwitch@users.noreply.github.com> +SteveYang <40466358+SteveY4ng@users.noreply.github.com> +FredyVia <942513309@qq.com> +legezywzh <94814730+legezywzh@users.noreply.github.com> +CismonX +amitgeron +Bernd Schubert +Daniel Rosenberg +Horst Birthelmer +Joanne Koong +Josef Bacik +Matthew +gandalfs_cat +MJ Harvey +Nils +Norman Wilson +leipeng +Vladimir Serbinenko +George Hilliard +Tyler Hall +yangyun +Abhishek + +# New authors since fuse-3.17.0 +Luis Henriques +Zegang + +# New authors since fuse-3.17.1-rc0 +Maksim Harbachou +Vassili Tchersky + +# New authors since fuse-3.17.1-rc1 +jnr0006 +Vassili Tchersky + +# New authors since fuse-3.17.1 +swj <1186093704@qq.com> +Ben Dooks diff --git a/ChangeLog.rst b/ChangeLog.rst new file mode 100644 index 0000000..5113f44 --- /dev/null +++ b/ChangeLog.rst @@ -0,0 +1,891 @@ +libfuse 3.17.2 (2025-04-23) +=========================== +* Fixed uninitized bufsize value (compilation warning and real + issue when HAVE_SPLICE was not defined) +* Fixed initialization races related to buffer realocation when + large buf sizes are used (/proc/sys/fs/fuse/max_pages_limit) +* Fix build with kernel < 5.9 +* Fix static_assert build failure with C++ version < 11 +* Compilation fix (remove second fuse_main_real_versioned declaration) +* Another conn.want flag conversion fix for high-level applications +* Check if pthread_setname_np() exists before use it +* fix example/memfs_ll rename deadlock error +* signal handlers: Store fuse_session unconditionally and restore + previous behavior that with multiple sessions the last session + was used for the signal exist handler + +libfuse 3.17.1 (2025-03-24) +=========================== +* fuse: Fix want conn.want flag conversion +* Prevent re-usage of stdio FDs for fusermount +* PanFS added to fusermount whitelist + +libfuse 3.17.1-rc1 (2025-02-18) +=============================== +* several BSD fixes +* x86 (32bit) build fixes +* nested declarations moved out of the inlined functions to avoid + build warnings +* signify public key added for future 3.18 + +libfuse 3.17.1-rc0 (2025-02.10) +=============================== + +* Fix libfuse build with FUSE_USE_VERSION 30 +* Fix build of memfs_ll without manual meson reconfigure +* Fix junk readdirplus results when filesystem not filling stat info +* Fix conn.want_ext truncation to 32bit +* Fix some build warnings with -Og +* Fix fuse_main_real symbols +* Several changes related to functions/symbols that added in + the libfuse version in 3.17 +* Add thread names to libfuse threads +* With auto-umounts the FUSE_COMMFD2 (parent process fd is + exported to be able to silence leak checkers + + +libfuse 3.17 (2025-01-01, not officially releaesed) +================================================== + +* 3.11 and 3.14.2 introduced ABI incompatibilities, the ABI is restored + to 3.10, .so version was increased since there were releases with + the incompatible ABI + +* The libfuse version a program was compiled against is now encoded into + that program, using inlined functions in fuse_lowlevel.h and fuse.h +* Allows to handle fatal signals and to print a backtrace. + New API function: fuse_set_fail_signal_handlers() + +* Allows fuse_log() messages to be send to syslog instead of stderr + New API functions: fuse_log_enable_syslog() and fuse_log_close_syslog() + +* Handle buffer misalignment for FUSE_WRITE + +* Added support for filesystem passthrough read/write of files when + FUSE_PASSTHROUGH capability is enabled + New API functions: fuse_passthrough_open() and fuse_passthrough_close(), + also see example/passthrough_hp.cc + +* Added fmask and dmask options to high-level API + - dmask: umask applied to directories + - fmask: umask applied to non-directories + +* Added FUSE_FILL_DIR_DEFAULTS enum to support C++ programs using + fuse_fill_dir_t function + +* Added support for FUSE_CAP_HANDLE_KILLPRIV_V2 + +Fixes: +* Fixed compilation failure on FreeBSD (mount_bsd.c now points to correct + header) + +libfuse 3.16.2 (2023-10-10) +=========================== + +* Various small fixes and improvements. + +libfuse 3.16.1 (2023-08-08) +=========================== + +* Readdir kernel cache can be enabled from high-level API. + +libfuse 3.15.1 (2023-07-05) +=========================== + +Future libfuse releases will be signed with `signify`_ rather than PGP (rationale_). This +release is the last to be signed with PGP and contains the signify public key for current +(3.15.X) and upcoming (3.16.X) minor release cycle. + +.. _signify: https://www.openbsd.org/papers/bsdcan-signify.html +.. _rationale: https://latacora.micro.blog/2019/07/16/the-pgp-problem.html + + +libfuse 3.15.0 (2023-06-09) +=========================== + +* Improved support for some less common systems (32 bit, alternative libcs) + +* Unsupported mount options are no longer silently accepted. + +* auto_unmount is now compatible with allow_other. + + +libfuse 3.14.1 (2023-03-26) +=========================== + +* The extended attribute name passed to the setxattr() handler is no longer + truncated at the beginning (bug introduced in 3.13.0). + +* As a result of the above, the additional setattr() flags introduced in 3.14 are no + longer available for now. They will hopefully be reintroduced in the next release. + +* Further improvements of configuration header handling. + + +libfuse 3.14.0 (2023-02-17) +=========================== + +* Properly fix the header installation issue. The fix in 3.13.1 resulted + in conflicts with other packages. + +* Introduce additional setattr() flags (FORCE, KILL_SUID, KILL_SGID, FILE, KILL_PRIV, + OPEN, TIMES_SET) + + +libfuse 3.13.1 (2023-02-03) +=========================== + +* Fixed an issue that resulted in errors when attempting to compile against + installed libfuse headers (because libc symbol versioning support was not + detected correctly in this case). + +libfuse 3.13.0 (2023-01-13) +=========================== + +* There is a new low-level API function `fuse_session_custom_io` that allows to implement + a daemon with a custom io. This can be used to create a daemon that can process incoming + FUSE requests to other destinations than `/dev/fuse`. + +* A segfault when loading custom FUSE modules has been fixed. + +* There is a new `fuse_notify_expire_entry` function. + +* A deadlock when resolving paths in the high-level API has been fixed. + +* libfuse can now be build explicitly for C libraries without symbol versioning support. + +libfuse 3.12.0 (2022-09-08) +=========================== + +* There is a new build parameter to specify where the SysV init script should be + installed. + +* The *max_idle_threads* parameter has been deprecated in favor of the new max_threads* + parameter (which avoids the excessive overhead of creating and destructing threads). + Using max_threads == 1 and calling fuse_session_loop_mt() will run single threaded + similar to fuse_session_loop(). + +The following changes apply when using the most recent API (-DFUSE_USE_VERSION=312, +see `example/passthrough_hp.cc` for an example for how to usse the new API): + +* `struct fuse_loop_config` is now private and has to be constructed using + *fuse_loop_cfg_create()* and destroyed with *fuse_loop_cfg_destroy()*. Parameters can be + changed using `fuse_loop_cfg_set_*()` functions. + +* *fuse_session_loop_mt()* now accepts `struct fuse_loop_config *` as NULL pointer. + +* *fuse_parse_cmdline()* now accepts a *max_threads* option. + + +libfuse 3.11.0 (2022-05-02) +=========================== + +* Add support for flag FOPEN_NOFLUSH for avoiding flush on close. +* Fixed returning an error condition to ioctl(2) + + +libfuse 3.10.5 (2021-09-06) +=========================== + +* Various improvements to make unit tests more robust. + + +libfuse 3.10.4 (2021-06-09) +=========================== + +* Building of unit tests is now optional. +* Fixed a test failure when running tests under XFS. +* Fixed memory leaks in examples. +* Minor documentation fixes. + +libfuse 3.10.3 (2021-04-12) +=========================== + +* Fix returning d_ino and d_type from readdir(3) in non-plus mode + +libfuse 3.10.2 (2021-02-05) +=========================== + +* Allow "nonempty" as a mount option, for backwards compatibility with fusermount 2. The + option has no effect since mounting over non-empty directories is allowed by default. +* Fix returning inode numbers from readdir() in offset==0 mode. +* FUSE filesystems can now be mounted underneath EXFAT mountpoints. +* Various minor bugfixes. + +libfuse 3.10.1 (2020-12-07) +=========================== + +* Various minor bugfixes. + +libfuse 3.10.0 (2020-10-09) +=========================== + +* Add FUSE_CAP_CACHE_SYMLINKS: allow caching symlinks in kernel page cache. +* Various minor bugfixes and improvements. + +libfuse 3.9.4 (2020-08-09) +========================== + +This was an "accidental" release, it is equivalent to 3.9.3. + +libfuse 3.9.3 (2020-08-09) +========================== + +* Fixed compilation under OS X and µClibc. +* Minor bugfixes and doc updates. + +libfuse 3.9.2 (2020-06-12) +========================== + +* Remove obsolete workarounds in examples. +* Do not require C++ compiler for building. +* Minor bugfixes. + +libfuse 3.9.1 (2020-03-19) +=========================== + +* Fixed memory leak in fuse_session_new(). +* Fixed an issue with the linker version script. +* Make ioctl prototype conditional on FUSE_USE_VERSION. Define FUSE_USE_VERSION < 35 to + get old ioctl prototype with int commands; define FUSE_USE_VERSION >= 35 to get new + ioctl prototype with unsigned int commands. +* Various small bugfixes. + +libfuse 3.9.0 (2019-12-14) +========================== + +* Added support for FUSE_EXPLICIT_INVAL_DATA to enable + only invalidate cached pages on explicit request. + +libfuse 3.8.0 (2019-11-03) +========================== + +* Added support for FUSE_LSEEK operation which can be used to report holes + in sparse files. + +libfuse 3.7.0 (2019-09-27) +========================== + +* Added UFSD to whitelist (so users can now mount FUSE filesystems + on mountpoints within UFSD filesystems). +* Added custom log message handler function support so that libfuse + applications can direct messages to syslog(3) or other logging systems. + stderr remains the default. See `fuse_log.h` for the new API. + +libfuse 3.6.2 (2019-07-09) +========================== + +* The init script is now installed to /etc/ rather than /usr/local/etc + by default. + +libfuse 3.6.1 (2019-06-13) +========================== + +* Fixed version number (release 3.6.0 was shipped with a declared + version of 3.0.0). + +libfuse 3.6.0 (2019-06-13) +========================== + +* Added a new example (passthrough_hp). The functionality is similar + to passthrough_ll, but the implementation focuses on performance and + correctness rather than simplicity. +* Added support for fuse kernel feature `max_pages` which allows to increase + the maximum number of pages that can be used per request. This feature was + introduced in kernel 4.20. `max_pages` is set based on the value in + `max_write`. By default `max_write` will be 1MiB now for kernels that support + `max_pages`. If you want smaller buffers or writes you have to set + `max_write` manually. + +libfuse 3.5.0 (2019-04-16) +========================== + +* Changed ioctl commands to "unsigned int" in order to support commands + which do not fit into a signed int. Commands issued by applications + are still truncated to 32 bits. +* Added SMB2 to whitelist (so users can now mount FUSE filesystems + on mountpoints within SMB 2.0 filesystems). +* Added a new `cache_readdir` flag to `fuse_file_info` to enable + caching of readdir results. Supported by kernels 4.20 and newer. +* Add support and documentation for FUSE_CAP_NO_OPENDIR_SUPPORT. + +libfuse 3.4.2 (2019-03-09) +========================== + +* Fixed a memory leak in `examples/passthrough_ll.c`. +* Added OpenAFS to whitelist (so users can now mount FUSE filesystems + on mountpoints within OpenAFS filesystems). +* Added HFS+ to whitelist (so users can now mount FUSE filesystems + on mountpoints within HFS+ filesystems). +* Documentation improvements. + +libfuse 3.4.1 (2018-12-22) +========================== + +* The `examples/passthrough_ll.c` example filesystem has been + significantly extended. +* Support for `copy_file_range` has been added. +* Build system updates for non-Linux systems. + +libfuse 3.4.0 +============= + +* Add `copy_file_range()` to support efficient copying of data from one file to + an other. + +libfuse 3.3.0 (2018-11-06) +========================== + +* The `auto_unmount` mode now works correctly in combination with + autofs. + +* The FUSE_CAP_READDIRPLUS_AUTO capability is no longer enabled by + default unless the file system defines both a readdir() and a + readdirplus() handler. + +* The description of the FUSE_CAP_READDIRPLUS_AUTO flag has been + improved. + +* Allow open `/dev/fuse` file descriptors to be passed via mountpoints of the + special format `/dev/fd/%u`. This allows mounting to be handled by the parent + so the FUSE filesystem process can run fully unprivileged. + +* Add a `drop_privileges` option to mount.fuse3 which causes it to open + `/dev/fuse` and mount the file system itself, then run the FUSE file + filesystem fully unprivileged and unable to re-acquire privilege via setuid, + fscaps, etc. + +* Documented under which conditions the `fuse_lowlevel_notify_*` + functions may block. + +libfuse 3.2.6 (2018-08-31) +========================== + +* The fuse_main() function now returns more fine-grained error codes. +* FUSE filesystems may now be mounted on mountpoint within + bcachefs, aufs and FAT filesystems. +* libfuse may now be used as a Meson subproject. +* Fix a few low-impact memory leaks. +* The `fuse.conf` file is no longer looked for in `/etc`, but in the + *sysconfdir* directory (which can be set with `meson configure`). By + default, the location is thus `/usr/local/etc/fuse.conf`. + +libfuse 3.2.5 (2018-07-24) +========================== + +* SECURITY UPDATE: In previous versions of libfuse it was possible to + for unprivileged users to specify the `allow_other` option even when + this was forbidden in `/etc/fuse.conf`. The vulnerability is + present only on systems where SELinux is active (including in + permissive mode). +* The fusermount binary has been hardened in several ways to reduce + potential attack surface. Most importantly, mountpoints and mount + options must now match a hard-coded whitelist. It is expected that + this whitelist covers all regular use-cases. +* Added a test of `seekdir` to test_syscalls. +* Fixed `readdir` bug when non-zero offsets are given to filler and the + filesystem client, after reading a whole directory, re-reads it from a + non-zero offset e. g. by calling `seekdir` followed by `readdir`. + +libfuse 3.2.4 (2018-07-11) +========================== + +* Fixed `rename` deadlock on FreeBSD. + +libfuse 3.2.3 (2018-05-11) +========================== + +* Fixed a number of compiler warnings. + +libfuse 3.2.2 (2018-03-31) +========================== + +* Added example fuse.conf file. +* Added "support" for -o nofail mount option (the option is accepted + and ignored). +* Various small bugfixes. + +libfuse 3.2.1 (2017-11-14) +========================== + +* Various small bugfixes. + +libfuse 3.2.0 (2017-09-12) +========================== + +* Support for building with autotools has been dropped. + +* Added new `fuse_invalidate_path()` routine for cache invalidation + from the high-level FUSE API, along with an example and tests. + +* There's a new `printcap` example that can be used to determine the + capabilities of the running kernel. + +* `fuse_loop_mt()` now returns the minus the actual errno if there was + an error (instead of just -1). + +* `fuse_loop()` no longer returns a positive value if the filesystem + loop was terminated without errors or signals. + +* Improved documentation of `fuse_lowlevel_notify_*` functions. + +* `fuse_lowlevel_notify_inval_inode()` and + `fuse_lowlevel_notify_inval_entry()` now return -ENOSYS instead of + an undefined error if the function is not supported by the kernel. + +* Documented the special meaning of the *zero* offset for the + fuse_fill_dir_t function. + +* The `passthrough_fh` example now works under FreeBSD. + +* libfuse can now be build without libiconv. + +* Fixed support for `FUSE_CAP_POSIX_ACL`: setting this capability + flag had no effect in the previous versions of libfuse 3.x; + now ACLs should actually work. + +* Fixed a number of compilation problems under FreeBSD. + +* Fixed installation directory for udev rules. + +* Fixed compilation with LTO. + +libfuse 3.1.1 (2017-08-06) +========================== + +* Documentation: clarified how filesystems are supposed to process + open() and create() flags (see include/fuse_lowlevel.h). + +* Fixed a compilation problem of the passthrough_ll example on + 32 bit systems (wrong check and wrong error message). + +* pkg-config is now used to determine the proper directory for + udev rules. + +* Fixed a symbol versioning problem that resulted in very strange + failures (segfaults, unexpected behavior) in different situations. + +* Fixed a test failure when /tmp is on btrfs. + +* The maximum number of idle worker threads used by `fuse_loop_mt()` + is now configurable. + +* `fuse_loop_mt()` and `fuse_session_loop_mt()` now take a + `struct fuse_loop_config` parameter that supersedes the *clone_fd* + parameter. + +* Incorporated several patches from the FreeBSD port. libfuse should + now compile under FreeBSD without the need for patches. + +* The passthrough_ll example now supports writeback caching. + +libfuse 3.1.0 (2017-07-08) +========================== + +* Added new `fuse_lib_help()` function. File-systems that previously + passed a ``--help`` option to `fuse_new()` must now process the + ``--help`` option internally and call `fuse_lib_help()` to print the + help for generic FUSE options. +* Fixed description of the `fuse_conn_info->time_gran`. The default + value of zero actually corresponds to full nanosecond resolution, + not one second resolution. +* The init script is now installed into the right location + (``$DESTDIR/etc/init.d`` rather than ``$prefix/$sysconfdir/init.d``) +* The `example/passthrough_ll` filesystem now supports creating + and writing to files. +* `fuse_main()` / `fuse_remove_signal_handlers()`: do not reset + `SIGPIPE` handler to `SIG_DFL` if it was not set by us. +* Documented the `RENAME_EXCHANGE` and `RENAME_NOREPLACE` flags that + may be passed to the `rename` handler of both the high- and + low-level API. Filesystem authors are strongly encouraged to check + that these flags are handled correctly. + +libfuse 3.0.2 (2017-05-24) +========================== + +* Option parsing for the high-level API now works correctly + (previously, default values would override specified values). +* Tests should now build (and run) under FreeBSD. +* Improved documentation of `struct fuse_context` +* Internal: calculate request buffer size from page size and kernel + page limit instead of using hardcoded 128 kB limit. + + +libfuse 3.0.1 (2017-04-10) +========================== + +* Re-introduced *examples/null.c*. +* Added experimental support for building with Meson. +* Document that `-o auto_unmount` implies `-o nodev,nosuid`. +* Document that the *use_ino* option of the high-level interface does + not affect the inode that libfuse and the kernel use internally. +* Fixed test cases for passthrough* examples (they weren't actually + testing the examples). +* Fixed several bugs in the passthrough* examples. + +libfuse 3.0.0 (2016-12-08) +========================== + +* NOTE TO PACKAGERS: + + libfuse 3 is designed to be co-installable with libfuse 2. However, + some files will be installed by both libfuse 2 and libfuse 3 + (e.g. /etc/fuse.conf, the udev and init scripts, and the + mount.fuse(8) manpage). These files should be taken from + libfuse 3. The format/content is guaranteed to remain backwards + compatible with libfuse 2. + + We recommend to ship libfuse2 and libfuse3 in three separate + packages: a libfuse-common package that contains files shared by + libfuse 2+3 (taken from the libfuse3 tarball), and libfuse2 and + libfuse3 packages that contain the shared library and helper + programs for the respective version. + +* Fixed test errors when running tests as root. + +* Made check for util-linux version more robust. + +* Added documentation for all fuse capability flags (`FUSE_CAP_*`) and + `struct fuse_conn_info` fields. + +* fuse_loop(), fuse_loop_mt(), fuse_session_loop() and + fuse_session_loop_mt() now return more detailed error codes instead + of just -1. See the documentation of fuse_session_loop() for details. + +* The FUSE main loop is now aborted if the file-system requests + capabilities that are not supported by the kernel. In this case, the + session loop is exited with a return code of ``-EPROTO``. + +* Most file-system capabilities that were opt-in in libfuse2 are now + enabled by default. Filesystem developers are encouraged to review + the documentation of the FUSE_CAP_* features to ensure that their + filesystem is compatible with the new semantics. As before, a + particular capability can still be disabled by unsetting the + corresponding bit of `fuse_conn_info.wants` in the init() handler. + +* Added FUSE_CAP_PARALLEL_DIROPS and FUSE_CAP_POSIX_ACL, + FUSE_HANDLE_KILLPRIV feature flags. + +* FUSE filesystems are now responsible for unsetting the setuid/setgid + flags when a file is written, truncated, or its owner + changed. Previously, this was handled by the kernel but subject to + race conditions. + +* The fusermount and mount.fuse binaries have been renamed to + fusermount3 and mount.fuse3 to allow co-installation of libfuse 2.x + and 3.x + +* Added a `max_read` field to `struct fuse_conn_info`. For the time + being, the maximum size of read requests has to be specified both + there *and* passed to fuse_session_new() using the ``-o + max_read=`` mount option. At some point in the future, specifying + the mount option will no longer be necessary. + +* Documentation: clarified that the fuse_argv structure that is passed + to `fuse_new()` and `fuse_lowlevel_new()` must always contain at + least one element. + +* The high-level init() handler now receives an additional struct + fuse_config pointer that can be used to adjust high-level API + specific configuration options. + +* The `nopath_flag` field of struct fuse_operations has been + removed. Instead, a new `nullpath_ok` flag can now be set + in struct fuse_config. + +* File systems that use the low-level API and support lookup requests + for '.' and '..' should continue make sure to set the + FUSE_CAP_EXPORT_SUPPORT bit in fuse_conn_info->want. + + (This has actually always been the case, but was not very obvious + from the documentation). + +* The help text generated by fuse_lowlevel_help(), fuse_new() (and + indirectly fuse_main()) no longer includes options that are unlikely + to be of interest to end-users. The full list of accepted options is + now included in the respective function's documentation (located in + the fuse.h/fuse_lowlevel.h and doc/html). + +* The ``-o nopath`` option has been dropped - it never actually did + anything (since it is unconditionally overwritten with the value of + the `nopath` flag in `struct fuse_operations`). + +* The ``-o large_read`` mount option has been dropped. Hopefully no + one uses a Linux 2.4 kernel anymore. + +* The `-o nonempty` mount point has been removed, mounting over + non-empty directories is now always allowed. This brings the + behavior of FUSE file systems in-line with the behavior of the + regular `mount` command. + + File systems that do not want to allow mounting to non-empty + directories should perform this check themselves before handing + control to libfuse. + +* The chmod, chown, truncate, utimens and getattr handlers of the + high-level API now all receive an additional struct fuse_file_info + pointer (which, however, may be NULL even if the file is currently + open). + + The fgetattr and ftruncate handlers have become obsolete and have + been removed. + +* The `fuse_session_new` function no longer accepts the ``-o + clone_fd`` option. Instead, this has become a parameter of the + `fuse_session_loop_mt` and `fuse_loop_mt` functions. + +* For low-level file systems that implement the `write_buf` handler, + the `splice_read` option is now enabled by default. As usual, this + can be changed in the file system's `init` handler. + +* The treatment of low-level options has been made more consistent: + + Options that can be set in the init() handler (via the + fuse_conn_info parameter) can now be set only here, + i.e. fuse_session_new() no longer accepts arguments that change the + fuse_conn_info object before or after the call do init(). As a side + effect, this removes the ambiguity where some options can be + overwritten by init(), while others overwrite the choices made by + init(). + + For file systems that wish to offer command line options for these + settings, the new fuse_parse_conn_info_opts() and + fuse_apply_conn_info_opts() functions are available. + + Consequently, the fuse_lowlevel_help() method has been dropped. + +* The `async_read` field in `struct fuse_conn_info` has been + removed. To determine if the kernel supports asynchronous reads, + file systems should check the `FUSE_CAP_ASYNC_READ` bit of the + `capable` field. To enable/disable asynchronous reads, file systems + should set the flag in the `wanted` field. + +* The `fuse_parse_cmdline` function no longer prints out help when the + ``--verbose`` or ``--help`` flags are given. This needs to be done + by the file system (e.g. using the `fuse_cmdline_help()` and + `fuse_lowlevel_help()` functions). + +* Added ``example/cuse_client.c`` to test ``example/cuse.c``. + +* Removed ``example/null.c``. This has not been working for a while + for unknown reasons -- maybe because it tries to treat the + mountpoint as a file rather than a directory? + +* There are several new examples that demonstrate the use of + the ``fuse_lowlevel_notify_*`` functions: + + - ``example/notify_store_retrieve.c`` + - ``example/notify_inval_inode.c`` + - ``example/notify_inval_entry.c`` + +* The ``-o big_writes`` mount option has been removed. It is now + always active. File systems that want to limit the size of write + requests should use the ``-o max_write=`` option instead. + +* The `fuse_lowlevel_new` function has been renamed to + `fuse_session_new` and no longer interprets the --version or --help + options. To print help or version information, use the new + `fuse_lowlevel_help` and `fuse_lowlevel_version` functions. + +* The ``allow_other`` and ``allow_root`` mount options (accepted by + `fuse_session_new()`) may now be specified together. In this case, + ``allow_root`` takes precedence. + +* There are new `fuse_session_unmount` and `fuse_session_mount` + functions that should be used in the low-level API. The `fuse_mount` + and `fuse_unmount` functions should be used with the high-level API + only. + +* Neither `fuse_mount` nor `fuse_session_mount` take struct fuse_opts + parameters anymore. Mount options are parsed by `fuse_new` (for the + high-level API) and `fuse_session_new` (for the low-level API) + instead. To print help or version information, use the new + `fuse_mount_help` and `fuse_mount_version` functions. + +* The ``fuse_lowlevel_notify_*`` functions now all take a `struct + fuse_session` parameter instead of a `struct fuse_chan`. + +* The channel interface (``fuse_chan_*`` functions) has been made + private. As a result, the typical initialization sequence of a + low-level file system has changed from :: + + ch = fuse_mount(mountpoint, &args); + se = fuse_lowlevel_new(&args, &lo_oper, sizeof(lo_oper), &lo); + fuse_set_signal_handlers(se); + fuse_session_add_chan(se, ch); + fuse_daemonize(fg); + if (mt) + fuse_session_loop_mt(se); + else + fuse_session_loop(se); + fuse_remove_signal_handlers(se); + fuse_session_remove_chan(ch); + fuse_session_destroy(se); + fuse_unmount(mountpoint, ch); + + to :: + + se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), NULL); + fuse_set_signal_handlers(se); + fuse_session_mount(se, mountpoint); + fuse_daemonize(fg); + if (mt) + fuse_session_loop_mt(se); + else + fuse_session_loop(se); + fuse_remove_signal_handlers(se); + fuse_session_unmount(se); + fuse_lowlevel_destroy(se); + + The typical high-level setup has changed from :: + + ch = fuse_mount(*mountpoint, &args); + fuse = fuse_new(ch, &args, op, op_size, user_data); + se = fuse_get_session(fuse); + fuse_set_signal_handlers(se); + fuse_daemonize(fg); + if (mt) + fuse_loop_mt(fuse); + else + fuse_loop(fuse); + fuse_remove_signal_handlers(se); + fuse_unmount(mountpoint, ch); + fuse_destroy(fuse); + + to :: + + fuse = fuse_new(&args, op, op_size, user_data); + se = fuse_get_session(fuse); + fuse_set_signal_handlers(se); + fuse_mount(fuse, mountpoint); + fuse_daemonize(fg); + if (mt) + fuse_loop_mt(fuse); + else + fuse_loop(fuse); + fuse_remove_signal_handlers(se); + fuse_unmount(fuse); + fuse_destroy(fuse); + + File systems that use `fuse_main` are not affected by this change. + + For integration with custom event loops, the new `fuse_session_fd` + function provides the file descriptor that's used for communication + with the kernel. + +* Added *clone_fd* option. This creates a separate device file + descriptor for each processing thread, which might improve + performance. + +* Added *writeback_cache* option. With kernel 3.14 and newer this + enables write-back caching which can significantly improve + performance. + +* Added *async_dio* option. With kernel 3.13 and newer, this allows + direct I/O to be done asynchronously. + +* The (high- and low-level) `rename` handlers now takes a *flags* + parameter (with values corresponding to the *renameat2* system call + introduced in Linux 3.15). + +* The "ulockmgr_server" has been dropped. + +* There is a new (low-level) `readdirplus` handler, with a + corresponding example in ``examples/fuse_lo-plus.c`` and a new + `fuse_add_direntry_plus` API function. + +* The (high-level) `readdir` handler now takes a *flags* argument. + +* The (high-level) `filler` function passed to `readdir` now takes an + additional *flags* argument. + +* The (high-level) `getdir` handler has been dropped. + +* The *flag_nullpath_ok* and *flag_utime_omit_ok* flags have been + dropped. + +* The (high-level) *utime* handler has been dropped. + +* The `fuse_invalidate` function has been removed. + +* The `fuse_is_lib_option` function has been removed. + +* The *fh_old* member of `struct fuse_file_info` has been dropped. + +* The type of the *writepage* member of `struct fuse_file_info` was + changed from *int* to *unsigned int*. + +* The `struct fuse_file_info` gained a new *poll_events* member. + +* There is a new `fuse_pkgversion` function. + +* The *fuse_off_t* and *fuse_ino_t* changed from *unsigned long* to + *uint64_t*, i.e. they are now 64 bits also on 32-bit systems. + +* The type of the *generation* member of `struct fuse_entry_param*` + changed from *unsigned* to *uint64_t*. + +* The (low-level) `setattr` handler gained a *FUSE_SET_ATTR_CTIME* bit + *for its *to_set* parameter. + +* The `struct fuse_session_ops` data structure has been dropped. + +* The documentation has been clarified and improved in many places. + + +FUSE 2.9.7 (2016-06-20) +======================= + +* Added SELinux support. +* Fixed race-condition when session is terminated right after starting + a FUSE file system. + +FUSE 2.9.6 (2016-04-23) +======================= + +* Tarball now includes documentation. +* Shared-object version has now been bumped correctly. + +FUSE 2.9.5 (2016-01-14) +======================= + +* New maintainer: Nikolaus Rath . Many thanks to + Miklos Szeredi for bringing FUSE to where it is + now! + +* fix warning in mount.c:receive_fd(). Reported by Albert Berger + +* fix possible memory leak. Reported by Jose R. Guzman + +FUSE 2.9.4 (2015-05-22) +======================= + +* fix exec environment for mount and umount. Found by Tavis Ormandy + (CVE-2015-3202). + +* fix fuse_remove_signal_handlers() to properly restore the default + signal handler. Reported by: Chris Johnson + +* highlevel API: fix directory file handle passed to ioctl() method. + Reported by Eric Biggers + +* libfuse: document deadlock avoidance for fuse_notify_inval_entry() + and fuse_notify_delete() + +* fusermount, libfuse: send value as unsigned in "user_id=" and + "group_id=" options. Uids/gids larger than 2147483647 would result + in EINVAL when mounting the filesystem. This also needs a fix in + the kernel. + +* Initialize stat buffer passed to ->getattr() and ->fgetattr() to + zero in all cases. Reported by Daniel Iwan + +* libfuse: Add missing includes. This allows compiling fuse with + musl. Patch by Daniel Thau + + +Older Versions (before 2013-01-01) +================================== + +Please see Git history, e.g. at +https://github.com/libfuse/libfuse/blob/fuse_2_9_3/ChangeLog. diff --git a/GPL2.txt b/GPL2.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/GPL2.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LGPL2.txt b/LGPL2.txt new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/LGPL2.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..184a5b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +The following files may be used under the terms of the GNU Lesser +General Public License, version 2.1 ("LGPL"): + +- All files in the include/ directory. +- All files in the lib/ directory. +- meson.build + +The full terms of the LGPL can be found in the LGPL2.txt file. + + +All other files may be used only under the terms of the GNU General +Public License, version 2 ("GPL"). The full text of this license can +be found in the GPL2.txt file. diff --git a/README.md b/README.md new file mode 100644 index 0000000..eaf1308 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +libfuse +======= + +About +----- + +FUSE (Filesystem in Userspace) is an interface for userspace programs +to export a filesystem to the Linux kernel. The FUSE project consists +of two components: the *fuse* kernel module (maintained in the regular +kernel repositories) and the *libfuse* userspace library (maintained +in this repository). libfuse provides the reference implementation +for communicating with the FUSE kernel module. + +A FUSE file system is typically implemented as a standalone +application that links with libfuse. libfuse provides functions to +mount the file system, unmount it, read requests from the kernel, and +send responses back. libfuse offers two APIs: a "high-level", +synchronous API, and a "low-level" asynchronous API. In both cases, +incoming requests from the kernel are passed to the main program using +callbacks. When using the high-level API, the callbacks may work with +file names and paths instead of inodes, and processing of a request +finishes when the callback function returns. When using the low-level +API, the callbacks must work with inodes and responses must be sent +explicitly using a separate set of API functions. + + +Development Status +------------------ + +libfuse is shipped by all major Linux distributions and has been in +production use across a wide range of systems for many years. However, +at present libfuse does not have any active, regular contributors. The +current maintainer continues to apply pull requests and makes regular +releases, but unfortunately has no capacity to do any development +beyond addressing high-impact issues. When reporting bugs, please +understand that unless you are including a pull request or are +reporting a critical issue, you will probably not get a response. If +you are using libfuse, please consider contributing to the project. + + +Supported Platforms +------------------- + +* Linux (fully) +* BSD (mostly/best-effort) +* For OS-X, please use [OSXFUSE](https://osxfuse.github.io/) + + +Installation +------------ + +You can download libfuse from https://github.com/libfuse/libfuse/releases. To build and +install, you must use [Meson](http://mesonbuild.com/) and +[Ninja](https://ninja-build.org). After downloading the tarball and `.sig` file, verify +it using [signify](https://www.openbsd.org/papers/bsdcan-signify.html): + + signify -V -m fuse-X.Y.Z.tar.gz -p fuse-X.Y.pub + +The `fuse-X.Y.pub` file contains the signing key and needs to be obtained from a +trustworthy source. Each libfuse release contains the signing key for the release after it +in the `signify` directory, so you only need to manually acquire this file once when you +install libfuse for the first time. + +After you have validated the tarball, extract it, create a (temporary) build directory and +run Meson: + + $ tar xzf fuse-X.Y.Z.tar.gz; cd fuse-X.Y.Z + $ mkdir build; cd build + $ meson setup .. + +Normally, the default build options will work fine. If you +nevertheless want to adjust them, you can do so with the +*meson configure* command: + + $ meson configure # list options + $ meson configure -D disable-mtab=true # set an optionq + + $ # ensure all meson options are applied to the final build system + $ meson setup --reconfigure ../ + +To build, test, and install libfuse, you then use Ninja: + + $ ninja + $ sudo python3 -m pytest test/ + $ sudo ninja install + +Running the tests requires the [py.test](http://www.pytest.org/) +Python module. Instead of running the tests as root, the majority of +tests can also be run as a regular user if *util/fusermount3* is made +setuid root first: + + $ sudo chown root:root util/fusermount3 + $ sudo chmod 4755 util/fusermount3 + $ python3 -m pytest test/ + +Security implications +--------------------- + +The *fusermount3* program is installed setuid root. This is done to +allow normal users to mount their own filesystem implementations. + +To limit the harm that malicious users can do this way, *fusermount3* +enforces the following limitations: + + - The user can only mount on a mountpoint for which they have write + permission + + - The mountpoint must not be a sticky directory which isn't owned by + the user (like /tmp usually is) + + - No other user (including root) can access the contents of the + mounted filesystem (though this can be relaxed by allowing the use + of the *allow_other* and *allow_root* mount options in + */etc/fuse.conf*) + + +If you intend to use the *allow_other* mount options, be aware that +FUSE has an unresolved [security +bug](https://github.com/libfuse/libfuse/issues/15): if the +*default_permissions* mount option is not used, the results of the +first permission check performed by the file system for a directory +entry will be re-used for subsequent accesses as long as the inode of +the accessed entry is present in the kernel cache - even if the +permissions have since changed, and even if the subsequent access is +made by a different user. This is of little concern if the filesystem +is accessible only to the mounting user (which has full access to the +filesystem anyway), but becomes a security issue when other users are +allowed to access the filesystem (since they can exploit this to +perform operations on the filesystem that they do not actually have +permissions for). + +This bug needs to be fixed in the Linux kernel and has been known +since 2006 but unfortunately no fix has been applied yet. If you +depend on correct permission handling for FUSE file systems, the only +workaround is to use `default_permissions` (which does not currently +support ACLs), or to completely disable caching of directory entry +attributes. + +Building your own filesystem +------------------------------ + +FUSE comes with several example file systems in the `example` +directory. For example, the *passthrough* examples mirror the contents +of the root directory under the mountpoint. Start from there and adapt +the code! + +The documentation of the API functions and necessary callbacks is +mostly contained in the files `include/fuse.h` (for the high-level +API) and `include/fuse_lowlevel.h` (for the low-level API). An +autogenerated html version of the API is available in the `doc/html` +directory and at http://libfuse.github.io/doxygen. + + +Getting Help +------------ + +If you need help, please ask on the +mailing list (subscribe at +https://lists.sourceforge.net/lists/listinfo/fuse-devel). + +Please report any bugs on the GitHub issue tracker at +https://github.com/libfuse/libfuse/issues. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..19e1366 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives me time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +Please submit information on the vulnerability as a +[private report](https://github.com/libfuse/libfuse/security/advisories/new). + +Please provide the following information in your report: + +- A description of the vulnerability and its impact +- How to reproduce the issue + +This project is maintained by a single volunteer on a reasonable-effort basis. As such, +I ask that you give me 90 days to work on a fix before public exposure. + +Note we are aware of a long-standing security issue when using `allow_others` (see +[#15](https://github.com/libfuse/libfuse/issues/15)). \ No newline at end of file diff --git a/checkpatch.pl b/checkpatch.pl new file mode 100755 index 0000000..9eed368 --- /dev/null +++ b/checkpatch.pl @@ -0,0 +1,7820 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp (the ugly bit) +# (c) 2007,2008, Andy Whitcroft (new conditions, test suite) +# (c) 2008-2010 Andy Whitcroft +# (c) 2010-2018 Joe Perches + +use strict; +use warnings; +use POSIX; +use File::Basename; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); +use Encode qw(decode encode); + +my $P = $0; +my $D = dirname(abs_path($P)); + +my $V = '0.32'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $quiet = 0; +my $verbose = 0; +my %verbose_messages = (); +my %verbose_emitted = (); +my $tree = 1; +my $chk_signoff = 1; +my $chk_fixes_tag = 1; +my $chk_patch = 1; +my $tst_only; +my $emacs = 0; +my $terse = 0; +my $showfile = 0; +my $file = 0; +my $git = 0; +my %git_commits = (); +my $check = 0; +my $check_orig = 0; +my $summary = 1; +my $mailback = 0; +my $summary_file = 0; +my $show_types = 0; +my $list_types = 0; +my $fix = 0; +my $fix_inplace = 0; +my $root; +my $gitroot = $ENV{'GIT_DIR'}; +$gitroot = ".git" if !defined($gitroot); +my %debug; +my %camelcase = (); +my %use_type = (); +my @use = (); +my %ignore_type = (); +my @ignore = (); +my $help = 0; +my $configuration_file = ".checkpatch.conf"; +my $max_line_length = 100; +my $ignore_perl_version = 0; +my $minimum_perl_version = 5.10.0; +my $min_conf_desc_length = 4; +my $spelling_file = "$D/spelling.txt"; +my $codespell = 0; +my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $user_codespellfile = ""; +my $conststructsfile = "$D/const_structs.checkpatch"; +my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; +my $typedefsfile; +my $color = "auto"; +my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE +# git output parsing needs US English output, so first set backtick child process LANGUAGE +my $git_command ='export LANGUAGE=en_US.UTF-8; git'; +my $tabsize = 8; +my ${CONFIG_} = "CONFIG_"; + +my %maybe_linker_symbol; # for externs in c exceptions, when seen in *vmlinux.lds.h + +sub help { + my ($exitcode) = @_; + + print << "EOM"; +Usage: $P [OPTION]... [FILE]... +Version: $V + +Options: + -q, --quiet quiet + -v, --verbose verbose mode + --no-tree run without a kernel tree + --no-signoff do not check for 'Signed-off-by' line + --no-fixes-tag do not check for 'Fixes:' tag + --patch treat FILE as patchfile (default) + --emacs emacs compile window format + --terse one line per report + --showfile emit diffed file position, not input file position + -g, --git treat FILE as a single commit or git revision range + single git commit with: + + ^ + ~n + multiple git commits with: + .. + ... + - + git merges are ignored + -f, --file treat FILE as regular source file + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --max-line-length=n set the maximum line length, (default $max_line_length) + if exceeded, warn on patches + requires --strict for use with --file + --min-conf-desc-length=n set the min description length, if shorter, warn + --tab-size=n set the number of spaces for tab (default $tabsize) + --root=PATH PATH to the kernel tree root + --no-summary suppress the per-file summary + --mailback only produce a report in case of warnings/errors + --summary-file include the filename in summary + --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of + 'values', 'possible', 'type', and 'attr' (default + is all off) + --test-only=WORD report only warnings/errors containing WORD + literally + --fix EXPERIMENTAL - may create horrible results + If correctable single-line errors exist, create + ".EXPERIMENTAL-checkpatch-fixes" + with potential errors corrected to the preferred + checkpatch style + --fix-inplace EXPERIMENTAL - may create horrible results + Is the same as --fix, but overwrites the input + file. It's your fault if there's no backup or git + --ignore-perl-version override checking of perl version. expect + runtime errors. + --codespell Use the codespell dictionary for spelling/typos + (default:$codespellfile) + --codespellfile Use this codespell dictionary + --typedefsfile Read additional types from this file + --color[=WHEN] Use colors 'always', 'never', or only when output + is a terminal ('auto'). Default is 'auto'. + --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default + ${CONFIG_}) + -h, --help, --version display this help and exit + +When FILE is - read standard input. +EOM + + exit($exitcode); +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub list_types { + my ($exitcode) = @_; + + my $count = 0; + + local $/ = undef; + + open(my $script, '<', abs_path($P)) or + die "$P: Can't read '$P' $!\n"; + + my $text = <$script>; + close($script); + + my %types = (); + # Also catch when type or level is passed through a variable + while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + if (defined($1)) { + if (exists($types{$2})) { + $types{$2} .= ",$1" if ($types{$2} ne $1); + } else { + $types{$2} = $1; + } + } else { + $types{$2} = "UNDETERMINED"; + } + } + + print("#\tMessage type\n\n"); + if ($color) { + print(" ( Color coding: "); + print(RED . "ERROR" . RESET); + print(" | "); + print(YELLOW . "WARNING" . RESET); + print(" | "); + print(GREEN . "CHECK" . RESET); + print(" | "); + print("Multiple levels / Undetermined"); + print(" )\n\n"); + } + + foreach my $type (sort keys %types) { + my $orig_type = $type; + if ($color) { + my $level = $types{$type}; + if ($level eq "ERROR") { + $type = RED . $type . RESET; + } elsif ($level eq "WARN") { + $type = YELLOW . $type . RESET; + } elsif ($level eq "CHK") { + $type = GREEN . $type . RESET; + } + } + print(++$count . "\t" . $type . "\n"); + if ($verbose && exists($verbose_messages{$orig_type})) { + my $message = $verbose_messages{$orig_type}; + $message =~ s/\n/\n\t/g; + print("\t" . $message . "\n\n"); + } + } + + exit($exitcode); +} + +my $conf = which_conf($configuration_file); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable $configuration_file file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +sub load_docs { + open(my $docs, '<', "$docsfile") + or warn "$P: Can't read the documentation file $docsfile $!\n"; + + my $type = ''; + my $desc = ''; + my $in_desc = 0; + + while (<$docs>) { + chomp; + my $line = $_; + $line =~ s/\s+$//; + + if ($line =~ /^\s*\*\*(.+)\*\*$/) { + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + $type = $1; + $desc = ''; + $in_desc = 1; + } elsif ($in_desc) { + if ($line =~ /^(?:\s{4,}|$)/) { + $line =~ s/^\s{4}//; + $desc .= $line; + $desc .= "\n"; + } else { + $verbose_messages{$type} = trim($desc); + $type = ''; + $desc = ''; + $in_desc = 0; + } + } + } + + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + close($docs); +} + +# Perl's Getopt::Long allows options to take optional arguments after a space. +# Prevent --color by itself from consuming other arguments +foreach (@ARGV) { + if ($_ eq "--color" || $_ eq "-color") { + $_ = "--color=$color"; + } +} + +GetOptions( + 'q|quiet+' => \$quiet, + 'v|verbose!' => \$verbose, + 'tree!' => \$tree, + 'signoff!' => \$chk_signoff, + 'fixes-tag!' => \$chk_fixes_tag, + 'patch!' => \$chk_patch, + 'emacs!' => \$emacs, + 'terse!' => \$terse, + 'showfile!' => \$showfile, + 'f|file!' => \$file, + 'g|git!' => \$git, + 'subjective!' => \$check, + 'strict!' => \$check, + 'ignore=s' => \@ignore, + 'types=s' => \@use, + 'show-types!' => \$show_types, + 'list-types!' => \$list_types, + 'max-line-length=i' => \$max_line_length, + 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'tab-size=i' => \$tabsize, + 'root=s' => \$root, + 'summary!' => \$summary, + 'mailback!' => \$mailback, + 'summary-file!' => \$summary_file, + 'fix!' => \$fix, + 'fix-inplace!' => \$fix_inplace, + 'ignore-perl-version!' => \$ignore_perl_version, + 'debug=s' => \%debug, + 'test-only=s' => \$tst_only, + 'codespell!' => \$codespell, + 'codespellfile=s' => \$user_codespellfile, + 'typedefsfile=s' => \$typedefsfile, + 'color=s' => \$color, + 'no-color' => \$color, #keep old behaviors of -nocolor + 'nocolor' => \$color, #keep old behaviors of -nocolor + 'kconfig-prefix=s' => \${CONFIG_}, + 'h|help' => \$help, + 'version' => \$help +) or $help = 2; + +if ($user_codespellfile) { + # Use the user provided codespell file unconditionally + $codespellfile = $user_codespellfile; +} elsif (!(-f $codespellfile)) { + # If /usr/share/codespell/dictionary.txt is not present, try to find it + # under codespell's install directory: /data/dictionary.txt + if (($codespell || $help) && which("python3") ne "") { + my $python_codespell_dict = << "EOF"; + +import os.path as op +import codespell_lib +codespell_dir = op.dirname(codespell_lib.__file__) +codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt') +print(codespell_file, end='') +EOF + + my $codespell_dict = `python3 -c "$python_codespell_dict" 2> /dev/null`; + $codespellfile = $codespell_dict if (-f $codespell_dict); + } +} + +# $help is 1 if either -h, --help or --version is passed as option - exitcode: 0 +# $help is 2 if invalid option is passed - exitcode: 1 +help($help - 1) if ($help); + +die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); +die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "$P: Invalid color mode: $color\n"; +} + +load_docs() if ($verbose); +list_types(0) if ($list_types); + +$fix = 1 if ($fix_inplace); +$check_orig = $check; + +my $exit = 0; + +my $perl_version_ok = 1; +if ($^V && $^V lt $minimum_perl_version) { + $perl_version_ok = 0; + printf "$P: requires at least perl version %vd\n", $minimum_perl_version; + exit(1) if (!$ignore_perl_version); +} + +#if no filenames are given, push '-' to read patch from stdin +if ($#ARGV < 0) { + push(@ARGV, '-'); +} + +# skip TAB size 1 to avoid additional checks on $tabsize - 1 +die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); + +sub hash_save_array_words { + my ($hashRef, $arrayRef) = @_; + + my @array = split(/,/, join(',', @$arrayRef)); + foreach my $word (@array) { + $word =~ s/\s*\n?$//g; + $word =~ s/^\s*//g; + $word =~ s/\s+/ /g; + $word =~ tr/[a-z]/[A-Z]/; + + next if ($word =~ m/^\s*#/); + next if ($word =~ m/^\s*$/); + + $hashRef->{$word}++; + } +} + +sub hash_show_words { + my ($hashRef, $prefix) = @_; + + if (keys %$hashRef) { + print "\nNOTE: $prefix message types:"; + foreach my $word (sort keys %$hashRef) { + print " $word"; + } + print "\n"; + } +} + +hash_save_array_words(\%ignore_type, \@ignore); +hash_save_array_words(\%use_type, \@use); + +my $dbg_values = 0; +my $dbg_possible = 0; +my $dbg_type = 0; +my $dbg_attr = 0; +for my $key (keys %debug) { + ## no critic + eval "\${dbg_$key} = '$debug{$key}';"; + die "$@" if ($@); +} + +my $rpt_cleaners = 0; + +if ($terse) { + $emacs = 1; + $quiet++; +} + +if ($tree) { + if (defined $root) { + if (!top_of_kernel_tree($root)) { + die "$P: $root: --root does not point at a valid tree\n"; + } + } else { + if (top_of_kernel_tree('.')) { + $root = '.'; + } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && + top_of_kernel_tree($1)) { + $root = $1; + } + } + + if (!defined $root) { + print "Must be run from the top-level dir. of a kernel tree\n"; + exit(2); + } +} + +my $emitted_corrupt = 0; + +our $Ident = qr{ + [A-Za-z_][A-Za-z\d_]* + (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* + }x; +our $Storage = qr{extern|static|asmlinkage}; +our $Sparse = qr{ + __user| + __kernel| + __force| + __iomem| + __must_check| + __kprobes| + __ref| + __refconst| + __refdata| + __rcu| + __private + }x; +our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; +our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; +our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; +our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; +our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; + +# Notes to $Attribute: +# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check +our $Attribute = qr{ + const| + volatile| + __percpu| + __nocast| + __safe| + __bitwise| + __packed__| + __packed2__| + __naked| + __maybe_unused| + __always_unused| + __noreturn| + __used| + __cold| + __pure| + __noclone| + __deprecated| + __read_mostly| + __ro_after_init| + __kprobes| + $InitAttribute| + __aligned\s*\(.*\)| + ____cacheline_aligned| + ____cacheline_aligned_in_smp| + ____cacheline_internodealigned_in_smp| + __weak| + __alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) + }x; +our $Modifier; +our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; +our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; +our $Lval = qr{$Ident(?:$Member)*}; + +our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; +our $Binary = qr{(?i)0b[01]+$Int_type?}; +our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; +our $Int = qr{[0-9]+$Int_type?}; +our $Octal = qr{0[0-7]+$Int_type?}; +our $String = qr{(?:\b[Lu])?"[X\t]*"}; +our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; +our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; +our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; +our $Float = qr{$Float_hex|$Float_dec|$Float_int}; +our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; +our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; +our $Compare = qr{<=|>=|==|!=|<|(?}; +our $Arithmetic = qr{\+|-|\*|\/|%}; +our $Operators = qr{ + <=|>=|==|!=| + =>|->|<<|>>|<|>|!|~| + &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic + }x; + +our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; + +our $BasicType; +our $NonptrType; +our $NonptrTypeMisordered; +our $NonptrTypeWithAttr; +our $Type; +our $TypeMisordered; +our $Declare; +our $DeclareMisordered; + +our $NON_ASCII_UTF8 = qr{ + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +}x; + +our $UTF8 = qr{ + [\x09\x0A\x0D\x20-\x7E] # ASCII + | $NON_ASCII_UTF8 +}x; + +our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; +our $typeOtherOSTypedefs = qr{(?x: + u_(?:char|short|int|long) | # bsd + u(?:nchar|short|int|long) # sysv +)}; +our $typeKernelTypedefs = qr{(?x: + (?:__)?(?:u|s|be|le)(?:8|16|32|64)| + atomic_t +)}; +our $typeStdioTypedefs = qr{(?x: + FILE +)}; +our $typeTypedefs = qr{(?x: + $typeC99Typedefs\b| + $typeOtherOSTypedefs\b| + $typeKernelTypedefs\b| + $typeStdioTypedefs\b +)}; + +our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; + +our $logFunctions = qr{(?x: + printk(?:_ratelimited|_once|_deferred_once|_deferred|)| + (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| + TP_printk| + WARN(?:_RATELIMIT|_ONCE|)| + panic| + MODULE_[A-Z_]+| + seq_vprintf|seq_printf|seq_puts +)}; + +our $allocFunctions = qr{(?x: + (?:(?:devm_)? + (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | + kstrdup(?:_const)? | + kmemdup(?:_nul)?) | + (?:\w+)?alloc_skb(?:_ip_align)? | + # dev_alloc_skb/netdev_alloc_skb, et al + dma_alloc_coherent +)}; + +our $signature_tags = qr{(?xi: + Signed-off-by:| + Co-developed-by:| + Acked-by:| + Tested-by:| + Reviewed-by:| + Reported-by:| + Suggested-by:| + To:| + Cc: +)}; + +our @link_tags = qw(Link Closes); + +#Create a search and print patterns for all these strings to be used directly below +our $link_tags_search = ""; +our $link_tags_print = ""; +foreach my $entry (@link_tags) { + if ($link_tags_search ne "") { + $link_tags_search .= '|'; + $link_tags_print .= ' or '; + } + $entry .= ':'; + $link_tags_search .= $entry; + $link_tags_print .= "'$entry'"; +} +$link_tags_search = "(?:${link_tags_search})"; + +our $tracing_logging_tags = qr{(?xi: + [=-]*> | + <[=-]* | + \[ | + \] | + start | + called | + entered | + entry | + enter | + in | + inside | + here | + begin | + exit | + end | + done | + leave | + completed | + out | + return | + [\.\!:\s]* +)}; + +sub edit_distance_min { + my (@arr) = @_; + my $len = scalar @arr; + if ((scalar @arr) < 1) { + # if underflow, return + return; + } + my $min = $arr[0]; + for my $i (0 .. ($len-1)) { + if ($arr[$i] < $min) { + $min = $arr[$i]; + } + } + return $min; +} + +sub get_edit_distance { + my ($str1, $str2) = @_; + $str1 = lc($str1); + $str2 = lc($str2); + $str1 =~ s/-//g; + $str2 =~ s/-//g; + my $len1 = length($str1); + my $len2 = length($str2); + # two dimensional array storing minimum edit distance + my @distance; + for my $i (0 .. $len1) { + for my $j (0 .. $len2) { + if ($i == 0) { + $distance[$i][$j] = $j; + } elsif ($j == 0) { + $distance[$i][$j] = $i; + } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { + $distance[$i][$j] = $distance[$i - 1][$j - 1]; + } else { + my $dist1 = $distance[$i][$j - 1]; #insert distance + my $dist2 = $distance[$i - 1][$j]; # remove + my $dist3 = $distance[$i - 1][$j - 1]; #replace + $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); + } + } + } + return $distance[$len1][$len2]; +} + +sub find_standard_signature { + my ($sign_off) = @_; + my @standard_signature_tags = ( + 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', + 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' + ); + foreach my $signature (@standard_signature_tags) { + return $signature if (get_edit_distance($sign_off, $signature) <= 2); + } + + return ""; +} + +our $obsolete_archives = qr{(?xi: + \Qfreedesktop.org/archives/dri-devel\E | + \Qlists.infradead.org\E | + \Qlkml.org\E | + \Qmail-archive.com\E | + \Qmailman.alsa-project.org/pipermail\E | + \Qmarc.info\E | + \Qozlabs.org/pipermail\E | + \Qspinics.net\E +)}; + +our @typeListMisordered = ( + qr{char\s+(?:un)?signed}, + qr{int\s+(?:(?:un)?signed\s+)?short\s}, + qr{int\s+short(?:\s+(?:un)?signed)}, + qr{short\s+int(?:\s+(?:un)?signed)}, + qr{(?:un)?signed\s+int\s+short}, + qr{short\s+(?:un)?signed}, + qr{long\s+int\s+(?:un)?signed}, + qr{int\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed\s+int}, + qr{int\s+(?:un)?signed\s+long}, + qr{int\s+(?:un)?signed}, + qr{int\s+long\s+long\s+(?:un)?signed}, + qr{long\s+long\s+int\s+(?:un)?signed}, + qr{long\s+long\s+(?:un)?signed\s+int}, + qr{long\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed}, +); + +our @typeList = ( + qr{void}, + qr{(?:(?:un)?signed\s+)?char}, + qr{(?:(?:un)?signed\s+)?short\s+int}, + qr{(?:(?:un)?signed\s+)?short}, + qr{(?:(?:un)?signed\s+)?int}, + qr{(?:(?:un)?signed\s+)?long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long}, + qr{(?:(?:un)?signed\s+)?long}, + qr{(?:un)?signed}, + qr{float}, + qr{double}, + qr{bool}, + qr{struct\s+$Ident}, + qr{union\s+$Ident}, + qr{enum\s+$Ident}, + qr{${Ident}_t}, + qr{${Ident}_handler}, + qr{${Ident}_handler_fn}, + @typeListMisordered, +); + +our $C90_int_types = qr{(?x: + long\s+long\s+int\s+(?:un)?signed| + long\s+long\s+(?:un)?signed\s+int| + long\s+long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+long\s+int| + (?:(?:un)?signed\s+)?long\s+long| + int\s+long\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long\s+long| + + long\s+int\s+(?:un)?signed| + long\s+(?:un)?signed\s+int| + long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+int| + (?:(?:un)?signed\s+)?long| + int\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long| + + int\s+(?:un)?signed| + (?:(?:un)?signed\s+)?int +)}; + +our @typeListFile = (); +our @typeListWithAttr = ( + @typeList, + qr{struct\s+$InitAttribute\s+$Ident}, + qr{union\s+$InitAttribute\s+$Ident}, +); + +our @modifierList = ( + qr{fastcall}, +); +our @modifierListFile = (); + +our @mode_permission_funcs = ( + ["module_param", 3], + ["module_param_(?:array|named|string)", 4], + ["module_param_array_named", 5], + ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], + ["proc_create(?:_data|)", 2], + ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], + ["IIO_DEV_ATTR_[A-Z_]+", 1], + ["SENSOR_(?:DEVICE_|)ATTR_2", 2], + ["SENSOR_TEMPLATE(?:_2|)", 3], + ["__ATTR", 2], +); + +my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; + +#Create a search pattern for all these functions to speed up a loop below +our $mode_perms_search = ""; +foreach my $entry (@mode_permission_funcs) { + $mode_perms_search .= '|' if ($mode_perms_search ne ""); + $mode_perms_search .= $entry->[0]; +} +$mode_perms_search = "(?:${mode_perms_search})"; + +our %deprecated_apis = ( + "synchronize_rcu_bh" => "synchronize_rcu", + "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", + "call_rcu_bh" => "call_rcu", + "rcu_barrier_bh" => "rcu_barrier", + "synchronize_sched" => "synchronize_rcu", + "synchronize_sched_expedited" => "synchronize_rcu_expedited", + "call_rcu_sched" => "call_rcu", + "rcu_barrier_sched" => "rcu_barrier", + "get_state_synchronize_sched" => "get_state_synchronize_rcu", + "cond_synchronize_sched" => "cond_synchronize_rcu", + "kmap" => "kmap_local_page", + "kunmap" => "kunmap_local", + "kmap_atomic" => "kmap_local_page", + "kunmap_atomic" => "kunmap_local", +); + +#Create a search pattern for all these strings to speed up a loop below +our $deprecated_apis_search = ""; +foreach my $entry (keys %deprecated_apis) { + $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); + $deprecated_apis_search .= $entry; +} +$deprecated_apis_search = "(?:${deprecated_apis_search})"; + +our $mode_perms_world_writable = qr{ + S_IWUGO | + S_IWOTH | + S_IRWXUGO | + S_IALLUGO | + 0[0-7][0-7][2367] +}x; + +our %mode_permission_string_types = ( + "S_IRWXU" => 0700, + "S_IRUSR" => 0400, + "S_IWUSR" => 0200, + "S_IXUSR" => 0100, + "S_IRWXG" => 0070, + "S_IRGRP" => 0040, + "S_IWGRP" => 0020, + "S_IXGRP" => 0010, + "S_IRWXO" => 0007, + "S_IROTH" => 0004, + "S_IWOTH" => 0002, + "S_IXOTH" => 0001, + "S_IRWXUGO" => 0777, + "S_IRUGO" => 0444, + "S_IWUGO" => 0222, + "S_IXUGO" => 0111, +); + +#Create a search pattern for all these strings to speed up a loop below +our $mode_perms_string_search = ""; +foreach my $entry (keys %mode_permission_string_types) { + $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); + $mode_perms_string_search .= $entry; +} +our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; +our $multi_mode_perms_string_search = qr{ + ${single_mode_perms_string_search} + (?:\s*\|\s*${single_mode_perms_string_search})* +}x; + +sub perms_to_octal { + my ($string) = @_; + + return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); + + my $val = ""; + my $oval = ""; + my $to = 0; + my $curpos = 0; + my $lastpos = 0; + while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { + $curpos = pos($string); + my $match = $2; + my $omatch = $1; + last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); + $lastpos = $curpos; + $to |= $mode_permission_string_types{$match}; + $val .= '\s*\|\s*' if ($val ne ""); + $val .= $match; + $oval .= $omatch; + } + $oval =~ s/^\s*\|\s*//; + $oval =~ s/\s*\|\s*$//; + return sprintf("%04o", $to); +} + +our $allowed_asm_includes = qr{(?x: + irq| + memory| + time| + reboot +)}; +# memory.h: ARM has a custom one + +# Load common spelling mistakes and build regular expression list. +my $misspellings; +my %spelling_fix; + +if (open(my $spelling, '<', $spelling_file)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my ($suspect, $fix) = split(/\|\|/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); +} else { + warn "No typos will be found - file '$spelling_file': $!\n"; +} + +if ($codespell) { + if (open(my $spelling, '<', $codespellfile)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + next if ($line =~ m/, disabled/i); + + $line =~ s/,.*$//; + + my ($suspect, $fix) = split(/->/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); + } else { + warn "No codespell typos will be found - file '$codespellfile': $!\n"; + } +} + +$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; + +sub read_words { + my ($wordsRef, $file) = @_; + + if (open(my $words, '<', $file)) { + while (<$words>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + if ($line =~ /\s/) { + print("$file: '$line' invalid - ignored\n"); + next; + } + + $$wordsRef .= '|' if (defined $$wordsRef); + $$wordsRef .= $line; + } + close($file); + return 1; + } + + return 0; +} + +my $const_structs; +if (show_type("CONST_STRUCT")) { + read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; +} + +if (defined($typedefsfile)) { + my $typeOtherTypedefs; + read_words(\$typeOtherTypedefs, $typedefsfile) + or warn "No additional types will be considered - file '$typedefsfile': $!\n"; + $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); +} + +sub build_types { + my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; + my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; + my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; + my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; + $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; + $BasicType = qr{ + (?:$typeTypedefs\b)| + (?:${all}\b) + }x; + $NonptrType = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${all}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeMisordered = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:${Misordered}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeWithAttr = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${allWithAttr}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $Type = qr{ + $NonptrType + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $TypeMisordered = qr{ + $NonptrTypeMisordered + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; + $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; +} +build_types(); + +our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; + +# Using $balanced_parens, $LvalOrFunc, or $FuncArg +# requires at least perl version v5.10.0 +# Any use must be runtime checked with $^V + +our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; +our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; +our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; + +our $declaration_macros = qr{(?x: + (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| + (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| + (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\(| + (?:$Storage\s+)?(?:XA_STATE|XA_STATE_ORDER)\s*\( +)}; + +our %allow_repeated_words = ( + add => '', + added => '', + bad => '', + be => '', +); + +sub deparenthesize { + my ($string) = @_; + return "" if (!defined($string)); + + while ($string =~ /^\s*\(.*\)\s*$/) { + $string =~ s@^\s*\(\s*@@; + $string =~ s@\s*\)\s*$@@; + } + + $string =~ s@\s+@ @g; + + return $string; +} + +sub seed_camelcase_file { + my ($file) = @_; + + return if (!(-f $file)); + + local $/; + + open(my $include_file, '<', "$file") + or warn "$P: Can't read '$file' $!\n"; + my $text = <$include_file>; + close($include_file); + + my @lines = split('\n', $text); + + foreach my $line (@lines) { + next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); + if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { + $camelcase{$1} = 1; + } + } +} + +our %maintained_status = (); + +sub is_maintained_obsolete { + my ($filename) = @_; + + return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); + + if (!exists($maintained_status{$filename})) { + $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + } + + return $maintained_status{$filename} =~ /obsolete/i; +} + +sub is_SPDX_License_valid { + my ($license) = @_; + + return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); + + my $root_path = abs_path($root); + my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; + return 0 if ($status ne ""); + return 1; +} + +my $camelcase_seeded = 0; +sub seed_camelcase_includes { + return if ($camelcase_seeded); + + my $files; + my $camelcase_cache = ""; + my @include_files = (); + + $camelcase_seeded = 1; + + if (-e "$gitroot") { + my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; + chomp $git_last_include_commit; + $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } else { + my $last_mod_date = 0; + $files = `find $root/include -name "*.h"`; + @include_files = split('\n', $files); + foreach my $file (@include_files) { + my $date = POSIX::strftime("%Y%m%d%H%M", + localtime((stat $file)[9])); + $last_mod_date = $date if ($last_mod_date < $date); + } + $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; + } + + if ($camelcase_cache ne "" && -f $camelcase_cache) { + open(my $camelcase_file, '<', "$camelcase_cache") + or warn "$P: Can't read '$camelcase_cache' $!\n"; + while (<$camelcase_file>) { + chomp; + $camelcase{$_} = 1; + } + close($camelcase_file); + + return; + } + + if (-e "$gitroot") { + $files = `${git_command} ls-files "include/*.h"`; + @include_files = split('\n', $files); + } + + foreach my $file (@include_files) { + seed_camelcase_file($file); + } + + if ($camelcase_cache ne "") { + unlink glob ".checkpatch-camelcase.*"; + open(my $camelcase_file, '>', "$camelcase_cache") + or warn "$P: Can't write '$camelcase_cache' $!\n"; + foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { + print $camelcase_file ("$_\n"); + } + close($camelcase_file); + } +} + +sub git_is_single_file { + my ($filename) = @_; + + return 0 if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} ls-files -- $filename 2>/dev/null`; + my $count = $output =~ tr/\n//; + return $count eq 1 && $output =~ m{^${filename}$}; +} + +sub git_commit_info { + my ($commit, $id, $desc) = @_; + + return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; + $output =~ s/^\s*//gm; + my @lines = split("\n", $output); + + return ($id, $desc) if ($#lines < 0); + + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { +# Maybe one day convert this block of bash into something that returns +# all matching commit ids, but it's very slow... +# +# echo "checking commits $1..." +# git rev-list --remotes | grep -i "^$1" | +# while read line ; do +# git log --format='%H %s' -1 $line | +# echo "commit $(cut -c 1-12,41-)" +# done + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || + $lines[0] =~ /^fatal: bad object $commit/) { + $id = undef; + } else { + $id = substr($lines[0], 0, 12); + $desc = substr($lines[0], 41); + } + + return ($id, $desc); +} + +$chk_signoff = 0 if ($file); +$chk_fixes_tag = 0 if ($file); + +my @rawlines = (); +my @lines = (); +my @fixed = (); +my @fixed_inserted = (); +my @fixed_deleted = (); +my $fixlinenr = -1; + +# If input is git commits, extract all commits from the commit expressions. +# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. +die "$P: No git repository found\n" if ($git && !-e "$gitroot"); + +if ($git) { + my @commits = (); + foreach my $commit_expr (@ARGV) { + my $git_range; + if ($commit_expr =~ m/^(.*)-(\d+)$/) { + $git_range = "-$2 $1"; + } elsif ($commit_expr =~ m/\.\./) { + $git_range = "$commit_expr"; + } else { + $git_range = "-1 $commit_expr"; + } + my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + foreach my $line (split(/\n/, $lines)) { + $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; + next if (!defined($1) || !defined($2)); + my $sha1 = $1; + my $subject = $2; + unshift(@commits, $sha1); + $git_commits{$sha1} = $subject; + } + } + die "$P: no git commits after extraction!\n" if (@commits == 0); + @ARGV = @commits; +} + +my $vname; +$allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; +for my $filename (@ARGV) { + my $FILE; + my $is_git_file = git_is_single_file($filename); + my $oldfile = $file; + $file = 1 if ($is_git_file); + if ($git) { + open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || + die "$P: $filename: git format-patch failed - $!\n"; + } elsif ($file) { + open($FILE, '-|', "diff -u /dev/null $filename") || + die "$P: $filename: diff failed - $!\n"; + } elsif ($filename eq '-') { + open($FILE, '<&STDIN'); + } else { + open($FILE, '<', "$filename") || + die "$P: $filename: open failed - $!\n"; + } + if ($filename eq '-') { + $vname = 'Your patch'; + } elsif ($git) { + $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; + } else { + $vname = $filename; + } + while (<$FILE>) { + chomp; + push(@rawlines, $_); + $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); + } + close($FILE); + + if ($#ARGV > 0 && $quiet == 0) { + print '-' x length($vname) . "\n"; + print "$vname\n"; + print '-' x length($vname) . "\n"; + } + + if (!process($filename)) { + $exit = 1; + } + @rawlines = (); + @lines = (); + @fixed = (); + @fixed_inserted = (); + @fixed_deleted = (); + $fixlinenr = -1; + @modifierListFile = (); + @typeListFile = (); + build_types(); + $file = $oldfile if ($is_git_file); +} + +if (!$quiet) { + hash_show_words(\%use_type, "Used"); + hash_show_words(\%ignore_type, "Ignored"); + + if (!$perl_version_ok) { + print << "EOM" + +NOTE: perl $^V is not modern enough to detect all possible issues. + An upgrade to at least perl $minimum_perl_version is suggested. +EOM + } + if ($exit) { + print << "EOM" + +NOTE: If any of the errors are false positives, please report + them to the maintainer, see CHECKPATCH in MAINTAINERS. +EOM + } +} + +exit($exit); + +sub top_of_kernel_tree { + my ($root) = @_; + + my @tree_check = ( + "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts", + ); + + foreach my $check (@tree_check) { + if (! -e $root . '/' . $check) { + return 0; + } + } + return 1; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $quoted = ""; + my $name_comment = ""; + my $address = ""; + my $comment = ""; + + if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { + $name = $1; + $address = $2; + $comment = $3 if defined $3; + } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + $formatted_email =~ s/\Q$address\E.*$//; + $name = $formatted_email; + $name = trim($name); + $name =~ s/^\"|\"$//g; + # If there's a name left after stripping spaces and + # leading quotes, and the address doesn't have both + # leading and trailing angle brackets, the address + # is invalid. ie: + # "joe smith joe@smith.com" bad + # "joe smith ]+>$/) { + $name = ""; + $address = ""; + $comment = ""; + } + } + + # Extract comments from names excluding quoted parts + # "John D. (Doe)" - Do not extract + if ($name =~ s/\"(.+)\"//) { + $quoted = $1; + } + while ($name =~ s/\s*($balanced_parens)\s*/ /) { + $name_comment .= trim($1); + } + $name =~ s/^[ \"]+|[ \"]+$//g; + $name = trim("$quoted $name"); + + $address = trim($address); + $address =~ s/^\<|\>$//g; + $comment = trim($comment); + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?"; + } + $formatted_email .= "$comment"; + return $formatted_email; +} + +sub reformat_email { + my ($email) = @_; + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + return format_email($email_name, $name_comment, $email_address, $comment); +} + +sub same_email_addresses { + my ($email1, $email2) = @_; + + my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); + my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); + + return $email1_name eq $email2_name && + $email1_address eq $email2_address && + $name1_comment eq $name2_comment && + $comment1 eq $comment2; +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) = @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub expand_tabs { + my ($str) = @_; + + my $res = ''; + my $n = 0; + for my $c (split(//, $str)) { + if ($c eq "\t") { + $res .= ' '; + $n++; + for (; ($n % $tabsize) != 0; $n++) { + $res .= ' '; + } + next; + } + $res .= $c; + $n++; + } + + return $res; +} +sub copy_spacing { + (my $res = shift) =~ tr/\t/ /c; + return $res; +} + +sub line_stats { + my ($line) = @_; + + # Drop the diff line leader and expand tabs + $line =~ s/^.//; + $line = expand_tabs($line); + + # Pick the indent from the front of the line. + my ($white) = ($line =~ /^(\s*)/); + + return (length($line), length($white)); +} + +my $sanitise_quote = ''; + +sub sanitise_line_reset { + my ($in_comment) = @_; + + if ($in_comment) { + $sanitise_quote = '*/'; + } else { + $sanitise_quote = ''; + } +} +sub sanitise_line { + my ($line) = @_; + + my $res = ''; + my $l = ''; + + my $qlen = 0; + my $off = 0; + my $c; + + # Always copy over the diff marker. + $res = substr($line, 0, 1); + + for ($off = 1; $off < length($line); $off++) { + $c = substr($line, $off, 1); + + # Comments we are whacking completely including the begin + # and end, all to $;. + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { + $sanitise_quote = '*/'; + + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { + $sanitise_quote = ''; + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { + $sanitise_quote = '//'; + + substr($res, $off, 2, $sanitise_quote); + $off++; + next; + } + + # A \ in a string means ignore the next character. + if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && + $c eq "\\") { + substr($res, $off, 2, 'XX'); + $off++; + next; + } + # Regular quotes. + if ($c eq "'" || $c eq '"') { + if ($sanitise_quote eq '') { + $sanitise_quote = $c; + + substr($res, $off, 1, $c); + next; + } elsif ($sanitise_quote eq $c) { + $sanitise_quote = ''; + } + } + + #print "c<$c> SQ<$sanitise_quote>\n"; + if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { + substr($res, $off, 1, 'X'); + } else { + substr($res, $off, 1, $c); + } + } + + if ($sanitise_quote eq '//') { + $sanitise_quote = ''; + } + + # The pathname on a #include may be surrounded by '<' and '>'. + if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { + my $clean = 'X' x length($1); + $res =~ s@\<.*\>@<$clean>@; + + # The whole of a #error is a string. + } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { + my $clean = 'X' x length($1); + $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; + } + + if ($allow_c99_comments && $res =~ m@(//.*$)@) { + my $match = $1; + $res =~ s/\Q$match\E/"$;" x length($match)/e; + } + + return $res; +} + +sub get_quoted_string { + my ($line, $rawline) = @_; + + return "" if (!defined($line) || !defined($rawline)); + return "" if ($line !~ m/($String)/g); + return substr($rawline, $-[0], $+[0] - $-[0]); +} + +sub ctx_statement_block { + my ($linenr, $remain, $off) = @_; + my $line = $linenr - 1; + my $blk = ''; + my $soff = $off; + my $coff = $off - 1; + my $coff_set = 0; + + my $loff = 0; + + my $type = ''; + my $level = 0; + my @stack = (); + my $p; + my $c; + my $len = 0; + + my $remainder; + while (1) { + @stack = (['', 0]) if ($#stack == -1); + + #warn "CSB: blk<$blk> remain<$remain>\n"; + # If we are about to drop off the end, pull in more + # context. + if ($off >= $len) { + for (; $remain > 0; $line++) { + last if (!defined $lines[$line]); + next if ($lines[$line] =~ /^-/); + $remain--; + $loff = $len; + $blk .= $lines[$line] . "\n"; + $len = length($blk); + $line++; + last; + } + # Bail if there is no further context. + #warn "CSB: blk<$blk> off<$off> len<$len>\n"; + if ($off >= $len) { + last; + } + if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { + $level++; + $type = '#'; + } + } + $p = $c; + $c = substr($blk, $off, 1); + $remainder = substr($blk, $off); + + #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; + + # Handle nested #if/#else. + if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, [ $type, $level ]); + } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { + ($type, $level) = @{$stack[$#stack - 1]}; + } elsif ($remainder =~ /^#\s*endif\b/) { + ($type, $level) = @{pop(@stack)}; + } + + # Statement ends at the ';' or a close '}' at the + # outermost level. + if ($level == 0 && $c eq ';') { + last; + } + + # An else is really a conditional as long as its not else if + if ($level == 0 && $coff_set == 0 && + (!defined($p) || $p =~ /(?:\s|\}|\+)/) && + $remainder =~ /^(else)(?:\s|{)/ && + $remainder !~ /^else\s+if\b/) { + $coff = $off + length($1) - 1; + $coff_set = 1; + #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; + #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; + } + + if (($type eq '' || $type eq '(') && $c eq '(') { + $level++; + $type = '('; + } + if ($type eq '(' && $c eq ')') { + $level--; + $type = ($level != 0)? '(' : ''; + + if ($level == 0 && $coff < $soff) { + $coff = $off; + $coff_set = 1; + #warn "CSB: mark coff<$coff>\n"; + } + } + if (($type eq '' || $type eq '{') && $c eq '{') { + $level++; + $type = '{'; + } + if ($type eq '{' && $c eq '}') { + $level--; + $type = ($level != 0)? '{' : ''; + + if ($level == 0) { + if (substr($blk, $off + 1, 1) eq ';') { + $off++; + } + last; + } + } + # Preprocessor commands end at the newline unless escaped. + if ($type eq '#' && $c eq "\n" && $p ne "\\") { + $level--; + $type = ''; + $off++; + last; + } + $off++; + } + # We are truly at the end, so shuffle to the next line. + if ($off == $len) { + $loff = $len + 1; + $line++; + $remain--; + } + + my $statement = substr($blk, $soff, $off - $soff + 1); + my $condition = substr($blk, $soff, $coff - $soff + 1); + + #warn "STATEMENT<$statement>\n"; + #warn "CONDITION<$condition>\n"; + + #print "coff<$coff> soff<$off> loff<$loff>\n"; + + return ($statement, $condition, + $line, $remain + 1, $off - $loff + 1, $level); +} + +sub statement_lines { + my ($stmt) = @_; + + # Strip the diff line prefixes and rip blank lines at start and end. + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_rawlines { + my ($stmt) = @_; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_block_size { + my ($stmt) = @_; + + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*{//; + $stmt =~ s/}\s*$//; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + my @stmt_statements = ($stmt =~ /;/g); + + my $stmt_lines = $#stmt_lines + 2; + my $stmt_statements = $#stmt_statements + 1; + + if ($stmt_lines > $stmt_statements) { + return $stmt_lines; + } else { + return $stmt_statements; + } +} + +sub ctx_statement_full { + my ($linenr, $remain, $off) = @_; + my ($statement, $condition, $level); + + my (@chunks); + + # Grab the first conditional/block pair. + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "F: c<$condition> s<$statement> remain<$remain>\n"; + push(@chunks, [ $condition, $statement ]); + if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { + return ($level, $linenr, @chunks); + } + + # Pull in the following conditional/block pairs and see if they + # could continue the statement. + for (;;) { + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "C: c<$condition> s<$statement> remain<$remain>\n"; + last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); + #print "C: push\n"; + push(@chunks, [ $condition, $statement ]); + } + + return ($level, $linenr, @chunks); +} + +sub ctx_block_get { + my ($linenr, $remain, $outer, $open, $close, $off) = @_; + my $line; + my $start = $linenr - 1; + my $blk = ''; + my @o; + my @c; + my @res = (); + + my $level = 0; + my @stack = ($level); + for ($line = $start; $remain > 0; $line++) { + next if ($rawlines[$line] =~ /^-/); + $remain--; + + $blk .= $rawlines[$line]; + + # Handle nested #if/#else. + if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, $level); + } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { + $level = $stack[$#stack - 1]; + } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { + $level = pop(@stack); + } + + foreach my $c (split(//, $lines[$line])) { + ##print "C<$c>L<$level><$open$close>O<$off>\n"; + if ($off > 0) { + $off--; + next; + } + + if ($c eq $close && $level > 0) { + $level--; + last if ($level == 0); + } elsif ($c eq $open) { + $level++; + } + } + + if (!$outer || $level <= 1) { + push(@res, $rawlines[$line]); + } + + last if ($level == 0); + } + + return ($level, @res); +} +sub ctx_block_outer { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); + return @r; +} +sub ctx_block { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); + return @r; +} +sub ctx_statement { + my ($linenr, $remain, $off) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); + return @r; +} +sub ctx_block_level { + my ($linenr, $remain) = @_; + + return ctx_block_get($linenr, $remain, 0, '{', '}', 0); +} +sub ctx_statement_level { + my ($linenr, $remain, $off) = @_; + + return ctx_block_get($linenr, $remain, 0, '(', ')', $off); +} + +sub ctx_locate_comment { + my ($first_line, $end_line) = @_; + + # If c99 comment on the current line, or the line before or after + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + + # Catch a comment on the end of the line itself. + ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + return $current_comment if (defined $current_comment); + + # Look through the context and try and figure out if there is a + # comment. + my $in_comment = 0; + $current_comment = ''; + for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { + my $line = $rawlines[$linenr - 1]; + #warn " $line\n"; + if ($linenr == $first_line and $line =~ m@^.\s*\*@) { + $in_comment = 1; + } + if ($line =~ m@/\*@) { + $in_comment = 1; + } + if (!$in_comment && $current_comment ne '') { + $current_comment = ''; + } + $current_comment .= $line . "\n" if ($in_comment); + if ($line =~ m@\*/@) { + $in_comment = 0; + } + } + + chomp($current_comment); + return($current_comment); +} +sub ctx_has_comment { + my ($first_line, $end_line) = @_; + my $cmt = ctx_locate_comment($first_line, $end_line); + + ##print "LINE: $rawlines[$end_line - 1 ]\n"; + ##print "CMMT: $cmt\n"; + + return ($cmt ne ''); +} + +sub raw_line { + my ($linenr, $cnt) = @_; + + my $offset = $linenr - 1; + $cnt++; + + my $line; + while ($cnt) { + $line = $rawlines[$offset++]; + next if (defined($line) && $line =~ /^-/); + $cnt--; + } + + return $line; +} + +sub get_stat_real { + my ($linenr, $lc) = @_; + + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + + return $stat_real; +} + +sub get_stat_here { + my ($linenr, $cnt, $here) = @_; + + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + return $herectx; +} + +sub cat_vet { + my ($vet) = @_; + my ($res, $coded); + + $res = ''; + while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { + $res .= $1; + if ($2 ne '') { + $coded = sprintf("^%c", unpack('C', $2) + 64); + $res .= $coded; + } + } + $res =~ s/$/\$/; + + return $res; +} + +my $av_preprocessor = 0; +my $av_pending; +my @av_paren_type; +my $av_pend_colon; + +sub annotate_reset { + $av_preprocessor = 0; + $av_pending = '_'; + @av_paren_type = ('E'); + $av_pend_colon = 'O'; +} + +sub annotate_values { + my ($stream, $type) = @_; + + my $res; + my $var = '_' x length($stream); + my $cur = $stream; + + print "$stream\n" if ($dbg_values > 1); + + while (length($cur)) { + @av_paren_type = ('E') if ($#av_paren_type < 0); + print " <" . join('', @av_paren_type) . + "> <$type> <$av_pending>" if ($dbg_values > 1); + if ($cur =~ /^(\s+)/o) { + print "WS($1)\n" if ($dbg_values > 1); + if ($1 =~ /\n/ && $av_preprocessor) { + $type = pop(@av_paren_type); + $av_preprocessor = 0; + } + + } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { + print "CAST($1)\n" if ($dbg_values > 1); + push(@av_paren_type, $type); + $type = 'c'; + + } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { + print "DECLARE($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^($Modifier)\s*/) { + print "MODIFIER($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { + print "DEFINE($1,$2)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + if ($2 ne '') { + $av_pending = 'N'; + } + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { + print "UNDEF($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + + } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { + print "PRE_START($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { + print "PRE_RESTART($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $av_paren_type[$#av_paren_type]); + + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:endif))/o) { + print "PRE_END($1)\n" if ($dbg_values > 1); + + $av_preprocessor = 1; + + # Assume all arms of the conditional end as this + # one does, and continue as if the #endif was not here. + pop(@av_paren_type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\\\n)/o) { + print "PRECONT($1)\n" if ($dbg_values > 1); + + } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { + print "ATTR($1)\n" if ($dbg_values > 1); + $av_pending = $type; + $type = 'N'; + + } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { + print "SIZEOF($1)\n" if ($dbg_values > 1); + if (defined $2) { + $av_pending = 'V'; + } + $type = 'N'; + + } elsif ($cur =~ /^(if|while|for)\b/o) { + print "COND($1)\n" if ($dbg_values > 1); + $av_pending = 'E'; + $type = 'N'; + + } elsif ($cur =~/^(case)/o) { + print "CASE($1)\n" if ($dbg_values > 1); + $av_pend_colon = 'C'; + $type = 'N'; + + } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { + print "KEYWORD($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(\()/o) { + print "PAREN('$1')\n" if ($dbg_values > 1); + push(@av_paren_type, $av_pending); + $av_pending = '_'; + $type = 'N'; + + } elsif ($cur =~ /^(\))/o) { + my $new_type = pop(@av_paren_type); + if ($new_type ne '_') { + $type = $new_type; + print "PAREN('$1') -> $type\n" + if ($dbg_values > 1); + } else { + print "PAREN('$1')\n" if ($dbg_values > 1); + } + + } elsif ($cur =~ /^($Ident)\s*\(/o) { + print "FUNC($1)\n" if ($dbg_values > 1); + $type = 'V'; + $av_pending = 'V'; + + } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { + if (defined $2 && $type eq 'C' || $type eq 'T') { + $av_pend_colon = 'B'; + } elsif ($type eq 'E') { + $av_pend_colon = 'L'; + } + print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Ident|$Constant)/o) { + print "IDENT($1)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Assignment)/o) { + print "ASSIGN($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~/^(;|{|})/) { + print "END($1)\n" if ($dbg_values > 1); + $type = 'E'; + $av_pend_colon = 'O'; + + } elsif ($cur =~/^(,)/) { + print "COMMA($1)\n" if ($dbg_values > 1); + $type = 'C'; + + } elsif ($cur =~ /^(\?)/o) { + print "QUESTION($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(:)/o) { + print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); + + substr($var, length($res), 1, $av_pend_colon); + if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { + $type = 'E'; + } else { + $type = 'N'; + } + $av_pend_colon = 'O'; + + } elsif ($cur =~ /^(\[)/o) { + print "CLOSE($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { + my $variant; + + print "OPV($1)\n" if ($dbg_values > 1); + if ($type eq 'V') { + $variant = 'B'; + } else { + $variant = 'U'; + } + + substr($var, length($res), 1, $variant); + $type = 'N'; + + } elsif ($cur =~ /^($Operators)/o) { + print "OP($1)\n" if ($dbg_values > 1); + if ($1 ne '++' && $1 ne '--') { + $type = 'N'; + } + + } elsif ($cur =~ /(^.)/o) { + print "C($1)\n" if ($dbg_values > 1); + } + if (defined $1) { + $cur = substr($cur, length($1)); + $res .= $type x length($1); + } + } + + return ($res, $var); +} + +sub possible { + my ($possible, $line) = @_; + my $notPermitted = qr{(?: + ^(?: + $Modifier| + $Storage| + $Type| + DEFINE_\S+ + )$| + ^(?: + goto| + return| + case| + else| + asm|__asm__| + do| + \#| + \#\#| + )(?:\s|$)| + ^(?:typedef|struct|enum)\b + )}x; + warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); + if ($possible !~ $notPermitted) { + # Check for modifiers. + $possible =~ s/\s*$Storage\s*//g; + $possible =~ s/\s*$Sparse\s*//g; + if ($possible =~ /^\s*$/) { + + } elsif ($possible =~ /\s/) { + $possible =~ s/\s*$Type\s*//g; + for my $modifier (split(' ', $possible)) { + if ($modifier !~ $notPermitted) { + warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); + push(@modifierListFile, $modifier); + } + } + + } else { + warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); + push(@typeListFile, $possible); + } + build_types(); + } else { + warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); + } +} + +my $prefix = ''; + +sub show_type { + my ($type) = @_; + + $type =~ tr/[a-z]/[A-Z]/; + + return defined $use_type{$type} if (scalar keys %use_type > 0); + + return !defined $ignore_type{$type}; +} + +sub report { + my ($level, $type, $msg) = @_; + + if (!show_type($type) || + (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { + return 0; + } + my $output = ''; + if ($color) { + if ($level eq 'ERROR') { + $output .= RED; + } elsif ($level eq 'WARNING') { + $output .= YELLOW; + } else { + $output .= GREEN; + } + } + $output .= $prefix . $level . ':'; + if ($show_types) { + $output .= BLUE if ($color); + $output .= "$type:"; + } + $output .= RESET if ($color); + $output .= ' ' . $msg . "\n"; + + if ($showfile) { + my @lines = split("\n", $output, -1); + splice(@lines, 1, 1); + $output = join("\n", @lines); + } + + if ($terse) { + $output = (split('\n', $output))[0] . "\n"; + } + + if ($verbose && exists($verbose_messages{$type}) && + !exists($verbose_emitted{$type})) { + $output .= $verbose_messages{$type} . "\n\n"; + $verbose_emitted{$type} = 1; + } + + push(our @report, $output); + + return 1; +} + +sub report_dump { + our @report; +} + +sub fixup_current_range { + my ($lineRef, $offset, $length) = @_; + + if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { + my $o = $1; + my $l = $2; + my $no = $o + $offset; + my $nl = $l + $length; + $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; + } +} + +sub fix_inserted_deleted_lines { + my ($linesRef, $insertedRef, $deletedRef) = @_; + + my $range_last_linenr = 0; + my $delta_offset = 0; + + my $old_linenr = 0; + my $new_linenr = 0; + + my $next_insert = 0; + my $next_delete = 0; + + my @lines = (); + + my $inserted = @{$insertedRef}[$next_insert++]; + my $deleted = @{$deletedRef}[$next_delete++]; + + foreach my $old_line (@{$linesRef}) { + my $save_line = 1; + my $line = $old_line; #don't modify the array + if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename + $delta_offset = 0; + } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk + $range_last_linenr = $new_linenr; + fixup_current_range(\$line, $delta_offset, 0); + } + + while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { + $deleted = @{$deletedRef}[$next_delete++]; + $save_line = 0; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); + } + + while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { + push(@lines, ${$inserted}{'LINE'}); + $inserted = @{$insertedRef}[$next_insert++]; + $new_linenr++; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); + } + + if ($save_line) { + push(@lines, $line); + $new_linenr++; + } + + $old_linenr++; + } + + return @lines; +} + +sub fix_insert_line { + my ($linenr, $line) = @_; + + my $inserted = { + LINENR => $linenr, + LINE => $line, + }; + push(@fixed_inserted, $inserted); +} + +sub fix_delete_line { + my ($linenr, $line) = @_; + + my $deleted = { + LINENR => $linenr, + LINE => $line, + }; + + push(@fixed_deleted, $deleted); +} + +sub ERROR { + my ($type, $msg) = @_; + + if (report("ERROR", $type, $msg)) { + our $clean = 0; + our $cnt_error++; + return 1; + } + return 0; +} +sub WARN { + my ($type, $msg) = @_; + + if (report("WARNING", $type, $msg)) { + our $clean = 0; + our $cnt_warn++; + return 1; + } + return 0; +} +sub CHK { + my ($type, $msg) = @_; + + if ($check && report("CHECK", $type, $msg)) { + our $clean = 0; + our $cnt_chk++; + return 1; + } + return 0; +} + +sub check_absolute_file { + my ($absolute, $herecurr) = @_; + my $file = $absolute; + + ##print "absolute<$absolute>\n"; + + # See if any suffix of this path is a path within the tree. + while ($file =~ s@^[^/]*/@@) { + if (-f "$root/$file") { + ##print "file<$file>\n"; + last; + } + } + if (! -f _) { + return 0; + } + + # It is, so see if the prefix is acceptable. + my $prefix = $absolute; + substr($prefix, -length($file)) = ''; + + ##print "prefix<$prefix>\n"; + if ($prefix ne ".../") { + WARN("USE_RELATIVE_PATH", + "use relative pathname instead of absolute in changelog text\n" . $herecurr); + } +} + +sub trim { + my ($string) = @_; + + $string =~ s/^\s+|\s+$//g; + + return $string; +} + +sub ltrim { + my ($string) = @_; + + $string =~ s/^\s+//; + + return $string; +} + +sub rtrim { + my ($string) = @_; + + $string =~ s/\s+$//; + + return $string; +} + +sub string_find_replace { + my ($string, $find, $replace) = @_; + + $string =~ s/$find/$replace/g; + + return $string; +} + +sub tabify { + my ($leading) = @_; + + my $source_indent = $tabsize; + my $max_spaces_before_tab = $source_indent - 1; + my $spaces_to_tab = " " x $source_indent; + + #convert leading spaces to tabs + 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; + #Remove spaces before a tab + 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; + + return "$leading"; +} + +sub pos_last_openparen { + my ($line) = @_; + + my $pos = 0; + + my $opens = $line =~ tr/\(/\(/; + my $closes = $line =~ tr/\)/\)/; + + my $last_openparen = 0; + + if (($opens == 0) || ($closes >= $opens)) { + return -1; + } + + my $len = length($line); + + for ($pos = 0; $pos < $len; $pos++) { + my $string = substr($line, $pos); + if ($string =~ /^($FuncArg|$balanced_parens)/) { + $pos += length($1) - 1; + } elsif (substr($line, $pos, 1) eq '(') { + $last_openparen = $pos; + } elsif (index($string, '(') == -1) { + last; + } + } + + return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; +} + +sub get_raw_comment { + my ($line, $rawline) = @_; + my $comment = ''; + + for my $i (0 .. (length($line) - 1)) { + if (substr($line, $i, 1) eq "$;") { + $comment .= substr($rawline, $i, 1); + } + } + + return $comment; +} + +sub exclude_global_initialisers { + my ($realfile) = @_; + + # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). + return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || + $realfile =~ m@^samples/bpf/.*_kern\.c$@ || + $realfile =~ m@/bpf/.*\.bpf\.c$@; +} + +sub process { + my $filename = shift; + + my $linenr=0; + my $prevline=""; + my $prevrawline=""; + my $stashline=""; + my $stashrawline=""; + + my $length; + my $indent; + my $previndent=0; + my $stashindent=0; + + our $clean = 1; + my $signoff = 0; + my $fixes_tag = 0; + my $is_revert = 0; + my $needs_fixes_tag = ""; + my $author = ''; + my $authorsignoff = 0; + my $author_sob = ''; + my $is_patch = 0; + my $is_binding_patch = -1; + my $in_header_lines = $file ? 0 : 1; + my $in_commit_log = 0; #Scanning lines before patch + my $has_patch_separator = 0; #Found a --- line + my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_lines = 0; #Number of commit log lines + my $commit_log_possible_stack_dump = 0; + my $commit_log_long_line = 0; + my $commit_log_has_diff = 0; + my $reported_maintainer_file = 0; + my $non_utf8_charset = 0; + + my $last_git_commit_id_linenr = -1; + + my $last_blank_line = 0; + my $last_coalesced_string_linenr = -1; + + our @report = (); + our $cnt_lines = 0; + our $cnt_error = 0; + our $cnt_warn = 0; + our $cnt_chk = 0; + + # Trace the real file/line as we go. + my $realfile = ''; + my $realline = 0; + my $realcnt = 0; + my $here = ''; + my $context_function; #undef'd unless there's a known function + my $in_comment = 0; + my $comment_edge = 0; + my $first_line = 0; + my $p1_prefix = ''; + + my $prev_values = 'E'; + + # suppression flags + my %suppress_ifbraces; + my %suppress_whiletrailers; + my %suppress_export; + my $suppress_statement = 0; + + my %signatures = (); + + # Pre-scan the patch sanitizing the lines. + # Pre-scan the patch looking for any __setup documentation. + # + my @setup_docs = (); + my $setup_docs = 0; + + my $camelcase_file_seeded = 0; + + my $checklicenseline = 1; + + sanitise_line_reset(); + my $line; + foreach my $rawline (@rawlines) { + $linenr++; + $line = $rawline; + + push(@fixed, $rawline) if ($fix); + + if ($rawline=~/^\+\+\+\s+(\S+)/) { + $setup_docs = 0; + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { + $setup_docs = 1; + } + #next; + } + if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + $in_comment = 0; + + # Guestimate if this is a continuing comment. Run + # the context looking for a comment "edge". If this + # edge is a close comment then we must be in a comment + # at context start. + my $edge; + my $cnt = $realcnt; + for (my $ln = $linenr + 1; $cnt > 0; $ln++) { + next if (defined $rawlines[$ln - 1] && + $rawlines[$ln - 1] =~ /^-/); + $cnt--; + #print "RAW<$rawlines[$ln - 1]>\n"; + last if (!defined $rawlines[$ln - 1]); + if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && + $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { + ($edge) = $1; + last; + } + } + if (defined $edge && $edge eq '*/') { + $in_comment = 1; + } + + # Guestimate if this is a continuing comment. If this + # is the start of a diff block and this line starts + # ' *' then it is very likely a comment. + if (!defined $edge && + $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) + { + $in_comment = 1; + } + + ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; + sanitise_line_reset($in_comment); + + } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { + # Standardise the strings and chars within the input to + # simplify matching -- only bother with positive lines. + $line = sanitise_line($rawline); + } + push(@lines, $line); + + if ($realcnt > 1) { + $realcnt-- if ($line =~ /^(?:\+| |$)/); + } else { + $realcnt = 0; + } + + #print "==>$rawline\n"; + #print "-->$line\n"; + + if ($setup_docs && $line =~ /^\+/) { + push(@setup_docs, $line); + } + } + + $prefix = ''; + + $realcnt = 0; + $linenr = 0; + $fixlinenr = -1; + foreach my $line (@lines) { + $linenr++; + $fixlinenr++; + my $sline = $line; #copy of $line + $sline =~ s/$;/ /g; #with comments as spaces + + my $rawline = $rawlines[$linenr - 1]; + my $raw_comment = get_raw_comment($line, $rawline); + +# check if it's a mode change, rename or start of a patch + if (!$in_commit_log && + ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || + ($line =~ /^rename (?:from|to) \S+\s*$/ || + $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { + $is_patch = 1; + } + +#extract the line range in the file after the patch is applied + if (!$in_commit_log && + $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { + my $context = $4; + $is_patch = 1; + $first_line = $linenr + 1; + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + annotate_reset(); + $prev_values = 'E'; + + %suppress_ifbraces = (); + %suppress_whiletrailers = (); + %suppress_export = (); + $suppress_statement = 0; + if ($context =~ /\b(\w+)\s*\(/) { + $context_function = $1; + } else { + undef $context_function; + } + next; + +# track the line number as we move through the hunk, note that +# new versions of GNU diff omit the leading space on completely +# blank context lines so we need to count that too. + } elsif ($line =~ /^( |\+|$)/) { + $realline++; + $realcnt-- if ($realcnt != 0); + + # Measure the line length and indent. + ($length, $indent) = line_stats($rawline); + + # Track the previous line. + ($prevline, $stashline) = ($stashline, $line); + ($previndent, $stashindent) = ($stashindent, $indent); + ($prevrawline, $stashrawline) = ($stashrawline, $rawline); + + #warn "line<$line>\n"; + + } elsif ($realcnt == 1) { + $realcnt--; + } + + my $hunk_line = ($realcnt != 0); + + $here = "#$linenr: " if (!$file); + $here = "#$realline: " if ($file); + + my $found_file = 0; + # extract the filename as it passes + if ($line =~ /^diff --git.*?(\S+)$/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + $found_file = 1; + } elsif ($line =~ /^\+\+\+\s+(\S+)/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + + $p1_prefix = $1; + if (!$file && $tree && $p1_prefix ne '' && + -e "$root/$p1_prefix") { + WARN("PATCH_PREFIX", + "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); + } + + if ($realfile =~ m@^include/asm/@) { + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); + } + $found_file = 1; + } + +#make up the handle for any error we report on this line + if ($showfile) { + $prefix = "$realfile:$realline: " + } elsif ($emacs) { + if ($file) { + $prefix = "$filename:$realline: "; + } else { + $prefix = "$filename:$linenr: "; + } + } + + if ($found_file) { + if (is_maintained_obsolete($realfile)) { + WARN("OBSOLETE", + "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); + } + if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { + $check = 1; + } else { + $check = $check_orig; + } + $checklicenseline = 1; + + if ($realfile !~ /^MAINTAINERS/) { + my $last_binding_patch = $is_binding_patch; + + $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; + + if (($last_binding_patch != -1) && + ($last_binding_patch ^ $is_binding_patch)) { + WARN("DT_SPLIT_BINDING_PATCH", + "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); + } + } + + next; + } + + $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); + + my $hereline = "$here\n$rawline\n"; + my $herecurr = "$here\n$rawline\n"; + my $hereprev = "$here\n$prevrawline\n$rawline\n"; + + $cnt_lines++ if ($realcnt != 0); + +# Verify the existence of a commit log if appropriate +# 2 is used because a $signature is counted in $commit_log_lines + if ($in_commit_log) { + if ($line !~ /^\s*$/) { + $commit_log_lines++; #could be a $signature + } + } elsif ($has_commit_log && $commit_log_lines < 2) { + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n"); + $commit_log_lines = 2; #warn only once + } + +# Check if the commit log has what seems like a diff which can confuse patch + if ($in_commit_log && !$commit_log_has_diff && + (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && + $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || + $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || + $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); + $commit_log_has_diff = 1; + } + +# Check for incorrect file permissions + if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { + my $permhere = $here . "FILE: $realfile\n"; + if ($realfile !~ m@scripts/@ && + $realfile !~ /\.(py|pl|awk|sh)$/) { + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" . $permhere); + } + } + +# Check the patch for a From: + if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { + $author = $1; + my $curline = $linenr; + while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { + $author .= $1; + } + $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); + $author =~ s/"//g; + $author = reformat_email($author); + } + +# Check the patch for a signoff: + if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { + $signoff++; + $in_commit_log = 0; + if ($author ne '' && $authorsignoff != 1) { + if (same_email_addresses($1, $author)) { + $authorsignoff = 1; + } else { + my $ctx = $1; + my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); + my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); + + if (lc $email_address eq lc $author_address && $email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 2; + } elsif (lc $email_address eq lc $author_address) { + $author_sob = $ctx; + $authorsignoff = 3; + } elsif ($email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 4; + + my $address1 = $email_address; + my $address2 = $author_address; + + if ($address1 =~ /(\S+)\+\S+(\@.*)/) { + $address1 = "$1$2"; + } + if ($address2 =~ /(\S+)\+\S+(\@.*)/) { + $address2 = "$1$2"; + } + if ($address1 eq $address2) { + $authorsignoff = 5; + } + } + } + } + } + +# Check for patch separator + if ($line =~ /^---$/) { + $has_patch_separator = 1; + $in_commit_log = 0; + } + +# Check if MAINTAINERS is being updated. If so, there's probably no need to +# emit the "does MAINTAINERS need updating?" message on file add/move/delete + if ($line =~ /^\s*MAINTAINERS\s*\|/) { + $reported_maintainer_file = 1; + } + +# Check signature styles + if (!$in_header_lines && + $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { + my $space_before = $1; + my $sign_off = $2; + my $space_after = $3; + my $email = $4; + my $ucfirst_sign_off = ucfirst(lc($sign_off)); + + if ($sign_off !~ /$signature_tags/) { + my $suggested_signature = find_standard_signature($sign_off); + if ($suggested_signature eq "") { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } else { + if (WARN("BAD_SIGN_OFF", + "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; + } + } + } + if (defined $space_before && $space_before ne "") { + if (WARN("BAD_SIGN_OFF", + "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { + if (WARN("BAD_SIGN_OFF", + "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + + } + if (!defined $space_after || $space_after ne " ") { + if (WARN("BAD_SIGN_OFF", + "Use a single space after $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); + if ($suggested_email eq "") { + ERROR("BAD_SIGN_OFF", + "Unrecognized email address: '$email'\n" . $herecurr); + } else { + my $dequoted = $suggested_email; + $dequoted =~ s/^"//; + $dequoted =~ s/" 1) { + WARN("BAD_SIGN_OFF", + "Use a single name comment in email: '$email'\n" . $herecurr); + } + + + # stable@vger.kernel.org or stable@kernel.org shouldn't + # have an email name. In addition comments should strictly + # begin with a # + if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { + if (($comment ne "" && $comment !~ /^#.+/) || + ($email_name ne "")) { + my $cur_name = $email_name; + my $new_comment = $comment; + $cur_name =~ s/[a-zA-Z\s\-\"]+//g; + + # Remove brackets enclosing comment text + # and # from start of comments to get comment text + $new_comment =~ s/^\((.*)\)$/$1/; + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^[\s\#]+|\s+$//g; + + $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); + $new_comment = " # $new_comment" if ($new_comment ne ""); + my $new_email = "$email_address$new_comment"; + + if (WARN("BAD_STABLE_ADDRESS_STYLE", + "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { + my $new_comment = $comment; + + # Extract comment text from within brackets or + # c89 style /*...*/ comments + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^\/\*(.*)\*\/$/$1/; + + $new_comment = trim($new_comment); + $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo + $new_comment = "($new_comment)" if ($new_comment ne ""); + my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); + + if (WARN("BAD_SIGN_OFF", + "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } + +# Check for duplicate signatures + my $sig_nospace = $line; + $sig_nospace =~ s/\s//g; + $sig_nospace = lc($sig_nospace); + if (defined $signatures{$sig_nospace}) { + WARN("BAD_SIGN_OFF", + "Duplicate signature\n" . $herecurr); + } else { + $signatures{$sig_nospace} = 1; + } + +# Check Co-developed-by: immediately followed by Signed-off-by: with same name and email + if ($sign_off =~ /^co-developed-by:$/i) { + if ($email eq $author) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . $herecurr); + } + if (!defined $lines[$linenr]) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr); + } elsif ($rawlines[$linenr] !~ /^signed-off-by:\s*(.*)/i) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr . $rawlines[$linenr] . "\n"); + } elsif ($1 ne $email) { + WARN("BAD_SIGN_OFF", + "Co-developed-by and Signed-off-by: name/email do not match\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + +# check if Reported-by: is followed by a Closes: tag + if ($sign_off =~ /^reported(?:|-and-tested)-by:$/i) { + if (!defined $lines[$linenr]) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . "\n"); + } elsif ($rawlines[$linenr] !~ /^closes:\s*/i) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + } + +# These indicate a bug fix + if (!$in_header_lines && !$is_patch && + $line =~ /^This reverts commit/) { + $is_revert = 1; + } + + if (!$in_header_lines && !$is_patch && + $line =~ /((?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller))/) { + $needs_fixes_tag = $1; + } + +# Check Fixes: styles is correct + if (!$in_header_lines && + $line =~ /^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})(?:\s*($balanced_parens))?/i) { + my $tag = $1; + my $orig_commit = $2; + my $title; + my $title_has_quotes = 0; + $fixes_tag = 1; + if (defined $3) { + # Always strip leading/trailing parens then double quotes if existing + $title = substr($3, 1, -1); + if ($title =~ /^".*"$/) { + $title = substr($title, 1, -1); + $title_has_quotes = 1; + } + } else { + $title = "commit title" + } + + + my $tag_case = not ($tag eq "Fixes:"); + my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i); + + my $id_length = not ($orig_commit =~ /^[0-9a-f]{12}$/i); + my $id_case = not ($orig_commit !~ /[A-F]/); + + my $id = "0123456789ab"; + my ($cid, $ctitle) = git_commit_info($orig_commit, $id, + $title); + + if ($ctitle ne $title || $tag_case || $tag_space || + $id_length || $id_case || !$title_has_quotes) { + if (WARN("BAD_FIXES_TAG", + "Please use correct Fixes: style 'Fixes: <12 chars of sha1> (\"\")' - ie: 'Fixes: $cid (\"$ctitle\")'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "Fixes: $cid (\"$ctitle\")"; + } + } + } + +# Check email subject for common tools that don't need to be mentioned + if ($in_header_lines && + $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { + WARN("EMAIL_SUBJECT", + "A patch subject line should describe the change not the tool that found it\n" . $herecurr); + } + +# Check for Gerrit Change-Ids not in any patch context + if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { + if (ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# Check if the commit log is in a possible stack dump + if ($in_commit_log && !$commit_log_possible_stack_dump && + ($line =~ /^\s*(?:WARNING:|BUG:)/ || + $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || + # timestamp + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || + $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || + $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { + # stack dump address styles + $commit_log_possible_stack_dump = 1; + } + +# Check for line lengths > 75 in commit log, warn once + if ($in_commit_log && !$commit_log_long_line && + length($line) > 75 && + !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || + # file delta changes + $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ || + # filename then : + $line =~ /^\s*(?:Fixes:|$link_tags_search|$signature_tags)/i || + # A Fixes:, link or signature tag line + $commit_log_possible_stack_dump)) { + WARN("COMMIT_LOG_LONG_LINE", + "Prefer a maximum 75 chars per line (possible unwrapped commit description?)\n" . $herecurr); + $commit_log_long_line = 1; + } + +# Reset possible stack dump if a blank line is found + if ($in_commit_log && $commit_log_possible_stack_dump && + $line =~ /^\s*$/) { + $commit_log_possible_stack_dump = 0; + } + +# Check for odd tags before a URI/URL + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*http/ && $1 !~ /^$link_tags_search$/) { + if ($1 =~ /^v(?:ersion)?\d+/i) { + WARN("COMMIT_LOG_VERSIONING", + "Patch version information should be after the --- line\n" . $herecurr); + } else { + WARN("COMMIT_LOG_USE_LINK", + "Unknown link reference '$1', use $link_tags_print instead\n" . $herecurr); + } + } + +# Check for misuse of the link tags + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*(\S+)/) { + my $tag = $1; + my $value = $2; + if ($tag =~ /^$link_tags_search$/ && $value !~ m{^https?://}) { + WARN("COMMIT_LOG_WRONG_LINK", + "'$tag' should be followed by a public http(s) link\n" . $herecurr); + } + } + +# Check for lines starting with a # + if ($in_commit_log && $line =~ /^#/) { + if (WARN("COMMIT_COMMENT_SYMBOL", + "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^/ /; + } + } + +# Check for git id commit length and improperly formed commit descriptions +# A correctly formed commit description is: +# commit <SHA-1 hash length 12+ chars> ("Complete commit subject") +# with the commit subject '("' prefix and '")' suffix +# This is a fairly compilicated block as it tests for what appears to be +# bare SHA-1 hash with minimum length of 5. It also avoids several types of +# possible SHA-1 matches. +# A commit match can span multiple lines so this block attempts to find a +# complete typical commit on a maximum of 3 lines + if ($perl_version_ok && + $in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && + $line !~ /^This reverts commit [0-9a-f]{7,40}/ && + (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || + ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && + $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && + $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { + my $init_char = "c"; + my $orig_commit = ""; + my $short = 1; + my $long = 0; + my $case = 1; + my $space = 1; + my $id = '0123456789ab'; + my $orig_desc = "commit description"; + my $description = ""; + my $herectx = $herecurr; + my $has_parens = 0; + my $has_quotes = 0; + + my $input = $line; + if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { + for (my $n = 0; $n < 2; $n++) { + if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { + $orig_desc = $1; + $has_parens = 1; + # Always strip leading/trailing parens then double quotes if existing + $orig_desc = substr($orig_desc, 1, -1); + if ($orig_desc =~ /^".*"$/) { + $orig_desc = substr($orig_desc, 1, -1); + $has_quotes = 1; + } + last; + } + last if ($#lines < $linenr + $n); + $input .= " " . trim($rawlines[$linenr + $n]); + $herectx .= "$rawlines[$linenr + $n]\n"; + } + $herectx = $herecurr if (!$has_parens); + } + + if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + $init_char = $1; + $orig_commit = lc($2); + $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { + $orig_commit = lc($1); + } + + ($id, $description) = git_commit_info($orig_commit, + $id, $orig_desc); + + if (defined($id) && + ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && + $last_git_commit_id_linenr != $linenr - 1) { + ERROR("GIT_COMMIT_ID", + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); + } + #don't report the next line if this line ends in commit and the sha1 hash is the next line + $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); + } + +# Check for mailing list archives other than lore.kernel.org + if ($rawline =~ m{http.*\b$obsolete_archives}) { + WARN("PREFER_LORE_ARCHIVE", + "Use lore.kernel.org archive links when possible - see https://lore.kernel.org/lists.html\n" . $herecurr); + } + +# Check for added, moved or deleted files + if (!$reported_maintainer_file && !$in_commit_log && + ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || + $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || + ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && + (defined($1) || defined($2))))) { + $is_patch = 1; + $reported_maintainer_file = 1; + WARN("FILE_PATH_CHANGES", + "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); + } + +# Check for adding new DT bindings not in schema format + if (!$in_commit_log && + ($line =~ /^new file mode\s*\d+\s*$/) && + ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { + WARN("DT_SCHEMA_BINDING_PATCH", + "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); + } + +# Check for wrappage within a valid hunk of the file + if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { + ERROR("CORRUPTED_PATCH", + "patch seems to be corrupt (line wrapped?)\n" . + $herecurr) if (!$emitted_corrupt++); + } + +# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php + if (($realfile =~ /^$/ || $line =~ /^\+/) && + $rawline !~ m/^$UTF8*$/) { + my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); + + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; + my $hereptr = "$hereline$ptr\n"; + + CHK("INVALID_UTF8", + "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); + } + +# Check if it's the start of a commit log +# (not a header line and we haven't seen the patch filename) + if ($in_header_lines && $realfile =~ /^$/ && + !($rawline =~ /^\s+(?:\S|$)/ || + $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { + $in_header_lines = 0; + $in_commit_log = 1; + $has_commit_log = 1; + } + +# Check if there is UTF-8 in a commit log when a mail header has explicitly +# declined it, i.e defined some charset where it is missing. + if ($in_header_lines && + $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && + $1 !~ /utf-8/i) { + $non_utf8_charset = 1; + } + + if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && + $rawline =~ /$NON_ASCII_UTF8/) { + WARN("UTF8_BEFORE_PATCH", + "8-bit UTF-8 used in possible commit log\n" . $herecurr); + } + +# Check for absolute kernel paths in commit message + if ($tree && $in_commit_log) { + while ($line =~ m{(?:^|\s)(/\S*)}g) { + my $file = $1; + + if ($file =~ m{^(.*?)(?::\d+)+:?$} && + check_absolute_file($1, $herecurr)) { + # + } else { + check_absolute_file($file, $herecurr); + } + } + } + +# Check for various typo / spelling mistakes + if (defined($misspellings) && + ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { + while ($rawline =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { + my $typo = $1; + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); + my $hereptr = "$hereline$ptr\n"; + my $typo_fix = $spelling_fix{lc($typo)}; + $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); + $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("TYPO_SPELLING", + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; + } + } + } + +# check for invalid commit id + if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { + my $id; + my $description; + ($id, $description) = git_commit_info($2, undef, undef); + if (!defined($id)) { + WARN("UNKNOWN_COMMIT_ID", + "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); + } + } + +# check for repeated words separated by a single space +# avoid false positive from list command eg, '-rw-r--r-- 1 root root' + if (($rawline =~ /^\+/ || $in_commit_log) && + $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { + pos($rawline) = 1 if (!$in_commit_log); + while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { + + my $first = $1; + my $second = $2; + my $start_pos = $-[1]; + my $end_pos = $+[2]; + if ($first =~ /(?:struct|union|enum)/) { + pos($rawline) += length($first) + length($second) + 1; + next; + } + + next if (lc($first) ne lc($second)); + next if ($first eq 'long'); + + # check for character before and after the word matches + my $start_char = ''; + my $end_char = ''; + $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); + $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); + + next if ($start_char =~ /^\S$/); + next if (index(" \t.,;?!", $end_char) == -1); + + # avoid repeating hex occurrences like 'ff ff fe 09 ...' + if ($first =~ /\b[0-9a-f]{2,}\b/i) { + next if (!exists($allow_repeated_words{lc($first)})); + } + + if (WARN("REPEATED_WORD", + "Possible repeated word: '$first'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; + } + } + + # if it's a repeated word on consecutive lines in a comment block + if ($prevline =~ /$;+\s*$/ && + $prevrawline =~ /($word_pattern)\s*$/) { + my $last_word = $1; + if ($rawline =~ /^\+\s*\*\s*$last_word /) { + if (WARN("REPEATED_WORD", + "Possible repeated word: '$last_word'\n" . $hereprev) && + $fix) { + $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; + } + } + } + } + +# ignore non-hunk lines and lines being removed + next if (!$hunk_line || $line =~ /^-/); + +#trailing whitespace + if ($line =~ /^\+.*\015/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("DOS_LINE_ENDINGS", + "DOS line endings\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/[\s\015]+$//; + } + } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("TRAILING_WHITESPACE", + "trailing whitespace\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + + $rpt_cleaners = 1; + } + +# Check for FSF mailing addresses. + if ($rawline =~ /\bwrite to the Free/i || + $rawline =~ /\b675\s+Mass\s+Ave/i || + $rawline =~ /\b59\s+Temple\s+Pl/i || + $rawline =~ /\b51\s+Franklin\s+St/i) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + my $msg_level = \&ERROR; + $msg_level = \&CHK if ($file); + &{$msg_level}("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) + } + +# check for Kconfig help text having a real description +# Only applies when adding the entry originally, after that we do not have +# sufficient context to determine whether it is indeed long enough. + if ($realfile =~ /Kconfig/ && + # 'choice' is usually the last thing on the line (though + # Kconfig supports named choices), so use a word boundary + # (\b) rather than a whitespace character (\s) + $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { + my $ln = $linenr; + my $needs_help = 0; + my $has_help = 0; + my $help_length = 0; + while (defined $lines[$ln]) { + my $f = $lines[$ln++]; + + next if ($f =~ /^-/); + last if ($f !~ /^[\+ ]/); # !patch context + + if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { + $needs_help = 1; + next; + } + if ($f =~ /^\+\s*help\s*$/) { + $has_help = 1; + next; + } + + $f =~ s/^.//; # strip patch context [+ ] + $f =~ s/#.*//; # strip # directives + $f =~ s/^\s+//; # strip leading blanks + next if ($f =~ /^$/); # skip blank lines + + # At the end of this Kconfig block: + # This only checks context lines in the patch + # and so hopefully shouldn't trigger false + # positives, even though some of these are + # common words in help texts + if ($f =~ /^(?:config|menuconfig|choice|endchoice| + if|endif|menu|endmenu|source)\b/x) { + last; + } + $help_length++ if ($has_help); + } + if ($needs_help && + $help_length < $min_conf_desc_length) { + my $stat_real = get_stat_real($linenr, $ln - 1); + WARN("CONFIG_DESCRIPTION", + "please write a help paragraph that fully describes the config symbol\n" . "$here\n$stat_real\n"); + } + } + +# check MAINTAINERS entries + if ($realfile =~ /^MAINTAINERS$/) { +# check MAINTAINERS entries for the right form + if ($rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } +# check MAINTAINERS entries for the right ordering too + my $preferred_order = 'MRLSWQBCPTFXNK'; + if ($rawline =~ /^\+[A-Z]:/ && + $prevrawline =~ /^[\+ ][A-Z]:/) { + $rawline =~ /^\+([A-Z]):\s*(.*)/; + my $cur = $1; + my $curval = $2; + $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; + my $prev = $1; + my $prevval = $2; + my $curindex = index($preferred_order, $cur); + my $previndex = index($preferred_order, $prev); + if ($curindex < 0) { + WARN("MAINTAINERS_STYLE", + "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); + } else { + if ($previndex >= 0 && $curindex < $previndex) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); + } elsif ((($prev eq 'F' && $cur eq 'F') || + ($prev eq 'X' && $cur eq 'X')) && + ($prevval cmp $curval) > 0) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); + } + } + } + } + + if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && + ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { + my $flag = $1; + my $replacement = { + 'EXTRA_AFLAGS' => 'asflags-y', + 'EXTRA_CFLAGS' => 'ccflags-y', + 'EXTRA_CPPFLAGS' => 'cppflags-y', + 'EXTRA_LDFLAGS' => 'ldflags-y', + }; + + WARN("DEPRECATED_VARIABLE", + "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); + } + +# check for DT compatible documentation + if (defined $root && + (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || + ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { + + my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; + + my $dt_path = $root . "/Documentation/devicetree/bindings/"; + my $vp_file = $dt_path . "vendor-prefixes.yaml"; + + foreach my $compat (@compats) { + my $compat2 = $compat; + $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; + my $compat3 = $compat; + $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; + `grep -Erq "$compat|$compat2|$compat3" $dt_path`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); + } + + next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; + my $vendor = $1; + `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); + } + } + } + +# check for using SPDX license tag at beginning of files + if ($realline == $checklicenseline) { + if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { + $checklicenseline = 2; + } elsif ($rawline =~ /^\+/) { + my $comment = ""; + if ($realfile =~ /\.(h|s|S)$/) { + $comment = '/*'; + } elsif ($realfile =~ /\.(c|rs|dts|dtsi)$/) { + $comment = '//'; + } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { + $comment = '#'; + } elsif ($realfile =~ /\.rst$/) { + $comment = '..'; + } + +# check SPDX comment style for .[chsS] files + if ($realfile =~ /\.[chsS]$/ && + $rawline =~ /SPDX-License-Identifier:/ && + $rawline !~ m@^\+\s*\Q$comment\E\s*@) { + WARN("SPDX_LICENSE_TAG", + "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); + } + + if ($comment !~ /^$/ && + $rawline !~ m@^\+\Q$comment\E SPDX-License-Identifier: @) { + WARN("SPDX_LICENSE_TAG", + "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); + } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { + my $spdx_license = $1; + if (!is_SPDX_License_valid($spdx_license)) { + WARN("SPDX_LICENSE_TAG", + "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); + } + if ($realfile =~ m@^Documentation/devicetree/bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR BSD-2-Clause/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("SPDX_LICENSE_TAG", + + "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; + } + } + if ($realfile =~ m@^include/dt-bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR \S+/) { + WARN("SPDX_LICENSE_TAG", + "DT binding headers should be licensed (GPL-2.0-only OR .*)\n" . $herecurr); + } + } + } + } + +# check for embedded filenames + if ($rawline =~ /^\+.*\b\Q$realfile\E\b/) { + WARN("EMBEDDED_FILENAME", + "It's generally not useful to have the filename in the file\n" . $herecurr); + } + +# check we are in a valid source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts)$/); + +# check for using SPDX-License-Identifier on the wrong line number + if ($realline != $checklicenseline && + $rawline =~ /\bSPDX-License-Identifier:/ && + substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { + WARN("SPDX_LICENSE_TAG", + "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); + } + +# line length limit (with some exclusions) +# +# There are a few types of lines that may extend beyond $max_line_length: +# logging functions like pr_info that end in a string +# lines with a single string +# #defines that are a single string +# lines with an RFC3986 like URL +# +# There are 3 different line length message types: +# LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length +# LONG_LINE_STRING a string starts before but extends beyond $max_line_length +# LONG_LINE all other lines longer than $max_line_length +# +# if LONG_LINE is ignored, the other 2 types are also ignored +# + + if ($line =~ /^\+/ && $length > $max_line_length) { + my $msg_type = "LONG_LINE"; + + # Check the allowed long line types first + + # logging functions that end in a string that starts + # before $max_line_length + if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = ""; + + # lines with only strings (w/ possible termination) + # #defines with only strings + } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || + $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { + $msg_type = ""; + + # More special cases + } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || + $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { + $msg_type = ""; + + # URL ($rawline is used in case the URL is in a comment) + } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { + $msg_type = ""; + + # Otherwise set the alternate message types + + # a comment starts before $max_line_length + } elsif ($line =~ /($;[\s$;]*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_COMMENT" + + # a quoted string starts before $max_line_length + } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_STRING" + } + + if ($msg_type ne "" && + show_type("LONG_LINE") && show_type($msg_type)) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}($msg_type, + "line length of $length exceeds $max_line_length columns\n" . $herecurr); + } + } + +# check for adding lines without a newline. + if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { + if (WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr+1, "No newline at end of file"); + } + } + +# check for .L prefix local symbols in .S files + if ($realfile =~ /\.S$/ && + $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { + WARN("AVOID_L_PREFIX", + "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/core-api/asm-annotations.rst\n" . $herecurr); + } + +# check we are in a valid source file C or perl if not then ignore this hunk + next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); + +# at the beginning of a line any tabs must come first and anything +# more than $tabsize must use tabs. + if ($rawline =~ /^\+\s* \t\s*\S/ || + $rawline =~ /^\+\s* \s*/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + $rpt_cleaners = 1; + if (ERROR("CODE_INDENT", + "code indent should use tabs where possible\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check for space before tabs. + if ($rawline =~ /^\+/ && $rawline =~ / \t/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("SPACE_BEFORE_TAB", + "please, no space before tabs\n" . $herevet) && + $fix) { + while ($fixed[$fixlinenr] =~ + s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} + while ($fixed[$fixlinenr] =~ + s/(^\+.*) +\t/$1\t/) {} + } + } + +# check for assignments on the start of a line + if ($sline =~ /^\+\s+($Assignment)[^=]/) { + my $operator = $1; + if (CHK("ASSIGNMENT_CONTINUATIONS", + "Assignment operator '$1' should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # add assignment operator to the previous line, remove from current line + $fixed[$fixlinenr - 1] .= " $operator"; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check for && or || at the start of a line + if ($rawline =~ /^\+\s*(&&|\|\|)/) { + my $operator = $1; + if (CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # insert logical operator at last non-comment, non-whitepsace char on previous line + $prevline =~ /[\s$;]*$/; + my $line_end = substr($prevrawline, $-[0]); + $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check indentation starts on a tab stop + if ($perl_version_ok && + $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { + my $indent = length($1); + if ($indent % $tabsize) { + if (WARN("TABSTOP", + "Statements should start on a tabstop\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; + } + } + } + +# check multi-line statement indentation matches previous line + if ($perl_version_ok && + $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { + $prevline =~ /^\+(\t*)(.*)$/; + my $oldindent = $1; + my $rest = $2; + + my $pos = pos_last_openparen($rest); + if ($pos >= 0) { + $line =~ /^(\+| )([ \t]*)/; + my $newindent = $2; + + my $goodtabindent = $oldindent . + "\t" x ($pos / $tabsize) . + " " x ($pos % $tabsize); + my $goodspaceindent = $oldindent . " " x $pos; + + if ($newindent ne $goodtabindent && + $newindent ne $goodspaceindent) { + + if (CHK("PARENTHESIS_ALIGNMENT", + "Alignment should match open parenthesis\n" . $hereprev) && + $fix && $line =~ /^\+/) { + $fixed[$fixlinenr] =~ + s/^\+[ \t]*/\+$goodtabindent/; + } + } + } + } + +# check for space after cast like "(int) foo" or "(struct foo) bar" +# avoid checking a few false positives: +# "sizeof(<type>)" or "__alignof__(<type>)" +# function pointer declarations like "(*foo)(int) = bar;" +# structure definitions like "(struct foo) { 0 };" +# multiline macros that define functions +# known attributes or the __attribute__ keyword + if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && + (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { + if (CHK("SPACING", + "No space is necessary after a cast\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/(\(\s*$Type\s*\))[ \t]+/$1/; + } + } + +# Block comments use * on subsequent lines + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $prevrawline =~ /^\+.*?\/\*/ && #starting /* + $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ + $rawline =~ /^\+/ && #line is new + $rawline !~ /^\+[ \t]*\*/) { #no leading * + WARN("BLOCK_COMMENT_STYLE", + "Block comments use * on subsequent lines\n" . $hereprev); + } + +# Block comments use */ on trailing lines + if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ + $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ + $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ + $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ + WARN("BLOCK_COMMENT_STYLE", + "Block comments use a trailing */ on a separate line\n" . $herecurr); + } + +# Block comment * alignment + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $line =~ /^\+[ \t]*$;/ && #leading comment + $rawline =~ /^\+[ \t]*\*/ && #leading * + (($prevrawline =~ /^\+.*?\/\*/ && #leading /* + $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ + $prevrawline =~ /^\+[ \t]*\*/)) { #leading * + my $oldindent; + $prevrawline =~ m@^\+([ \t]*/?)\*@; + if (defined($1)) { + $oldindent = expand_tabs($1); + } else { + $prevrawline =~ m@^\+(.*/?)\*@; + $oldindent = expand_tabs($1); + } + $rawline =~ m@^\+([ \t]*)\*@; + my $newindent = $1; + $newindent = expand_tabs($newindent); + if (length($oldindent) ne length($newindent)) { + WARN("BLOCK_COMMENT_STYLE", + "Block comments should align the * on each line\n" . $hereprev); + } + } + +# check for missing blank lines after struct/union declarations +# with exceptions for various attributes and macros + if ($prevline =~ /^[\+ ]};?\s*$/ && + $line =~ /^\+/ && + !($line =~ /^\+\s*$/ || + $line =~ /^\+\s*(?:EXPORT_SYMBOL|early_param|ALLOW_ERROR_INJECTION)/ || + $line =~ /^\+\s*MODULE_/i || + $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || + $line =~ /^\+[a-z_]*init/ || + $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || + $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*builtin_[\w_]*driver/ || + $line =~ /^\+\s*__setup/)) { + if (CHK("LINE_SPACING", + "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for multiple consecutive blank lines + if ($prevline =~ /^[\+ ]\s*$/ && + $line =~ /^\+\s*$/ && + $last_blank_line != ($linenr - 1)) { + if (CHK("LINE_SPACING", + "Please don't use multiple blank lines\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + + $last_blank_line = $linenr; + } + +# check for missing blank lines after declarations +# (declarations must have the same indentation and not be at the start of line) + if (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/) { + # use temporaries + my $sl = $sline; + my $pl = $prevline; + # remove $Attribute/$Sparse uses to simplify comparisons + $sl =~ s/\b(?:$Attribute|$Sparse)\b//g; + $pl =~ s/\b(?:$Attribute|$Sparse)\b//g; + if (($pl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $pl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $pl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $pl =~ /^\+\s+$declaration_macros/) && + # for "else if" which can look like "$Ident $Ident" + !($pl =~ /^\+\s+$c90_Keywords\b/ || + # other possible extensions of declaration lines + $pl =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + # not starting a section or a macro "\" extended line + $pl =~ /(?:\{\s*|\\)$/) && + # looks like a declaration + !($sl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $sl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $sl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $sl =~ /^\+\s+$declaration_macros/ || + # start of struct or union or enum + $sl =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || + # start or end of block or continuation of declaration + $sl =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + # bitfield continuation + $sl =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + # other possible extensions of declaration lines + $sl =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + } + +# check for spaces at the beginning of a line. +# Exceptions: +# 1) within comments +# 2) indented preprocessor commands +# 3) hanging labels + if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("LEADING_SPACE", + "please, no spaces at the start of a line\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check we are in a valid C source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c)$/); + +# check for unusual line ending [ or ( + if ($line =~ /^\+.*([\[\(])\s*$/) { + CHK("OPEN_ENDED_LINE", + "Lines should not end with a '$1'\n" . $herecurr); + } + +# check if this appears to be the start function declaration, save the name + if ($sline =~ /^\+\{\s*$/ && + $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { + $context_function = $1; + } + +# check if this appears to be the end of function declaration + if ($sline =~ /^\+\}\s*$/) { + undef $context_function; + } + +# check indentation of any line with a bare else +# (but not if it is a multiple line "if (foo) return bar; else return baz;") +# if the previous line is a break or return and is indented 1 tab more... + if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { + my $tabs = length($1) + 1; + if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || + ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && + defined $lines[$linenr] && + $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { + WARN("UNNECESSARY_ELSE", + "else is not generally useful after a break or return\n" . $hereprev); + } + } + +# check indentation of a line with a break; +# if the previous line is a goto, return or break +# and is indented the same # of tabs + if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { + my $tabs = $1; + if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { + if (WARN("UNNECESSARY_BREAK", + "break is not useful after a $1\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + } + +# check for RCS/CVS revision markers + if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { + WARN("CVS_KEYWORD", + "CVS style keyword markers, these will _not_ be updated\n". $herecurr); + } + +# check for old HOTPLUG __dev<foo> section markings + if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { + WARN("HOTPLUG_SECTION", + "Using $1 is unnecessary\n" . $herecurr); + } + +# Check for potential 'bare' types + my ($stat, $cond, $line_nr_next, $remain_next, $off_next, + $realline_next); +#print "LINE<$line>\n"; + if ($linenr > $suppress_statement && + $realcnt && $sline =~ /.\s*\S/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0); + $stat =~ s/\n./\n /g; + $cond =~ s/\n./\n /g; + +#print "linenr<$linenr> <$stat>\n"; + # If this statement has no statement boundaries within + # it there is no point in retrying a statement scan + # until we hit end of it. + my $frag = $stat; $frag =~ s/;+\s*$//; + if ($frag !~ /(?:{|;)/) { +#print "skip<$line_nr_next>\n"; + $suppress_statement = $line_nr_next; + } + + # Find the real next line. + $realline_next = $line_nr_next; + if (defined $realline_next && + (!defined $lines[$realline_next - 1] || + substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { + $realline_next++; + } + + my $s = $stat; + $s =~ s/{.*$//s; + + # Ignore goto labels. + if ($s =~ /$Ident:\*$/s) { + + # Ignore functions being called + } elsif ($s =~ /^.\s*$Ident\s*\(/s) { + + } elsif ($s =~ /^.\s*else\b/s) { + + # declarations always start with types + } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { + my $type = $1; + $type =~ s/\s+/ /g; + possible($type, "A:" . $s); + + # definitions in global scope can only start with types + } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { + possible($1, "B:" . $s); + } + + # any (foo ... *) is a pointer cast, and foo is a type + while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { + possible($1, "C:" . $s); + } + + # Check for any sort of function declaration. + # int foo(something bar, other baz); + # void (*store_gdt)(x86_descr_ptr *); + if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { + my ($name_len) = length($1); + + my $ctx = $s; + substr($ctx, 0, $name_len + 1, ''); + $ctx =~ s/\)[^\)]*$//; + + for my $arg (split(/\s*,\s*/, $ctx)) { + if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { + + possible($1, "D:" . $s); + } + } + } + + } + +# +# Checks which may be anchored in the context. +# + +# Check for switch () and associated case and default +# statements should be at the same indent. + if ($line=~/\bswitch\s*\(.*\)/) { + my $err = ''; + my $sep = ''; + my @ctx = ctx_block_outer($linenr, $realcnt); + shift(@ctx); + for my $ctx (@ctx) { + my ($clen, $cindent) = line_stats($ctx); + if ($ctx =~ /^\+\s*(case\s+|default:)/ && + $indent != $cindent) { + $err .= "$sep$ctx\n"; + $sep = ''; + } else { + $sep = "[...]\n"; + } + } + if ($err ne '') { + ERROR("SWITCH_CASE_INDENT_LEVEL", + "switch and case should be at the same indent\n$hereline$err"); + } + } + +# if/while/etc brace do not go on next line, unless defining a do while loop, +# or if that brace on the next line is for something else + if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { + my $pre_ctx = "$1$2"; + + my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); + + if ($line =~ /^\+\t{6,}/) { + WARN("DEEP_INDENTATION", + "Too many leading tabs - consider code refactoring\n" . $herecurr); + } + + my $ctx_cnt = $realcnt - $#ctx - 1; + my $ctx = join("\n", @ctx); + + my $ctx_ln = $linenr; + my $ctx_skip = $realcnt; + + while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && + defined $lines[$ctx_ln - 1] && + $lines[$ctx_ln - 1] =~ /^-/)) { + ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; + $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); + $ctx_ln++; + } + + #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; + #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; + + if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { + ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && + $ctx =~ /\)\s*\;\s*$/ && + defined $lines[$ctx_ln - 1]) + { + my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); + if ($nindent > $indent) { + WARN("TRAILING_SEMICOLON", + "trailing semicolon indicates no statements, indent implies otherwise\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + } + } + +# Check relative indent for conditionals and blocks. + if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($s, $c) = ($stat, $cond); + + substr($s, 0, length($c), ''); + + # remove inline comments + $s =~ s/$;/ /g; + $c =~ s/$;/ /g; + + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + + # Make sure we remove the line prefixes as we have + # none on the first line, and are going to readd them + # where necessary. + $s =~ s/\n./\n/gs; + while ($s =~ /\n\s+\\\n/) { + $cond_lines += $s =~ s/\n\s+\\\n/\n/g; + } + + # We want to check the first line inside the block + # starting at the end of the conditional, so remove: + # 1) any blank line termination + # 2) any opening brace { on end of the line + # 3) any do (...) { + my $continuation = 0; + my $check = 0; + $s =~ s/^.*\bdo\b//; + $s =~ s/^\s*{//; + if ($s =~ s/^\s*\\//) { + $continuation = 1; + } + if ($s =~ s/^\s*?\n//) { + $check = 1; + $cond_lines++; + } + + # Also ignore a loop construct at the end of a + # preprocessor statement. + if (($prevline =~ /^.\s*#\s*define\s/ || + $prevline =~ /\\\s*$/) && $continuation == 0) { + $check = 0; + } + + my $cond_ptr = -1; + $continuation = 0; + while ($cond_ptr != $cond_lines) { + $cond_ptr = $cond_lines; + + # If we see an #else/#elif then the code + # is not linear. + if ($s =~ /^\s*\#\s*(?:else|elif)/) { + $check = 0; + } + + # Ignore: + # 1) blank lines, they should be at 0, + # 2) preprocessor lines, and + # 3) labels. + if ($continuation || + $s =~ /^\s*?\n/ || + $s =~ /^\s*#\s*?/ || + $s =~ /^\s*$Ident\s*:/) { + $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; + if ($s =~ s/^.*?\n//) { + $cond_lines++; + } + } + } + + my (undef, $sindent) = line_stats("+" . $s); + my $stat_real = raw_line($linenr, $cond_lines); + + # Check if either of these lines are modified, else + # this is not this patch's fault. + if (!defined($stat_real) || + $stat !~ /^\+/ && $stat_real !~ /^\+/) { + $check = 0; + } + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; + + if ($check && $s ne '' && + (($sindent % $tabsize) != 0 || + ($sindent < $indent) || + ($sindent == $indent && + ($s !~ /^\s*(?:\}|\{|else\b)/)) || + ($sindent > $indent + $tabsize))) { + WARN("SUSPECT_CODE_INDENT", + "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); + } + } + + # Track the 'values' across context and added lines. + my $opline = $line; $opline =~ s/^./ /; + my ($curr_values, $curr_vars) = + annotate_values($opline . "\n", $prev_values); + $curr_values = $prev_values . $curr_values; + if ($dbg_values) { + my $outline = $opline; $outline =~ s/\t/ /g; + print "$linenr > .$outline\n"; + print "$linenr > $curr_values\n"; + print "$linenr > $curr_vars\n"; + } + $prev_values = substr($curr_values, -1); + +#ignore lines not being added + next if ($line =~ /^[^\+]/); + +# check for self assignments used to avoid compiler warnings +# e.g.: int foo = foo, *bar = NULL; +# struct foo bar = *(&(bar)); + if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { + my $var = $1; + if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { + WARN("SELF_ASSIGNMENT", + "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); + } + } + +# check for dereferences that span multiple lines + if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && + $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { + $prevline =~ /($Lval\s*(?:\.|->))\s*$/; + my $ref = $1; + $line =~ /^.\s*($Lval)/; + $ref .= $1; + $ref =~ s/\s//g; + WARN("MULTILINE_DEREFERENCE", + "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); + } + +# check for declarations of signed or unsigned without int + while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { + my $type = $1; + my $var = $2; + $var = "" if (!defined $var); + if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { + my $sign = $1; + my $pointer = $2; + + $pointer = "" if (!defined $pointer); + + if (WARN("UNSPECIFIED_INT", + "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && + $fix) { + my $decl = trim($sign) . " int "; + my $comp_pointer = $pointer; + $comp_pointer =~ s/\s//g; + $decl .= $comp_pointer; + $decl = rtrim($decl) if ($var eq ""); + $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; + } + } + } + +# TEST: allow direct testing of the type matcher. + if ($dbg_type) { + if ($line =~ /^.\s*$Declare\s*$/) { + ERROR("TEST_TYPE", + "TEST: is type\n" . $herecurr); + } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { + ERROR("TEST_NOT_TYPE", + "TEST: is not type ($1 is)\n". $herecurr); + } + next; + } +# TEST: allow direct testing of the attribute matcher. + if ($dbg_attr) { + if ($line =~ /^.\s*$Modifier\s*$/) { + ERROR("TEST_ATTR", + "TEST: is attr\n" . $herecurr); + } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { + ERROR("TEST_NOT_ATTR", + "TEST: is not attr ($1 is)\n". $herecurr); + } + next; + } + +# check for initialisation to aggregates open brace on the next line + if ($line =~ /^.\s*{/ && + $prevline =~ /(?:^|[^=])=\s*$/) { + if (ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/\s*=\s*$/ = {/; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $line; + $fixedline =~ s/^(.\s*)\{\s*/$1/; + fix_insert_line($fixlinenr, $fixedline); + } + } + +# +# Checks which are anchored on the added line. +# + +# check for malformed paths in #include statements (uses RAW line) + if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { + my $path = $1; + if ($path =~ m{//}) { + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" . $herecurr); + } + if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { + ERROR("UAPI_INCLUDE", + "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); + } + } + +# no C99 // comments + if ($line =~ m{//}) { + if (ERROR("C99_COMMENTS", + "do not use C99 // comments\n" . $herecurr) && + $fix) { + my $line = $fixed[$fixlinenr]; + if ($line =~ /\/\/(.*)$/) { + my $comment = trim($1); + $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; + } + } + } + # Remove C99 comments. + $line =~ s@//.*@@; + $opline =~ s@//.*@@; + +# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider +# the whole statement. +#print "APW <$lines[$realline_next - 1]>\n"; + if (defined $realline_next && + exists $lines[$realline_next - 1] && + !defined $suppress_export{$realline_next} && + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { + # Handle definitions which produce identifiers with + # a prefix: + # XXX(foo); + # EXPORT_SYMBOL(something_foo); + my $name = $1; + $name =~ s/^\s*($Ident).*/$1/; + if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && + $name =~ /^${Ident}_$2/) { +#print "FOO C name<$name>\n"; + $suppress_export{$realline_next} = 1; + + } elsif ($stat !~ /(?: + \n.}\s*$| + ^.DEFINE_$Ident\(\Q$name\E\)| + ^.DECLARE_$Ident\(\Q$name\E\)| + ^.LIST_HEAD\(\Q$name\E\)| + ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| + \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() + )/x) { +#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; + $suppress_export{$realline_next} = 2; + } else { + $suppress_export{$realline_next} = 1; + } + } + if (!defined $suppress_export{$linenr} && + $prevline =~ /^.\s*$/ && + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { +#print "FOO B <$lines[$linenr - 1]>\n"; + $suppress_export{$linenr} = 2; + } + if (defined $suppress_export{$linenr} && + $suppress_export{$linenr} == 2) { + WARN("EXPORT_SYMBOL", + "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); + } + +# check for global initialisers. + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && + !exclude_global_initialisers($realfile)) { + if (ERROR("GLOBAL_INITIALISERS", + "do not initialise globals to $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; + } + } +# check for static initialisers. + if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { + if (ERROR("INITIALISED_STATIC", + "do not initialise statics to $1\n" . + $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; + } + } + +# check for misordered declarations of char/short/int/long with signed/unsigned + while ($sline =~ m{(\b$TypeMisordered\b)}g) { + my $tmp = trim($1); + WARN("MISORDERED_TYPE", + "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); + } + +# check for unnecessary <signed> int declarations of short/long/long long + while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { + my $type = trim($1); + next if ($type !~ /\bint\b/); + next if ($type !~ /\b(?:short|long\s+long|long)\b/); + my $new_type = $type; + $new_type =~ s/\b\s*int\s*\b/ /; + $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; + $new_type =~ s/^const\s+//; + $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); + $new_type = "const $new_type" if ($type =~ /^const\b/); + $new_type =~ s/\s+/ /g; + $new_type = trim($new_type); + if (WARN("UNNECESSARY_INT", + "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; + } + } + +# check for static const char * arrays. + if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static const char * array should probably be static const char * const\n" . + $herecurr); + } + +# check for initialized const char arrays that should be static const + if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { + if (WARN("STATIC_CONST_CHAR_ARRAY", + "const array should probably be static const\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; + } + } + +# check for static char foo[] = "bar" declarations. + if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static char array declaration should probably be static const char\n" . + $herecurr); + } + +# check for const <foo> const where <foo> is not a pointer or array type + if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { + my $found = $1; + if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { + WARN("CONST_CONST", + "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); + } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { + WARN("CONST_CONST", + "'const $found const' should probably be 'const $found'\n" . $herecurr); + } + } + +# check for const static or static <non ptr type> const declarations +# prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' + if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || + $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { + if (WARN("STATIC_CONST", + "Move const after static - use 'static const $1'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; + $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; + } + } + +# check for non-global char *foo[] = {"bar", ...} declarations. + if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "char * array declaration might be better as static const\n" . + $herecurr); + } + +# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) + if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { + my $array = $1; + if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { + my $array_div = $1; + if (WARN("ARRAY_SIZE", + "Prefer ARRAY_SIZE($array)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; + } + } + } + +# check for function declarations without arguments like "int foo()" + if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { + if (ERROR("FUNCTION_WITHOUT_ARGS", + "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; + } + } + +# check for new typedefs, only function parameters and sparse annotations +# make sense. + if ($line =~ /\btypedef\s/ && + $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && + $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && + $line !~ /\b$typeTypedefs\b/ && + $line !~ /\b__bitwise\b/) { + WARN("NEW_TYPEDEFS", + "do not add new typedefs\n" . $herecurr); + } + +# * goes on variable not on type + # (char*[ const]) + while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { + #print "AA<$1>\n"; + my ($ident, $from, $to) = ($1, $2, $2); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + +## print "1: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to) { + if (ERROR("POINTER_LOCATION", + "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && + $fix) { + my $sub_from = $ident; + my $sub_to = $ident; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { + #print "BB<$1>\n"; + my ($match, $from, $to, $ident) = ($1, $2, $2, $3); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + # Modifiers should have spaces. + $to =~ s/(\b$Modifier$)/$1 /; + +## print "2: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to && $ident !~ /^$Modifier$/) { + if (ERROR("POINTER_LOCATION", + "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && + $fix) { + + my $sub_from = $match; + my $sub_to = $match; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + +# do not use BUG() or variants + if ($line =~ /\b(?!AA_|BUILD_|DCCP_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("AVOID_BUG", + "Do not crash the kernel unless it is absolutely unavoidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BUG() or variants\n" . $herecurr); + } + +# avoid LINUX_VERSION_CODE + if ($line =~ /\bLINUX_VERSION_CODE\b/) { + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); + } + +# check for uses of printk_ratelimit + if ($line =~ /\bprintk_ratelimit\s*\(/) { + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); + } + +# printk should use KERN_* levels + if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); + } + +# prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> + if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { + my $printk = $1; + my $modifier = $2; + my $orig = $3; + $modifier = "" if (!defined($modifier)); + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + my $level2 = $level; + $level2 = "dbg" if ($level eq "debug"); + $level .= $modifier; + $level2 .= $modifier; + WARN("PREFER_PR_LEVEL", + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); + } + +# prefer dev_<level> to dev_printk(KERN_<LEVEL> + if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + $level = "dbg" if ($level eq "debug"); + WARN("PREFER_DEV_LEVEL", + "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); + } + +# trace_printk should not be used in production code. + if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { + WARN("TRACE_PRINTK", + "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); + } + +# ENOSYS means "bad syscall nr" and nothing else. This will have a small +# number of false positives, but assembly files are not checked, so at +# least the arch entry code will not trigger this warning. + if ($line =~ /\bENOSYS\b/) { + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); + } + +# ENOTSUPP is not a standard error code and should be avoided in new patches. +# Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. +# Similarly to ENOSYS warning a small number of false positives is expected. + if (!$file && $line =~ /\bENOTSUPP\b/) { + if (WARN("ENOTSUPP", + "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; + } + } + +# function brace can't be on same line, except for #defines of do while, +# or if closed on same line + if ($perl_version_ok && + $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && + $sline !~ /\#\s*define\b.*do\s*\{/ && + $sline !~ /}/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + my $fixed_line = $rawline; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; + my $line1 = $1; + my $line2 = $2; + fix_insert_line($fixlinenr, ltrim($line1)); + fix_insert_line($fixlinenr, "\+{"); + if ($line2 !~ /^\s*$/) { + fix_insert_line($fixlinenr, "\+\t" . trim($line2)); + } + } + } + +# open braces for enum, union and struct go on the same line. + if ($line =~ /^.\s*{/ && + $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following $1 go on the same line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = rtrim($prevrawline) . " {"; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)\{\s*/$1\t/; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +# missing space after union, struct or enum definition + if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { + if (WARN("SPACING", + "missing space after $1 definition\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; + } + } + +# Function pointer declarations +# check spacing between type, funcptr, and args +# canonical declaration is "type (*funcptr)(args...)" + if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { + my $declare = $1; + my $pre_pointer_space = $2; + my $post_pointer_space = $3; + my $funcname = $4; + my $post_funcname_space = $5; + my $pre_args_space = $6; + +# the $Declare variable will capture all spaces after the type +# so check it for a missing trailing missing space but pointer return types +# don't need a space so don't warn for those. + my $post_declare_space = ""; + if ($declare =~ /(\s+)$/) { + $post_declare_space = $1; + $declare = rtrim($declare); + } + if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { + WARN("SPACING", + "missing space after return type\n" . $herecurr); + $post_declare_space = " "; + } + +# unnecessary space "type (*funcptr)(args...)" +# This test is not currently implemented because these declarations are +# equivalent to +# int foo(int bar, ...) +# and this is form shouldn't/doesn't generate a checkpatch warning. +# +# elsif ($declare =~ /\s{2,}$/) { +# WARN("SPACING", +# "Multiple spaces after return type\n" . $herecurr); +# } + +# unnecessary space "type ( *funcptr)(args...)" + if (defined $pre_pointer_space && + $pre_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer open parenthesis\n" . $herecurr); + } + +# unnecessary space "type (* funcptr)(args...)" + if (defined $post_pointer_space && + $post_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr )(args...)" + if (defined $post_funcname_space && + $post_funcname_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr) (args...)" + if (defined $pre_args_space && + $pre_args_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer arguments\n" . $herecurr); + } + + if (show_type("SPACING") && $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; + } + } + +# check for spacing round square brackets; allowed: +# 1. with a type on the left -- int [] a; +# 2. at the beginning of a line for slice initialisers -- [0...10] = 5, +# 3. inside a curly brace -- = { [0...10] = 5 } + while ($line =~ /(.*?\s)\[/g) { + my ($where, $prefix) = ($-[1], $1); + if ($prefix !~ /$Type\s+$/ && + ($where != 0 || $prefix !~ /^.\s+$/) && + $prefix !~ /[{,:]\s+$/) { + if (ERROR("BRACKET_SPACE", + "space prohibited before open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(\+.*?)\s+\[/$1\[/; + } + } + } + +# check for spaces between functions and their parentheses. + while ($line =~ /($Ident)\s+\(/g) { + my $name = $1; + my $ctx_before = substr($line, 0, $-[1]); + my $ctx = "$ctx_before$name"; + + # Ignore those directives where spaces _are_ permitted. + if ($name =~ /^(?: + if|for|while|switch|return|case| + volatile|__volatile__| + __attribute__|format|__extension__| + asm|__asm__|scoped_guard)$/x) + { + # cpp #define statements have non-optional spaces, ie + # if there is a space between the name and the open + # parenthesis it is simply not a parameter group. + } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { + + # cpp #elif statement condition may start with a ( + } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { + + # If this whole things ends with a type its most + # likely a typedef for a function. + } elsif ($ctx =~ /$Type$/) { + + } else { + if (WARN("SPACING", + "space prohibited between function name and open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b$name\s+\(/$name\(/; + } + } + } + +# Check operator spacing. + if (!($line=~/\#\s*include/)) { + my $fixed_line = ""; + my $line_fixed = 0; + + my $ops = qr{ + <<=|>>=|<=|>=|==|!=| + \+=|-=|\*=|\/=|%=|\^=|\|=|&=| + =>|->|<<|>>|<|>|=|!|~| + &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| + \?:|\?|: + }x; + my @elements = split(/($ops|;)/, $opline); + +## print("element count: <" . $#elements . ">\n"); +## foreach my $el (@elements) { +## print("el: <$el>\n"); +## } + + my @fix_elements = (); + my $off = 0; + + foreach my $el (@elements) { + push(@fix_elements, substr($rawline, $off, length($el))); + $off += length($el); + } + + $off = 0; + + my $blank = copy_spacing($opline); + my $last_after = -1; + + for (my $n = 0; $n < $#elements; $n += 2) { + + my $good = $fix_elements[$n] . $fix_elements[$n + 1]; + +## print("n: <$n> good: <$good>\n"); + + $off += length($elements[$n]); + + # Pick up the preceding and succeeding characters. + my $ca = substr($opline, 0, $off); + my $cc = ''; + if (length($opline) >= ($off + length($elements[$n + 1]))) { + $cc = substr($opline, $off + length($elements[$n + 1])); + } + my $cb = "$ca$;$cc"; + + my $a = ''; + $a = 'V' if ($elements[$n] ne ''); + $a = 'W' if ($elements[$n] =~ /\s$/); + $a = 'C' if ($elements[$n] =~ /$;$/); + $a = 'B' if ($elements[$n] =~ /(\[|\()$/); + $a = 'O' if ($elements[$n] eq ''); + $a = 'E' if ($ca =~ /^\s*$/); + + my $op = $elements[$n + 1]; + + my $c = ''; + if (defined $elements[$n + 2]) { + $c = 'V' if ($elements[$n + 2] ne ''); + $c = 'W' if ($elements[$n + 2] =~ /^\s/); + $c = 'C' if ($elements[$n + 2] =~ /^$;/); + $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); + $c = 'O' if ($elements[$n + 2] eq ''); + $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); + } else { + $c = 'E'; + } + + my $ctx = "${a}x${c}"; + + my $at = "(ctx:$ctx)"; + + my $ptr = substr($blank, 0, $off) . "^"; + my $hereptr = "$hereline$ptr\n"; + + # Pull out the value of this operator. + my $op_type = substr($curr_values, $off + 1, 1); + + # Get the full operator variant. + my $opv = $op . substr($curr_vars, $off, 1); + + # Ignore operators passed as parameters. + if ($op_type ne 'V' && + $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { + +# # Ignore comments +# } elsif ($op =~ /^$;+$/) { + + # ; should have either the end of line or a space or \ after it + } elsif ($op eq ';') { + if ($ctx !~ /.x[WEBC]/ && + $cc !~ /^\\/ && $cc !~ /^;/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + + # // is a comment + } elsif ($op eq '//') { + + # : when part of a bitfield + } elsif ($opv eq ':B') { + # skip the bitfield test for now + + # No spaces for: + # -> + } elsif ($op eq '->') { + if ($ctx =~ /Wx.|.xW/) { + if (ERROR("SPACING", + "spaces prohibited around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # , must not have a space before and must have a space on the right. + } elsif ($op eq ',') { + my $rtrim_before = 0; + my $space_after = 0; + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $rtrim_before = 1; + } + } + if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $last_after = $n; + $space_after = 1; + } + } + if ($rtrim_before || $space_after) { + if ($rtrim_before) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + } else { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + } + if ($space_after) { + $good .= " "; + } + } + + # '*' as part of a type definition -- reported already. + } elsif ($opv eq '*_') { + #warn "'*' is part of type\n"; + + # unary operators should have a space before and + # none after. May be left adjacent to another + # unary operator, or a cast + } elsif ($op eq '!' || $op eq '~' || + $opv eq '*U' || $opv eq '-U' || + $opv eq '&U' || $opv eq '&&U') { + if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { + if (ERROR("SPACING", + "space required before that '$op' $at\n" . $hereptr)) { + if ($n != $last_after + 2) { + $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } + if ($op eq '*' && $cc =~/\s*$Modifier\b/) { + # A unary '*' may be const + + } elsif ($ctx =~ /.xW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # unary ++ and unary -- are allowed no space on one side. + } elsif ($op eq '++' or $op eq '--') { + if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { + if (ERROR("SPACING", + "space required one side of that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + if ($ctx =~ /Wx[BE]/ || + ($ctx =~ /Wx./ && $cc =~ /^;/)) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + if ($ctx =~ /ExW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # << and >> may either have or not have spaces both sides + } elsif ($op eq '<<' or $op eq '>>' or + $op eq '&' or $op eq '^' or $op eq '|' or + $op eq '+' or $op eq '-' or + $op eq '*' or $op eq '/' or + $op eq '%') + { + if ($check) { + if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { + if (CHK("SPACING", + "spaces preferred around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + $fix_elements[$n + 2] =~ s/^\s+//; + $line_fixed = 1; + } + } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { + if (CHK("SPACING", + "space preferred before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { + if (ERROR("SPACING", + "need consistent spacing around '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # A colon needs no spaces before when it is + # terminating a case value or a label. + } elsif ($opv eq ':C' || $opv eq ':L') { + if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + + # All the others need spaces both sides. + } elsif ($ctx !~ /[EWC]x[CWE]/) { + my $ok = 0; + + # Ignore email addresses <foo@bar> + if (($op eq '<' && + $cc =~ /^\S+\@\S+>/) || + ($op eq '>' && + $ca =~ /<\S+\@\S+$/)) + { + $ok = 1; + } + + # for asm volatile statements + # ignore a colon with another + # colon immediately before or after + if (($op eq ':') && + ($ca =~ /:$/ || $cc =~ /^:/)) { + $ok = 1; + } + + # messages are ERROR, but ?: are CHK + if ($ok == 0) { + my $msg_level = \&ERROR; + $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); + + if (&{$msg_level}("SPACING", + "spaces required around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + } + $off += length($elements[$n + 1]); + +## print("n: <$n> GOOD: <$good>\n"); + + $fixed_line = $fixed_line . $good; + } + + if (($#elements % 2) == 0) { + $fixed_line = $fixed_line . $fix_elements[$#elements]; + } + + if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { + $fixed[$fixlinenr] = $fixed_line; + } + + + } + +# check for whitespace before a non-naked semicolon + if ($line =~ /^\+.*\S\s+;\s*$/) { + if (WARN("SPACING", + "space prohibited before semicolon\n" . $herecurr) && + $fix) { + 1 while $fixed[$fixlinenr] =~ + s/^(\+.*\S)\s+;/$1;/; + } + } + +# check for multiple assignments + if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { + CHK("MULTIPLE_ASSIGNMENTS", + "multiple assignments should be avoided\n" . $herecurr); + } + +## # check for multiple declarations, allowing for a function declaration +## # continuation. +## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && +## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { +## +## # Remove any bracketed sections to ensure we do not +## # falsely report the parameters of functions. +## my $ln = $line; +## while ($ln =~ s/\([^\(\)]*\)//g) { +## } +## if ($ln =~ /,/) { +## WARN("MULTIPLE_DECLARATION", +## "declaring multiple variables together should be avoided\n" . $herecurr); +## } +## } + +#need space before brace following if, while, etc + if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || + $line =~ /\b(?:else|do)\{/) { + if (ERROR("SPACING", + "space required before the open brace '{'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; + } + } + +## # check for blank lines before declarations +## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && +## $prevrawline =~ /^.\s*$/) { +## WARN("SPACING", +## "No blank lines before declarations\n" . $hereprev); +## } +## + +# closing brace should have a space following it when it has anything +# on the line + if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { + if (ERROR("SPACING", + "space required after that close brace '}'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/}((?!(?:,|;|\)))\S)/} $1/; + } + } + +# check spacing on square brackets + if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { + if (ERROR("SPACING", + "space prohibited after that open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\[\s+/\[/; + } + } + if ($line =~ /\s\]/) { + if (ERROR("SPACING", + "space prohibited before that close square bracket ']'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\]/\]/; + } + } + +# check spacing on parentheses + if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && + $line !~ /for\s*\(\s+;/) { + if (ERROR("SPACING", + "space prohibited after that open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\(\s+/\(/; + } + } + if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && + $line !~ /for\s*\(.*;\s+\)/ && + $line !~ /:\s+\)/) { + if (ERROR("SPACING", + "space prohibited before that close parenthesis ')'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\)/\)/; + } + } + +# check unnecessary parentheses around addressof/dereference single $Lvals +# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar + + while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { + my $var = $1; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around $var\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; + } + } + +# check for unnecessary parentheses around function pointer uses +# ie: (foo->bar)(); should be foo->bar(); +# but not "if (foo->bar) (" to avoid some false positives + if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { + my $var = $2; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around function pointer $var\n" . $herecurr) && + $fix) { + my $var2 = deparenthesize($var); + $var2 =~ s/\s//g; + $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; + } + } + +# check for unnecessary parentheses around comparisons in if uses +# when !drivers/staging or command-line uses --strict + if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && + $perl_version_ok && defined($stat) && + $stat =~ /(^.\s*if\s*($balanced_parens))/) { + my $if_stat = $1; + my $test = substr($2, 1, -1); + my $herectx; + while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { + my $match = $1; + # avoid parentheses around potential macro args + next if ($match =~ /^\s*\w+\s*$/); + if (!defined($herectx)) { + $herectx = $here . "\n"; + my $cnt = statement_rawlines($if_stat); + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + last if $rl =~ /^[ \+].*\{/; + } + } + CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around '$match'\n" . $herectx); + } + } + +# check that goto labels aren't indented (allow a single space indentation) +# and ignore bitfield definitions like foo:1 +# Strictly, labels can have whitespace after the identifier and before the : +# but this is not allowed here as many ?: uses would appear to be labels + if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && + $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && + $sline !~ /^.\s+default:/) { + if (WARN("INDENTED_LABEL", + "labels should not be indented\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.)\s+/$1/; + } + } + +# check if a statement with a comma should be two statements like: +# foo = bar(), /* comma should be semicolon */ +# bar = baz(); + if (defined($stat) && + $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + WARN("SUSPECT_COMMA_SEMICOLON", + "Possible comma where semicolon could be used\n" . $herectx); + } + +# return is not a function + if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { + my $spacing = $1; + if ($perl_version_ok && + $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { + my $value = $1; + $value = deparenthesize($value); + if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { + ERROR("RETURN_PARENTHESES", + "return is not a function, parentheses are not required\n" . $herecurr); + } + } elsif ($spacing !~ /\s+/) { + ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr); + } + } + +# unnecessary return in a void function +# at end-of-function, with the previous line a single leading tab, then return; +# and the line before that not a goto label target like "out:" + if ($sline =~ /^[ \+]}\s*$/ && + $prevline =~ /^\+\treturn\s*;\s*$/ && + $linenr >= 3 && + $lines[$linenr - 3] =~ /^[ +]/ && + $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { + WARN("RETURN_VOID", + "void function return statements are not generally useful\n" . $hereprev); + } + +# if statements using unnecessary parentheses - ie: if ((foo == bar)) + if ($perl_version_ok && + $line =~ /\bif\s*((?:\(\s*){2,})/) { + my $openparens = $1; + my $count = $openparens =~ tr@\(@\(@; + my $msg = ""; + if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { + my $comp = $4; #Not $1 because of $LvalOrFunc + $msg = " - maybe == should be = ?" if ($comp eq "=="); + WARN("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses$msg\n" . $herecurr); + } + } + +# comparisons with a constant or upper case identifier on the left +# avoid cases like "foo + BAR < baz" +# only fix matches surrounded by parentheses to avoid incorrect +# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" + if ($perl_version_ok && + $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { + my $lead = $1; + my $const = $2; + my $comp = $3; + my $to = $4; + my $newcomp = $comp; + if ($lead !~ /(?:$Operators|\.)\s*$/ && + $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && + WARN("CONSTANT_COMPARISON", + "Comparisons should place the constant on the right side of the test\n" . $herecurr) && + $fix) { + if ($comp eq "<") { + $newcomp = ">"; + } elsif ($comp eq "<=") { + $newcomp = ">="; + } elsif ($comp eq ">") { + $newcomp = "<"; + } elsif ($comp eq ">=") { + $newcomp = "<="; + } + $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; + } + } + +# Return of what appears to be an errno should normally be negative + if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { + my $name = $1; + if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { + WARN("USE_NEGATIVE_ERRNO", + "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); + } + } + +# Need a space before open parenthesis after if, while etc + if ($line =~ /\b(if|while|for|switch)\(/) { + if (ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b(if|while|for|switch)\(/$1 \(/; + } + } + +# Check for illegal assignment in if conditional -- and check for trailing +# statements after the conditional. + if ($line =~ /do\s*(?!{)/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($stat_next) = ctx_statement_block($line_nr_next, + $remain_next, $off_next); + $stat_next =~ s/\n./\n /g; + ##print "stat<$stat> stat_next<$stat_next>\n"; + + if ($stat_next =~ /^\s*while\b/) { + # If the statement carries leading newlines, + # then count those as offsets. + my ($whitespace) = + ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = + statement_rawlines($whitespace) - 1; + + $suppress_whiletrailers{$line_nr_next + + $offset} = 1; + } + } + if (!defined $suppress_whiletrailers{$linenr} && + defined($stat) && defined($cond) && + $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { + my ($s, $c) = ($stat, $cond); + my $fixed_assign_in_if = 0; + + if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + if (ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr) && + $fix && $perl_version_ok) { + if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { + my $space = $1; + my $not = $2; + my $statement = $3; + my $assigned = $4; + my $test = $8; + my $against = $9; + my $brace = $15; + fix_delete_line($fixlinenr, $rawline); + fix_insert_line($fixlinenr, "$space$statement;"); + my $newline = "${space}if ("; + $newline .= '!' if defined($not); + $newline .= '(' if (defined $not && defined($test) && defined($against)); + $newline .= "$assigned"; + $newline .= " $test $against" if (defined($test) && defined($against)); + $newline .= ')' if (defined $not && defined($test) && defined($against)); + $newline .= ')'; + $newline .= " {" if (defined($brace)); + fix_insert_line($fixlinenr + 1, $newline); + $fixed_assign_in_if = 1; + } + } + } + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + $s =~ s/$;//g; # Remove any comments + if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && + $c !~ /}\s*while\s*/) + { + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + my $stat_real = ''; + + $stat_real = raw_line($linenr, $cond_lines) + . "\n" if ($cond_lines); + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + if (ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr . $stat_real) && + !$fixed_assign_in_if && + $cond_lines == 0 && + $fix && $perl_version_ok && + $fixed[$fixlinenr] =~ /^\+(\s*)((?:if|while|for)\s*$balanced_parens)\s*(.*)$/) { + my $indent = $1; + my $test = $2; + my $rest = rtrim($4); + if ($rest =~ /;$/) { + $fixed[$fixlinenr] = "\+$indent$test"; + fix_insert_line($fixlinenr + 1, "$indent\t$rest"); + } + } + } + } + +# Check for bitwise tests written as boolean + if ($line =~ / + (?: + (?:\[|\(|\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\|) + | + (?:\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\||\)|\]) + )/x) + { + WARN("HEXADECIMAL_BOOLEAN_TEST", + "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); + } + +# if and else should not have general statements after it + if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { + my $s = $1; + $s =~ s/$;//g; # Remove any comments + if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + } +# if should not continue a brace + if ($line =~ /}\s*if\b/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line (or did you mean 'else if'?)\n" . + $herecurr); + } +# case and default should not have general statements after them + if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && + $line !~ /\G(?: + (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| + \s*return\s+ + )/xg) + { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + + # Check for }<nl>else {, these must be at the same + # indent level to be relevant to each other. + if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && + $previndent == $indent) { + if (ERROR("ELSE_AFTER_BRACE", + "else should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/}\s*$//; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)else/$1} else/; + fix_insert_line($fixlinenr, $fixedline); + } + } + + if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && + $previndent == $indent) { + my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + + if ($s =~ /^\s*;/) { + if (ERROR("WHILE_AFTER_BRACE", + "while should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + my $trailing = $rawline; + $trailing =~ s/^\+//; + $trailing = trim($trailing); + $fixedline =~ s/}\s*$/} $trailing/; + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +#Specific variable tests + while ($line =~ m{($Constant|$Lval)}g) { + my $var = $1; + +#CamelCase + if ($var !~ /^$Constant$/ && + $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore some autogenerated defines and enum values + $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && +#Ignore Page<foo> variants + $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && +#Ignore ETHTOOL_LINK_MODE_<foo> variants + $var !~ /^ETHTOOL_LINK_MODE_/ && +#Ignore SI style variants like nS, mV and dB +#(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) + $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && +#Ignore some three character SI units explicitly, like MiB and KHz + $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { + while ($var =~ m{\b($Ident)}g) { + my $word = $1; + next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); + if ($check) { + seed_camelcase_includes(); + if (!$file && !$camelcase_file_seeded) { + seed_camelcase_file($realfile); + $camelcase_file_seeded = 1; + } + } + if (!defined $camelcase{$word}) { + $camelcase{$word} = 1; + CHK("CAMELCASE", + "Avoid CamelCase: <$word>\n" . $herecurr); + } + } + } + } + +#no spaces allowed after \ in define + if ($line =~ /\#\s*define.*\\\s+$/) { + if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", + "Whitespace after \\ makes next lines useless\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + } + +# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes +# itself <asm/foo.h> (uses RAW line) + if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { + my $file = "$1.h"; + my $checkfile = "include/linux/$file"; + if (-f "$root/$checkfile" && + $realfile ne $checkfile && + $1 !~ /$allowed_asm_includes/) + { + my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; + if ($asminclude > 0) { + if ($realfile =~ m{^arch/}) { + CHK("ARCH_INCLUDE_LINUX", + "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } else { + WARN("INCLUDE_LINUX", + "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } + } + } + } + +# multi-statement macros should be enclosed in a do while loop, grab the +# first statement and ensure its the whole macro if its not enclosed +# in a known good container + if ($realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + my $has_flow_statement = 0; + my $has_arg_concat = 0; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; + #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; + + $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); + $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); + + $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; + my $define_args = $1; + my $define_stmt = $dstat; + my @def_args = (); + + if (defined $define_args && $define_args ne "") { + $define_args = substr($define_args, 1, length($define_args) - 2); + $define_args =~ s/\s*//g; + $define_args =~ s/\\\+?//g; + @def_args = split(",", $define_args); + } + + $dstat =~ s/$;//g; + $dstat =~ s/\\\n.//g; + $dstat =~ s/^\s*//s; + $dstat =~ s/\s*$//s; + + # Flatten any parentheses and braces + while ($dstat =~ s/\([^\(\)]*\)/1u/ || + $dstat =~ s/\{[^\{\}]*\}/1u/ || + $dstat =~ s/.\[[^\[\]]*\]/1u/) + { + } + + # Flatten any obvious string concatenation. + while ($dstat =~ s/($String)\s*$Ident/$1/ || + $dstat =~ s/$Ident\s*($String)/$1/) + { + } + + # Make asm volatile uses seem like a generic function + $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; + + my $exceptions = qr{ + $Declare| + module_param_named| + MODULE_PARM_DESC| + DECLARE_PER_CPU| + DEFINE_PER_CPU| + __typeof__\(| + union| + struct| + \.$Ident\s*=\s*| + ^\"|\"$| + ^\[ + }x; + #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; + + $ctx =~ s/\n*$//; + my $stmt_cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $stmt_cnt, $here); + + if ($dstat ne '' && + $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), + $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); + $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz + $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants + $dstat !~ /$exceptions/ && + $dstat !~ /^\.$Ident\s*=/ && # .foo = + $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo + $dstat !~ /^case\b/ && # case ... + $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} + $dstat !~ /^for\s*$Constant$/ && # for (...) + $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() + $dstat !~ /^do\s*{/ && # do {... + $dstat !~ /^\(\{/ && # ({... + $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) + { + if ($dstat =~ /^\s*if\b/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); + } elsif ($dstat =~ /;/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); + } else { + ERROR("COMPLEX_MACRO", + "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); + } + + } + + # Make $define_stmt single line, comment-free, etc + my @stmt_array = split('\n', $define_stmt); + my $first = 1; + $define_stmt = ""; + foreach my $l (@stmt_array) { + $l =~ s/\\$//; + if ($first) { + $define_stmt = $l; + $first = 0; + } elsif ($l =~ /^[\+ ]/) { + $define_stmt .= substr($l, 1); + } + } + $define_stmt =~ s/$;//g; + $define_stmt =~ s/\s+/ /g; + $define_stmt = trim($define_stmt); + +# check if any macro arguments are reused (ignore '...' and 'type') + foreach my $arg (@def_args) { + next if ($arg =~ /\.\.\./); + next if ($arg =~ /^type$/i); + my $tmp_stmt = $define_stmt; + $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\#+\s*$arg\b//g; + $tmp_stmt =~ s/\b$arg\s*\#\#//g; + my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; + if ($use_cnt > 1) { + CHK("MACRO_ARG_REUSE", + "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); + } +# check if any macro arguments may have other precedence issues + if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && + ((defined($1) && $1 ne ',') || + (defined($2) && $2 ne ','))) { + CHK("MACRO_ARG_PRECEDENCE", + "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); + } + +# check if this is an unused argument + if ($define_stmt !~ /\b$arg\b/) { + WARN("MACRO_ARG_UNUSED", + "Argument '$arg' is not used in function-like macro\n" . "$herectx"); + } + } + +# check for macros with flow control, but without ## concatenation +# ## concatenation is commonly a macro that defines a function so ignore those + if ($has_flow_statement && !$has_arg_concat) { + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("MACRO_WITH_FLOW_CONTROL", + "Macros with flow control statements should be avoided\n" . "$herectx"); + } + +# check for line continuations outside of #defines, preprocessor #, and asm + + } elsif ($realfile =~ m@/vmlinux.lds.h$@) { + $line =~ s/(\w+)/$maybe_linker_symbol{$1}++/ge; + #print "REAL: $realfile\nln: $line\nkeys:", sort keys %maybe_linker_symbol; + } else { + if ($prevline !~ /^..*\\$/ && + $line !~ /^\+\s*\#.*\\$/ && # preprocessor + $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm + $line =~ /^\+.*\\$/) { + WARN("LINE_CONTINUATIONS", + "Avoid unnecessary line continuations\n" . $herecurr); + } + } + +# do {} while (0) macro tests: +# single-statement macros do not need to be enclosed in do while (0) loop, +# macro should not end with a semicolon + if ($perl_version_ok && + $realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + + $dstat =~ s/\\\n.//g; + $dstat =~ s/$;/ /g; + + if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { + my $stmts = $2; + my $semis = $3; + + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (($stmts =~ tr/;/;/) == 1 && + $stmts !~ /^\s*(if|while|for|switch)\b/) { + WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", + "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); + } + if (defined $semis && $semis ne "") { + WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", + "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); + } + } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("TRAILING_SEMICOLON", + "macros should not use a trailing semicolon\n" . "$herectx"); + } + } + +# check for redundant bracing round if etc + if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, 1); + #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; + #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; + if ($#chunks > 0 && $level == 0) { + my @allowed = (); + my $allow = 0; + my $seen = 0; + my $herectx = $here . "\n"; + my $ln = $linenr - 1; + for my $chunk (@chunks) { + my ($cond, $block) = @{$chunk}; + + # If the condition carries leading newlines, then count those as offsets. + my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = statement_rawlines($whitespace) - 1; + + $allowed[$allow] = 0; + #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; + + # We have looked at and allowed this specific line. + $suppress_ifbraces{$ln + $offset} = 1; + + $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; + $ln += statement_rawlines($block) - 1; + + substr($block, 0, length($cond), ''); + + $seen++ if ($block =~ /^\s*{/); + + #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed[$allow] = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed[$allow] = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed[$allow] = 1; + } + $allow++; + } + if ($seen) { + my $sum_allowed = 0; + foreach (@allowed) { + $sum_allowed += $_; + } + if ($sum_allowed == 0) { + WARN("BRACES", + "braces {} are not necessary for any arm of this statement\n" . $herectx); + } elsif ($sum_allowed != $allow && + $seen != $allow) { + CHK("BRACES", + "braces {} should be used on all arms of this statement\n" . $herectx); + } + } + } + } + if (!defined $suppress_ifbraces{$linenr - 1} && + $line =~ /\b(if|while|for|else)\b/) { + my $allowed = 0; + + # Check the pre-context. + if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { + #print "APW: ALLOWED: pre<$1>\n"; + $allowed = 1; + } + + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, $-[0]); + + # Check the condition. + my ($cond, $block) = @{$chunks[0]}; + #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed = 1; + } + # Check the post-context. + if (defined $chunks[1]) { + my ($cond, $block) = @{$chunks[1]}; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if ($block =~ /^\s*\{/) { + #print "APW: ALLOWED: chunk-1 block<$block>\n"; + $allowed = 1; + } + } + if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { + my $cnt = statement_rawlines($block); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("BRACES", + "braces {} are not necessary for single statement blocks\n" . $herectx); + } + } + +# check for single line unbalanced braces + if ($sline =~ /^.\s*\}\s*else\s*$/ || + $sline =~ /^.\s*else\s*\{\s*$/) { + CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); + } + +# check for unnecessary blank lines around braces + if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + } + } + if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# no volatiles please + my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; + if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); + } + +# Check for user-visible strings broken across lines, which breaks the ability +# to grep for the string. Make exceptions when the previous string ends in a +# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' +# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value + if ($line =~ /^\+\s*$String/ && + $prevline =~ /"\s*$/ && + $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { + if (WARN("SPLIT_STRING", + "quoted string split across lines\n" . $hereprev) && + $fix && + $prevrawline =~ /^\+.*"\s*$/ && + $last_coalesced_string_linenr != $linenr - 1) { + my $extracted_string = get_quoted_string($line, $rawline); + my $comma_close = ""; + if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { + $comma_close = $1; + } + + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/"\s*$//; + $fixedline .= substr($extracted_string, 1) . trim($comma_close); + fix_insert_line($fixlinenr - 1, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; + if ($fixedline !~ /\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $last_coalesced_string_linenr = $linenr; + } + } + +# check for missing a space in a string concatenation + if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { + WARN('MISSING_SPACE', + "break quoted strings at a space character\n" . $hereprev); + } + +# check for an embedded function name in a string when the function is known +# This does not work very well for -f --file checking as it depends on patch +# context providing the function name or a single line form for in-file +# function declarations + if ($line =~ /^\+.*$String/ && + defined($context_function) && + get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && + length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { + WARN("EMBEDDED_FUNCTION_NAME", + "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); + } + +# check for unnecessary function tracing like uses +# This does not use $logFunctions because there are many instances like +# 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions + if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { + if (WARN("TRACING_LOGGING", + "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# check for spaces before a quoted newline + if ($rawline =~ /^.*\".*\s\\n/) { + if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", + "unnecessary whitespace before a quoted newline\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; + } + + } + +# concatenated string without spaces between elements + if ($line =~ /$String[A-Z_]/ || + ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { + if (CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr) && + $fix) { + while ($line =~ /($String)/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; + $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; + } + } + } + +# uncoalesced string fragments + if ($line =~ /$String\s*[Lu]?"/) { + if (WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr) && + $fix) { + while ($line =~ /($String)(?=\s*")/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; + } + } + } + +# check for non-standard and hex prefixed decimal printf formats + my $show_L = 1; #don't show the same defect twice + my $show_Z = 1; + while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { + my $string = substr($rawline, $-[1], $+[1] - $-[1]); + $string =~ s/%%/__/g; + # check for %L + if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { + WARN("PRINTF_L", + "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); + $show_L = 0; + } + # check for %Z + if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { + WARN("PRINTF_Z", + "%Z$1 is non-standard C, use %z$1\n" . $herecurr); + $show_Z = 0; + } + # check for 0x<decimal> + if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { + ERROR("PRINTF_0XDECIMAL", + "Prefixing 0x with decimal output is defective\n" . $herecurr); + } + } + +# check for line continuations in quoted strings with odd counts of " + if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { + WARN("LINE_CONTINUATIONS", + "Avoid line continuations in quoted strings\n" . $herecurr); + } + +# warn about #if 0 + if ($line =~ /^.\s*\#\s*if\s+0\b/) { + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); + } + +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" . $herecurr); + } + +# check for needless "if (<foo>) fn(<foo>)" uses + if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { + my $tested = quotemeta($1); + my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; + if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { + my $func = $1; + if (WARN('NEEDLESS_IF', + "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && + $fix) { + my $do_fix = 1; + my $leading_tabs = ""; + my $new_leading_tabs = ""; + if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { + $leading_tabs = $1; + } else { + $do_fix = 0; + } + if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { + $new_leading_tabs = $1; + if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { + $do_fix = 0; + } + } else { + $do_fix = 0; + } + if ($do_fix) { + fix_delete_line($fixlinenr - 1, $prevrawline); + $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; + } + } + } + } + +# check for unnecessary "Out of Memory" messages + if ($line =~ /^\+.*\b$logFunctions\s*\(/ && + $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && + (defined $1 || defined $3) && + $linenr > 3) { + my $testval = $2; + my $testline = $lines[$linenr - 3]; + + my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); +# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); + + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && + $s !~ /\b__GFP_NOWARN\b/ ) { + WARN("OOM_MESSAGE", + "Possible unnecessary 'out of memory' message\n" . $hereprev); + } + } + +# check for logging functions with KERN_<LEVEL> + if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && + $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { + my $level = $1; + if (WARN("UNNECESSARY_KERN_LEVEL", + "Possible unnecessary $level\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s*$level\s*//; + } + } + +# check for logging continuations + if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { + WARN("LOGGING_CONTINUATION", + "Avoid logging continuation uses where feasible\n" . $herecurr); + } + +# check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions + if (defined $stat && + $line =~ /\b$logFunctions\s*\(/ && + index($stat, '"') >= 0) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + pos($stat_real) = index($stat_real, '"'); + while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { + my $pspec = $1; + my $h = $2; + my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; + if (WARN("UNNECESSARY_MODIFIER", + "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && + $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { + my $nspec = $pspec; + $nspec =~ s/h//g; + $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; + } + } + } + +# check for mask then right shift without a parentheses + if ($perl_version_ok && + $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && + $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so + WARN("MASK_THEN_SHIFT", + "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); + } + +# check for pointer comparisons to NULL + if ($perl_version_ok) { + while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { + my $val = $1; + my $equal = "!"; + $equal = "" if ($4 eq "!="); + if (CHK("COMPARISON_TO_NULL", + "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; + } + } + } + +# check for bad placement of section $InitAttribute (e.g.: __initdata) + if ($line =~ /(\b$InitAttribute\b)/) { + my $attr = $1; + if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { + my $ptr = $1; + my $var = $2; + if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && + ERROR("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr)) || + ($ptr !~ /\b(union|struct)\s+$attr\b/ && + WARN("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr))) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; + } + } + } + +# check for $InitAttributeData (ie: __initdata) with const + if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { + my $attr = $1; + $attr =~ /($InitAttributePrefix)(.*)/; + my $attr_prefix = $1; + my $attr_type = $2; + if (ERROR("INIT_ATTRIBUTE", + "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/$InitAttributeData/${attr_prefix}initconst/; + } + } + +# check for $InitAttributeConst (ie: __initconst) without const + if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { + my $attr = $1; + if (ERROR("INIT_ATTRIBUTE", + "Use of $attr requires a separate use of const\n" . $herecurr) && + $fix) { + my $lead = $fixed[$fixlinenr] =~ + /(^\+\s*(?:static\s+))/; + $lead = rtrim($1); + $lead = "$lead " if ($lead !~ /^\+$/); + $lead = "${lead}const "; + $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; + } + } + +# check for __read_mostly with const non-pointer (should just be const) + if ($line =~ /\b__read_mostly\b/ && + $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { + if (ERROR("CONST_READ_MOSTLY", + "Invalid use of __read_mostly with const type\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; + } + } + +# don't use __constant_<foo> functions outside of include/uapi/ + if ($realfile !~ m@^include/uapi/@ && + $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { + my $constant_func = $1; + my $func = $constant_func; + $func =~ s/^__constant_//; + if (WARN("CONSTANT_CONVERSION", + "$constant_func should be $func\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; + } + } + +# prefer usleep_range over udelay + if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + my $delay = $1; + # ignore udelay's < 10, however + if (! ($delay < 10) ) { + CHK("USLEEP_RANGE", + "usleep_range is preferred over udelay; see function description of usleep_range() and udelay().\n" . $herecurr); + } + if ($delay > 2000) { + WARN("LONG_UDELAY", + "long udelay - prefer mdelay; see function description of mdelay().\n" . $herecurr); + } + } + +# warn about unexpectedly long msleep's + if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if ($1 < 20) { + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); + } + } + +# check for comparisons of jiffies + if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); + } + +# check for comparisons of get_jiffies_64() + if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { + WARN("JIFFIES_COMPARISON", + "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); + } + +# warn about #ifdefs in C files +# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { +# print "#ifdef in C files should be avoided\n"; +# print "$herecurr"; +# $clean = 0; +# } + +# warn about spacing in #ifdefs + if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { + if (ERROR("SPACING", + "exactly one space required after that #$1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; + } + + } + +# check for spinlock_t definitions without a comment. + if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || + $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { + my $which = $1; + if (!ctx_has_comment($first_line, $linenr)) { + CHK("UNCOMMENTED_DEFINITION", + "$1 definition without comment\n" . $herecurr); + } + } +# check for memory barriers without a comment. + + my $barriers = qr{ + mb| + rmb| + wmb + }x; + my $barrier_stems = qr{ + mb__before_atomic| + mb__after_atomic| + store_release| + load_acquire| + store_mb| + (?:$barriers) + }x; + my $all_barriers = qr{ + (?:$barriers)| + smp_(?:$barrier_stems)| + virt_(?:$barrier_stems) + }x; + + if ($line =~ /\b(?:$all_barriers)\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("MEMORY_BARRIER", + "memory barrier without comment\n" . $herecurr); + } + } + + my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; + + if ($realfile !~ m@^include/asm-generic/@ && + $realfile !~ m@/barrier\.h$@ && + $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && + $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { + WARN("MEMORY_BARRIER", + "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); + } + +# check for waitqueue_active without a comment. + if ($line =~ /\bwaitqueue_active\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("WAITQUEUE_ACTIVE", + "waitqueue_active without comment\n" . $herecurr); + } + } + +# check for data_race without a comment. + if ($line =~ /\bdata_race\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("DATA_RACE", + "data_race without comment\n" . $herecurr); + } + } + +# check of hardware specific defines + if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { + CHK("ARCH_DEFINES", + "architecture specific defines should be avoided\n" . $herecurr); + } + +# check that the storage class is not after a type + if ($line =~ /\b($Type)\s+($Storage)\b/) { + WARN("STORAGE_CLASS", + "storage class '$2' should be located before type '$1'\n" . $herecurr); + } +# Check that the storage class is at the beginning of a declaration + if ($line =~ /\b$Storage\b/ && + $line !~ /^.\s*$Storage/ && + $line =~ /^.\s*(.+?)\$Storage\s/ && + $1 !~ /[\,\)]\s*$/) { + WARN("STORAGE_CLASS", + "storage class should be at the beginning of the declaration\n" . $herecurr); + } + +# check the location of the inline attribute, that it is between +# storage class and type. + if ($line =~ /\b$Type\s+$Inline\b/ || + $line =~ /\b$Inline\s+$Storage\b/) { + ERROR("INLINE_LOCATION", + "inline keyword should sit between storage class and type\n" . $herecurr); + } + +# Check for __inline__ and __inline, prefer inline + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b(__inline__|__inline)\b/) { + if (WARN("INLINE", + "plain inline is preferred over $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; + + } + } + +# Check for compiler attributes + if ($realfile !~ m@\binclude/uapi/@ && + $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { + my $attr = $1; + $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; + + my %attr_list = ( + "alias" => "__alias", + "aligned" => "__aligned", + "always_inline" => "__always_inline", + "assume_aligned" => "__assume_aligned", + "cold" => "__cold", + "const" => "__attribute_const__", + "copy" => "__copy", + "designated_init" => "__designated_init", + "externally_visible" => "__visible", + "format" => "printf|scanf", + "gnu_inline" => "__gnu_inline", + "malloc" => "__malloc", + "mode" => "__mode", + "no_caller_saved_registers" => "__no_caller_saved_registers", + "noclone" => "__noclone", + "noinline" => "noinline", + "nonstring" => "__nonstring", + "noreturn" => "__noreturn", + "packed" => "__packed", + "pure" => "__pure", + "section" => "__section", + "used" => "__used", + "weak" => "__weak" + ); + + while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { + my $orig_attr = $1; + my $params = ''; + $params = $2 if defined($2); + my $curr_attr = $orig_attr; + $curr_attr =~ s/^[\s_]+|[\s_]+$//g; + if (exists($attr_list{$curr_attr})) { + my $new = $attr_list{$curr_attr}; + if ($curr_attr eq "format" && $params) { + $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; + $new = "__$1\($2"; + } else { + $new = "$new$params"; + } + if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && + $fix) { + my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; + $fixed[$fixlinenr] =~ s/$remove//; + $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; + $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; + $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; + } + } + } + + # Check for __attribute__ unused, prefer __always_unused or __maybe_unused + if ($attr =~ /^_*unused/) { + WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); + } + } + +# Check for __attribute__ weak, or __weak declarations (may have link issues) + if ($perl_version_ok && + $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && + ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || + $line =~ /\b__weak\b/)) { + ERROR("WEAK_DECLARATION", + "Using weak declarations can have unintended link defects\n" . $herecurr); + } + +# check for c99 types like uint8_t used outside of uapi/ and tools/ + if ($realfile !~ m@\binclude/uapi/@ && + $realfile !~ m@\btools/@ && + $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { + my $type = $1; + if ($type =~ /\b($typeC99Typedefs)\b/) { + $type = $1; + my $kernel_type = 'u'; + $kernel_type = 's' if ($type =~ /^_*[si]/); + $type =~ /(\d+)/; + $kernel_type .= $1; + if (CHK("PREFER_KERNEL_TYPES", + "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; + } + } + } + +# check for cast of C90 native int or longer types constants + if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { + my $cast = $1; + my $const = $2; + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } + if (WARN("TYPECAST_INT_CONSTANT", + "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; + } + } + +# check for sizeof(&) + if ($line =~ /\bsizeof\s*\(\s*\&/) { + WARN("SIZEOF_ADDRESS", + "sizeof(& should be avoided\n" . $herecurr); + } + +# check for sizeof without parenthesis + if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { + if (WARN("SIZEOF_PARENTHESIS", + "sizeof $1 should be sizeof($1)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; + } + } + +# check for struct spinlock declarations + if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" . $herecurr); + } + +# check for seq_printf uses that could be seq_puts + if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { + my $fmt = get_quoted_string($line, $rawline); + $fmt =~ s/%%//g; + if ($fmt !~ /%/) { + if (WARN("PREFER_SEQ_PUTS", + "Prefer seq_puts to seq_printf\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; + } + } + } + +# check for vsprintf extension %p<foo> misuses + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && + $1 !~ /^_*volatile_*$/) { + my $stat_real; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + for (my $count = $linenr; $count <= $lc; $count++) { + my $specifier; + my $extension; + my $qualifier; + my $bad_specifier = ""; + my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); + $fmt =~ s/%%//g; + + while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { + $specifier = $1; + $extension = $2; + $qualifier = $3; + if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || + ($extension eq "f" && + defined $qualifier && $qualifier !~ /^w/) || + ($extension eq "4" && + defined $qualifier && $qualifier !~ /^cc/)) { + $bad_specifier = $specifier; + last; + } + if ($extension eq "x" && !defined($stat_real)) { + if (!defined($stat_real)) { + $stat_real = get_stat_real($linenr, $lc); + } + WARN("VSPRINTF_SPECIFIER_PX", + "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); + } + } + if ($bad_specifier ne "") { + my $stat_real = get_stat_real($linenr, $lc); + my $msg_level = \&WARN; + my $ext_type = "Invalid"; + my $use = ""; + if ($bad_specifier =~ /p[Ff]/) { + $use = " - use %pS instead"; + $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); + } elsif ($bad_specifier =~ /pA/) { + $use = " - '%pA' is only intended to be used from Rust code"; + $msg_level = \&ERROR; + } + + &{$msg_level}("VSPRINTF_POINTER_EXTENSION", + "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); + } + } + } + +# Check for misused memsets + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { + + my $ms_addr = $2; + my $ms_val = $7; + my $ms_size = $12; + + if ($ms_size =~ /^(0x|)0$/i) { + ERROR("MEMSET", + "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); + } elsif ($ms_size =~ /^(0x|)1$/i) { + WARN("MEMSET", + "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); + } + } + +# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# if (WARN("PREFER_ETHER_ADDR_COPY", +# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; +# } +# } + +# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# WARN("PREFER_ETHER_ADDR_EQUAL", +# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") +# } + +# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr +# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# +# my $ms_val = $7; +# +# if ($ms_val =~ /^(?:0x|)0+$/i) { +# if (WARN("PREFER_ETH_ZERO_ADDR", +# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; +# } +# } elsif ($ms_val =~ /^(?:0xff|255)$/i) { +# if (WARN("PREFER_ETH_BROADCAST_ADDR", +# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; +# } +# } +# } + +# strcpy uses that should likely be strscpy + if ($line =~ /\bstrcpy\s*\(/) { + WARN("STRCPY", + "Prefer strscpy over strcpy - see: https://github.com/KSPP/linux/issues/88\n" . $herecurr); + } + +# strlcpy uses that should likely be strscpy + if ($line =~ /\bstrlcpy\s*\(/) { + WARN("STRLCPY", + "Prefer strscpy over strlcpy - see: https://github.com/KSPP/linux/issues/89\n" . $herecurr); + } + +# strncpy uses that should likely be strscpy or strscpy_pad + if ($line =~ /\bstrncpy\s*\(/) { + WARN("STRNCPY", + "Prefer strscpy, strscpy_pad, or __nonstring over strncpy - see: https://github.com/KSPP/linux/issues/90\n" . $herecurr); + } + +# ethtool_sprintf uses that should likely be ethtool_puts + if ($line =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with only two arguments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + # use $rawline because $line loses %s via sanitization and thus we can't match against it. + if ($rawline =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*\"\%s\"\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with standalone \"%s\" specifier\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*"\%s"\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + +# typecasts on min/max could be min_t/max_t + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (defined $2 || defined $7) { + my $call = $1; + my $cast1 = deparenthesize($2); + my $arg1 = $3; + my $cast2 = deparenthesize($7); + my $arg2 = $8; + my $cast; + + if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { + $cast = "$cast1 or $cast2"; + } elsif ($cast1 ne "") { + $cast = $cast1; + } else { + $cast = $cast2; + } + WARN("MINMAX", + "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); + } + } + +# check usleep_range arguments + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { + my $min = $1; + my $max = $7; + if ($min eq $max) { + WARN("USLEEP_RANGE", + "usleep_range should not use min == max args; see function description of usleep_range().\n" . "$here\n$stat\n"); + } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && + $min > $max) { + WARN("USLEEP_RANGE", + "usleep_range args reversed, use min then max; see function description of usleep_range().\n" . "$here\n$stat\n"); + } + } + +# check for naked sscanf + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/ && + ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && + $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && + $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + WARN("NAKED_SSCANF", + "unchecked sscanf return value\n" . "$here\n$stat_real\n"); + } + +# check for simple sscanf that should be kstrto<foo> + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { + my $format = $6; + my $count = $format =~ tr@%@%@; + if ($count == 1 && + $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { + WARN("SSCANF_TO_KSTRTO", + "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); + } + } + } + +# check for new externs in .h files. + if ($realfile =~ /\.h$/ && + $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { + if (CHK("AVOID_EXTERNS", + "extern prototypes should be avoided in .h files\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; + } + } + +# check for new externs in .c files. + if ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) + { + my $function_name = $1; + my $paren_space = $2; + + my $s = $stat; + if (defined $cond) { + substr($s, 0, length($cond), ''); + } + if ($s =~ /^\s*;/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + + if ($paren_space =~ /\n/) { + WARN("FUNCTION_ARGUMENTS", + "arguments for function declarations should follow identifier\n" . $herecurr); + } + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^\+extern struct\s+(\w+)\s+(\w+)\[\];/) + { + my ($st_type, $st_name) = ($1, $2); + + for my $s (keys %maybe_linker_symbol) { + #print "Linker symbol? $st_name : $s\n"; + goto LIKELY_LINKER_SYMBOL + if $st_name =~ /$s/; + } + WARN("AVOID_EXTERNS", + "found a file-scoped extern type:$st_type name:$st_name in .c file\n" + . "is this a linker symbol ?\n" . $herecurr); + LIKELY_LINKER_SYMBOL: + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*extern\s+/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + +# check for function declarations that have arguments without identifier names + if (defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && + $1 ne "void") { + my $args = trim($1); + while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { + my $arg = trim($1); + if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { + WARN("FUNCTION_ARGUMENTS", + "function definition argument '$arg' should also have an identifier name\n" . $herecurr); + } + } + } + +# check for function definitions + if ($perl_version_ok && + defined $stat && + $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { + $context_function = $1; + +# check for multiline function definition with misplaced open brace + my $ok = 0; + my $cnt = statement_rawlines($stat); + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + $ok = 1 if ($rl =~ /^[ \+]\{/); + $ok = 1 if ($rl =~ /\{/ && $n == 0); + last if $rl =~ /^[ \+].*\{/; + } + if (!$ok) { + ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herectx); + } + } + +# checks for new __setup's + if ($rawline =~ /\b__setup\("([^"]*)"/) { + my $name = $1; + + if (!grep(/$name/, @setup_docs)) { + CHK("UNDOCUMENTED_SETUP", + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); + } + } + +# check for pointless casting of alloc functions + if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { + WARN("UNNECESSARY_CASTS", + "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); + } + +# alloc style +# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + CHK("ALLOC_SIZEOF_STRUCT", + "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); + } + +# check for (kv|k)[mz]alloc with multiplies that could be kmalloc_array/kvmalloc_array/kvcalloc/kcalloc + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $a2 = $10; + my $newfunc = "kmalloc_array"; + $newfunc = "kvmalloc_array" if ($oldfunc eq "kvmalloc"); + $newfunc = "kvcalloc" if ($oldfunc eq "kvzalloc"); + $newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); + my $r1 = $a1; + my $r2 = $a2; + if ($a1 =~ /^sizeof\s*\S/) { + $r1 = $a2; + $r2 = $a1; + } + if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && + !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_MULTIPLY", + "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && + $cnt == 1 && + $fix) { + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; + } + } + } + +# check for krealloc arg reuse + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && + $1 eq $3) { + WARN("KREALLOC_ARG_REUSE", + "Reusing the krealloc arg is almost always a bug\n" . $herecurr); + } + +# check for alloc argument mismatch + if ($line =~ /\b((?:devm_)?((?:k|kv)?(calloc|malloc_array)(?:_node)?))\s*\(\s*sizeof\b/) { + WARN("ALLOC_ARRAY_ARGS", + "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); + } + +# check for multiple semicolons + if ($line =~ /;\s*;\s*$/) { + if (WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; + } + } + +# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi + if ($realfile !~ m@^include/uapi/@ && + $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { + my $ull = ""; + $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); + if (CHK("BIT_MACRO", + "Prefer using the BIT$ull macro\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; + } + } + +# check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) + if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { + WARN("IS_ENABLED_CONFIG", + "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); + } + +# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + my $config = $1; + if (WARN("PREFER_IS_ENABLED", + "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; + } + } + +# check for /* fallthrough */ like comment, prefer fallthrough; + my @fallthroughs = ( + 'fallthrough', + '@fallthrough@', + 'lint -fallthrough[ \t]*', + 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', + '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', + 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + ); + if ($raw_comment ne '') { + foreach my $ft (@fallthroughs) { + if ($raw_comment =~ /$ft/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("PREFER_FALLTHROUGH", + "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); + last; + } + } + } + +# check for switch/default statements without a break; + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("DEFAULT_NO_BREAK", + "switch default: should use break\n" . $herectx); + } + +# check for gcc specific __FUNCTION__ + if ($line =~ /\b__FUNCTION__\b/) { + if (WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; + } + } + +# check for uses of __DATE__, __TIME__, __TIMESTAMP__ + while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { + ERROR("DATE_TIME", + "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); + } + +# check for use of yield() + if ($line =~ /\byield\s*\(\s*\)/) { + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); + } + +# check for comparisons against true and false + if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { + my $lead = $1; + my $arg = $2; + my $test = $3; + my $otype = $4; + my $trail = $5; + my $op = "!"; + + ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); + + my $type = lc($otype); + if ($type =~ /^(?:true|false)$/) { + if (("$test" eq "==" && "$type" eq "true") || + ("$test" eq "!=" && "$type" eq "false")) { + $op = ""; + } + + CHK("BOOL_COMPARISON", + "Using comparison to $otype is error prone\n" . $herecurr); + +## maybe suggesting a correct construct would better +## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); + + } + } + +# check for semaphores initialized locked + if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { + WARN("CONSIDER_COMPLETION", + "consider using a completion\n" . $herecurr); + } + +# recommend kstrto* over simple_strto* and strict_strto* + if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { + WARN("CONSIDER_KSTRTO", + "$1 is obsolete, use k$3 instead\n" . $herecurr); + } + +# check for __initcall(), use device_initcall() explicitly or more appropriate function please + if ($line =~ /^.\s*__initcall\s*\(/) { + WARN("USE_DEVICE_INITCALL", + "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); + } + +# check for spin_is_locked(), suggest lockdep instead + if ($line =~ /\bspin_is_locked\(/) { + WARN("USE_LOCKDEP", + "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); + } + +# check for deprecated apis + if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { + my $deprecated_api = $1; + my $new_api = $deprecated_apis{$deprecated_api}; + WARN("DEPRECATED_API", + "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); + } + +# check for various structs that are normally const (ops, kgdb, device_tree) +# and avoid what seem like struct definitions 'struct foo {' + if (defined($const_structs) && + $line !~ /\bconst\b/ && + $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { + WARN("CONST_STRUCT", + "struct $1 should normally be const\n" . $herecurr); + } + +# use of NR_CPUS is usually wrong +# ignore definitions of NR_CPUS and usage to define arrays as likely right +# ignore designated initializers using NR_CPUS + if ($line =~ /\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && + $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) + { + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); + } + +# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. + if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { + ERROR("DEFINE_ARCH_HAS", + "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); + } + +# likely/unlikely comparisons similar to "(likely(foo) > 0)" + if ($perl_version_ok && + $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { + WARN("LIKELY_MISUSE", + "Using $1 should generally have parentheses around the comparison\n" . $herecurr); + } + +# return sysfs_emit(foo, fmt, ...) fmt without newline + if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && + substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { + my $offset = $+[6] - 1; + if (WARN("SYSFS_EMIT", + "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && + $fix) { + substr($fixed[$fixlinenr], $offset, 0) = '\\n'; + } + } + +# check for array definition/declarations that should use flexible arrays instead + if ($sline =~ /^[\+ ]\s*\}(?:\s*__packed)?\s*;\s*$/ && + $prevline =~ /^\+\s*(?:\}(?:\s*__packed\s*)?|$Type)\s*$Ident\s*\[\s*(0|1)\s*\]\s*;\s*$/) { + if (ERROR("FLEXIBLE_ARRAY", + "Use C99 flexible arrays - see https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays\n" . $hereprev) && + $1 == '0' && $fix) { + $fixed[$fixlinenr - 1] =~ s/\[\s*0\s*\]/[]/; + } + } + +# nested likely/unlikely calls + if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { + WARN("LIKELY_MISUSE", + "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); + } + +# whine mightly about in_atomic + if ($line =~ /\bin_atomic\s*\(/) { + if ($realfile =~ m@^drivers/@) { + ERROR("IN_ATOMIC", + "do not use in_atomic in drivers\n" . $herecurr); + } elsif ($realfile !~ m@^kernel/@) { + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); + } + } + +# Complain about RCU Tasks Trace used outside of BPF (and of course, RCU). + our $rcu_trace_funcs = qr{(?x: + rcu_read_lock_trace | + rcu_read_lock_trace_held | + rcu_read_unlock_trace | + call_rcu_tasks_trace | + synchronize_rcu_tasks_trace | + rcu_barrier_tasks_trace | + rcu_request_urgent_qs_task + )}; + our $rcu_trace_paths = qr{(?x: + kernel/bpf/ | + include/linux/bpf | + net/bpf/ | + kernel/rcu/ | + include/linux/rcu + )}; + if ($line =~ /\b($rcu_trace_funcs)\s*\(/) { + if ($realfile !~ m{^$rcu_trace_paths}) { + WARN("RCU_TASKS_TRACE", + "use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr); + } + } + +# check for lockdep_set_novalidate_class + if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || + $line =~ /__lockdep_no_validate__\s*\)/ ) { + if ($realfile !~ m@^kernel/lockdep@ && + $realfile !~ m@^include/linux/lockdep@ && + $realfile !~ m@^drivers/base/core@) { + ERROR("LOCKDEP", + "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); + } + } + + if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || + $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { + WARN("EXPORTED_WORLD_WRITABLE", + "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); + } + +# check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> +# and whether or not function naming is typical and if +# DEVICE_ATTR permissions uses are unusual too + if ($perl_version_ok && + defined $stat && + $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { + my $var = $1; + my $perms = $2; + my $show = $3; + my $store = $4; + my $octal_perms = perms_to_octal($perms); + if ($show =~ /^${var}_show$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0644") { + if (WARN("DEVICE_ATTR_RW", + "Use DEVICE_ATTR_RW\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; + } + } elsif ($show =~ /^${var}_show$/ && + $store =~ /^NULL$/ && + $octal_perms eq "0444") { + if (WARN("DEVICE_ATTR_RO", + "Use DEVICE_ATTR_RO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; + } + } elsif ($show =~ /^NULL$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0200") { + if (WARN("DEVICE_ATTR_WO", + "Use DEVICE_ATTR_WO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; + } + } elsif ($octal_perms eq "0644" || + $octal_perms eq "0444" || + $octal_perms eq "0200") { + my $newshow = "$show"; + $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); + my $newstore = $store; + $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); + my $rename = ""; + if ($show ne $newshow) { + $rename .= " '$show' to '$newshow'"; + } + if ($store ne $newstore) { + $rename .= " '$store' to '$newstore'"; + } + WARN("DEVICE_ATTR_FUNCTIONS", + "Consider renaming function(s)$rename\n" . $herecurr); + } else { + WARN("DEVICE_ATTR_PERMS", + "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); + } + } + +# Mode permission misuses where it seems decimal should be octal +# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop +# o Ignore module_param*(...) uses with a decimal 0 permission as that has a +# specific definition of not visible in sysfs. +# o Ignore proc_create*(...) uses with a decimal 0 permission as that means +# use the default permissions + if ($perl_version_ok && + defined $stat && + $line =~ /$mode_perms_search/) { + foreach my $entry (@mode_permission_funcs) { + my $func = $entry->[0]; + my $arg_pos = $entry->[1]; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + + my $skip_args = ""; + if ($arg_pos > 1) { + $arg_pos--; + $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; + } + my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; + if ($stat =~ /$test/) { + my $val = $1; + $val = $6 if ($skip_args ne ""); + if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && + (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || + ($val =~ /^$Octal$/ && length($val) ne 4))) { + ERROR("NON_OCTAL_PERMISSIONS", + "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); + } + if ($val =~ /^$Octal$/ && (oct($val) & 02)) { + ERROR("EXPORTED_WORLD_WRITABLE", + "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); + } + } + } + } + +# check for uses of S_<PERMS> that could be octal for readability + while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { + my $oval = $1; + my $octal = perms_to_octal($oval); + if (WARN("SYMBOLIC_PERMS", + "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; + } + } + +# validate content of MODULE_LICENSE against list from include/linux/module.h + if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { + my $extracted_string = get_quoted_string($line, $rawline); + my $valid_licenses = qr{ + GPL| + GPL\ v2| + GPL\ and\ additional\ rights| + Dual\ BSD/GPL| + Dual\ MIT/GPL| + Dual\ MPL/GPL| + Proprietary + }x; + if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { + WARN("MODULE_LICENSE", + "unknown module license " . $extracted_string . "\n" . $herecurr); + } + if (!$file && $extracted_string eq '"GPL v2"') { + if (WARN("MODULE_LICENSE", + "Prefer \"GPL\" over \"GPL v2\" - see commit bf7fbeeae6db (\"module: Cure the MODULE_LICENSE \"GPL\" vs. \"GPL v2\" bogosity\")\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bMODULE_LICENSE\s*\(\s*"GPL v2"\s*\)/MODULE_LICENSE("GPL")/; + } + } + } + +# check for sysctl duplicate constants + if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { + WARN("DUPLICATED_SYSCTL_CONST", + "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); + } + } + + # If we have no input at all, then there is nothing to report on + # so just keep quiet. + if ($#rawlines == -1) { + exit(0); + } + + # In mailback mode only produce a report in the negative, for + # things that appear to be patches. + if ($mailback && ($clean == 1 || !$is_patch)) { + exit(0); + } + + # This is not a patch, and we are in 'no-patch' mode so + # just keep quiet. + if (!$chk_patch && !$is_patch) { + exit(0); + } + + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { + ERROR("NOT_UNIFIED_DIFF", + "Does not appear to be a unified-diff format patch\n"); + } + if ($is_patch && $has_commit_log && $chk_fixes_tag) { + if ($needs_fixes_tag ne "" && !$is_revert && !$fixes_tag) { + WARN("MISSING_FIXES_TAG", + "The commit message has '$needs_fixes_tag', perhaps it also needs a 'Fixes:' tag?\n"); + } + } + if ($is_patch && $has_commit_log && $chk_signoff) { + if ($signoff == 0) { + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n"); + } elsif ($authorsignoff != 1) { + # authorsignoff values: + # 0 -> missing sign off + # 1 -> sign off identical + # 2 -> names and addresses match, comments mismatch + # 3 -> addresses match, names different + # 4 -> names match, addresses different + # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match + + my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; + + if ($authorsignoff == 0) { + ERROR("NO_AUTHOR_SIGN_OFF", + "Missing Signed-off-by: line by nominal patch author '$author'\n"); + } elsif ($authorsignoff == 2) { + CHK("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 3) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email name mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 4) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email address mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 5) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); + } + } + } + + print report_dump(); + if ($summary && !($clean == 1 && $quiet == 1)) { + print "$filename " if ($summary_file); + print "total: $cnt_error errors, $cnt_warn warnings, " . + (($check)? "$cnt_chk checks, " : "") . + "$cnt_lines lines checked\n"; + } + + if ($quiet == 0) { + # If there were any defects found and not already fixing them + if (!$clean and !$fix) { + print << "EOM" + +NOTE: For some of the reported defects, checkpatch may be able to + mechanically convert to the typical style using --fix or --fix-inplace. +EOM + } + # If there were whitespace errors which cleanpatch can fix + # then suggest that. + if ($rpt_cleaners) { + $rpt_cleaners = 0; + print << "EOM" + +NOTE: Whitespace errors detected. + You may wish to use scripts/cleanpatch or scripts/cleanfile +EOM + } + } + + if ($clean == 0 && $fix && + ("@rawlines" ne "@fixed" || + $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { + my $newfile = $filename; + $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); + my $linecount = 0; + my $f; + + @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); + + open($f, '>', $newfile) + or die "$P: Can't open $newfile for write\n"; + foreach my $fixed_line (@fixed) { + $linecount++; + if ($file) { + if ($linecount > 3) { + $fixed_line =~ s/^\+//; + print $f $fixed_line . "\n"; + } + } else { + print $f $fixed_line . "\n"; + } + } + close($f); + + if (!$quiet) { + print << "EOM"; + +Wrote EXPERIMENTAL --fix correction(s) to '$newfile' + +Do _NOT_ trust the results written to this file. +Do _NOT_ submit these changes without inspecting them for correctness. + +This EXPERIMENTAL file is simply a convenience to help rewrite patches. +No warranties, expressed or implied... +EOM + } + } + + if ($quiet == 0) { + print "\n"; + if ($clean == 1) { + print "$vname has no obvious style problems and is ready for submission.\n"; + } else { + print "$vname has style problems, please review.\n"; + } + } + return $clean; +} diff --git a/dev-docs/extend-authors.sh b/dev-docs/extend-authors.sh new file mode 100755 index 0000000..30e7dff --- /dev/null +++ b/dev-docs/extend-authors.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Check if a starting tag is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 <starting_tag>" + echo "Example: $0 fuse-3.16.2" + exit 1 +fi + +START_TAG=$1 + +# Extract email addresses from git log +git_emails=$(git log ${START_TAG}..HEAD --format='<%aE>' | sort -u | sed 's/^<//;s/>$//') + +# Extract email addresses from AUTHORS file +authors_emails=$(grep -oP '(?<=<)[^>]+' AUTHORS | sort -u) + +# Find new email addresses (in git_emails but not in authors_emails) +# -1 suppresses lines unique to AUTHORS, -3 suppresses lines common to both +# Result: only lines unique to git_emails (i.e., new authors) +new_emails=$(comm -1 -3 <(echo "$authors_emails") <(echo "$git_emails")) + +# If there are new email addresses, add corresponding authors to the AUTHORS file +if [ -n "$new_emails" ]; then + echo -e "\nNew authors to be added:" + echo -e "\n# New authors since ${START_TAG}" >> AUTHORS + for email in $new_emails; do + author=$(git log -1 --format='%aN <%aE>' --author="$email") + echo "$author" + echo "$author" >> AUTHORS + done + echo "AUTHORS file has been updated." +else + echo "No new authors found since ${START_TAG}." +fi diff --git a/dev-docs/release-process.md b/dev-docs/release-process.md new file mode 100644 index 0000000..fe4c225 --- /dev/null +++ b/dev-docs/release-process.md @@ -0,0 +1,61 @@ +Release Process +=============== + +* `set TAG fuse-A.B.C` +* Update version in + * `ChangeLog.rst` + * `meson.build` + * `include/fuse_common.h` (`#define FUSE_{MINOR/MAJOR}_VERSION`) +* When creating new minor release: + * Create signing key for the next release: `P=fuse-<A.B+1> signify-openbsd -G -n -p signify/$P.pub -s + signify/$P.sec; git add signify/$P.pub` + * Expire old release signing keys (keep one around just in case) +* To update authors run : dev-docs/extend-authors.sh +* `git commit --all -m "Released $TAG"` +* `git tag $TAG` +* Build tarball, `./make_release_tarball.sh` +* Test build: + * `cd fuse-x.y.z` + * `md build && (cd build && meson .. && ninja)` + * `sudo sudo chown root:root build/util/fusermount3` + * `sudo chmod 4755 build/util/fusermount3` + * `(cd build; python3 -m pytest test/)` +* Upload API docs: + * `rm -r ../libfuse.github.io/doxygen && cp -a doc/html ../libfuse.github.io/doxygen` + * `git -C ../libfuse.github.io add doxygen/` + * `git -C ../libfuse.github.io commit --all -m "Re-generated doxygen documentation"` + * `git -C ../libfuse.github.io push` +* `git checkout master && git push && git push --tags` +* Create release on Github +* Write announcement to fuse-devel + + +Announcement email template + +``` +To: fuse-devel@lists.sourceforge.net +Subject: [ANNOUNCE] libfuse XXXX has been released + +Dear all, + +I am pleased to announce the release of libfuse XXX. + +The source code is available for download at https://github.com/libfuse/libfuse/releases. + +Please report any issues on this mailing list or the GitHub issue +tracker at https://github.com/libfuse/libfuse/issues. + +From ChangeLog.rst: + +[INSERT NEW ENTRIES] + +The following people have contributed code to this release: + +[INSERT CONTRIBUTORS] + +(a full list of credits containing all known contributors is included in +the `AUTHORS` file). + +Best, +-Nikolaus +``` diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 0000000..c403ea2 --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,2695 @@ +# Doxyfile 1.9.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = libfuse + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.h \ + *.c \ + *.h \ + *.dox + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = example + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = *.c \ + *.h + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = doc/fast17-vangoor.pdf + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = FUSE_USE_VERSION=35 + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node, +# Edge and Graph Attributes specification</a> You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a +# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about +# arrows shapes.</a> +# The default value is: labelfontname=Helvetica,labelfontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" + +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' <a +# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a> +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a +# graph for each documented class showing the direct and indirect inheritance +# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, +# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set +# to TEXT the direct and indirect inheritance relations will be shown as texts / +# links. +# Possible values are: NO, YES, TEXT and GRAPH. +# The default value is: YES. + +CLASS_GRAPH = TEXT + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. See also the chapter Grouping +# in the manual. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES diff --git a/doc/README.NFS b/doc/README.NFS new file mode 100644 index 0000000..edf5482 --- /dev/null +++ b/doc/README.NFS @@ -0,0 +1,44 @@ +NFS exporting is supported in Linux kernels 2.6.27 or later. + +You need to add an fsid=NNN option to /etc/exports to make exporting a +FUSE directory work. + +Filesystem support +------------------ + +NFS exporting works to some extent on all fuse filesystems, but not +perfectly. This is due to the stateless nature of the protocol, the +server has no way of knowing whether the client is keeping a reference +to a file or not, and hence that file may be removed from the server's +cache. In that case there has to be a way to look up that object +using the inode number, otherwise an ESTALE error will be returned. + +1) low-level interface + +Filesystems need to set FUSE_CAP_EXPORT_SUPPORT in conn->wants and +implement special lookups for the names "." and "..". The former may +be requested on any inode, including non-directories, while the latter +is only requested for directories. Otherwise these special lookups +should behave identically to ordinary lookups. + +Furthermore, setting FUSE_CAP_EXPORT_SUPPORT requires the file system +to handle node-ids (fuse_ino_t) that the file system may does not know +about - e.g. a fuse FORGET request might have been received or the node-id +was used in a previous instance of the file system daemon. The node-id might +not be valid at all when an invalid handle is passed to open_by_handle_at(). +This implies that the filesystem *must not* reuse node-ids even if +generation numbers are set correctly. This is because generation numbers +are not provided by the kernel to e.g. the getattr() handler, so the +handler would be unable to tell if the provided node-id refers to the +"known" current one, or a previous one that has been forgotten and re-used. + +2) high-level interface + +Because the high-level interface is path based, it is not possible to +delegate looking up by inode to the filesystem. + +To work around this, currently a "noforget" option is provided, which +makes the library remember nodes forever. This will make the NFS +server happy, but also results in an ever growing memory footprint for +the filesystem. For this reason if the filesystem is large (or the +memory is small), then this option is not recommended. diff --git a/doc/fast17-vangoor.pdf b/doc/fast17-vangoor.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cef723762933f15b9b179794c7335574b96fa62f GIT binary patch literal 599157 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�?i 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�$W;Igw!{^1IDv+^Yn!e9 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 HHl_R@i@NEa literal 0 HcmV?d00001 diff --git a/doc/fusermount3.1 b/doc/fusermount3.1 new file mode 100644 index 0000000..1455742 --- /dev/null +++ b/doc/fusermount3.1 @@ -0,0 +1,44 @@ +.TH FUSERMOUNT3 1 2011\-10\-23 2.8.6 "Filesystem in Userspace (FUSE)" + +.SH NAME +\fBfusermount3\fR \- mount and unmount FUSE filesystems + +.SH SYNOPSIS +\fBfusermount3\fR [\fIOPTIONS\fR] \fIMOUNTPOINT\fR + +.SH DESCRIPTION +Filesystem in Userspace (FUSE) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. It also aims to provide a secure method for non privileged users to create and mount their own filesystem implementations. +.PP +\fBfusermount3\fR is a program to mount and unmount FUSE +filesystems. It should be called directly only for unmounting FUSE +file systems. To allow mounting and unmounting by unprivileged users, +\fBfusermount3\fR needs to be installed set-uid root. +.SH OPTIONS +.IP "\-h" 4 +print help. +.IP "\-V" 4 +print version. +.IP "-o \fIOPTION\fR[,\fIOPTION\fR...]" 4 +mount options. +.IP "-u" 4 +unmount. +.IP "-q" 4 +quiet. +.IP "-z" 4 +lazy unmount. + +.SH SEE ALSO +\fImount\fR(8), +\fImount.fuse3\fR(8), +\fIfuse\fR(4), + +.SH HOMEPAGE +More information about fusermount3 and the FUSE project can be found at <\fIhttp://fuse.sourceforge.net/\fR>. + +.SH AUTHORS +.LP +FUSE is currently maintained by Nikolaus Rath <Nikolaus@rath.org> +.LP +The original author of FUSE is Miklos Szeredi <\fImiklos@szeredi.hu\fR>. +.LP +This manual page was originally written by Daniel Baumann <\fIdaniel.baumann@progress\-technologies.net\fR>. diff --git a/doc/html/annotated.html b/doc/html/annotated.html new file mode 100644 index 0000000..ea1f541 --- /dev/null +++ b/doc/html/annotated.html @@ -0,0 +1,73 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.8"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>libfuse: Data Structures + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Data Structures
+
+ + + + + diff --git a/doc/html/bc_s.png b/doc/html/bc_s.png new file mode 100644 index 0000000000000000000000000000000000000000..224b29aa9847d5a4b3902efd602b7ddf7d33e6c2 GIT binary patch literal 676 zcmV;V0$crwP)y__>=_9%My z{n931IS})GlGUF8K#6VIbs%684A^L3@%PlP2>_sk`UWPq@f;rU*V%rPy_ekbhXT&s z(GN{DxFv}*vZp`F>S!r||M`I*nOwwKX+BC~3P5N3-)Y{65c;ywYiAh-1*hZcToLHK ztpl1xomJ+Yb}K(cfbJr2=GNOnT!UFA7Vy~fBz8?J>XHsbZoDad^8PxfSa0GDgENZS zuLCEqzb*xWX2CG*b&5IiO#NzrW*;`VC9455M`o1NBh+(k8~`XCEEoC1Ybwf;vr4K3 zg|EB<07?SOqHp9DhLpS&bzgo70I+ghB_#)K7H%AMU3v}xuyQq9&Bm~++VYhF09a+U zl7>n7Jjm$K#b*FONz~fj;I->Bf;ule1prFN9FovcDGBkpg>)O*-}eLnC{6oZHZ$o% zXKW$;0_{8hxHQ>l;_*HATI(`7t#^{$(zLe}h*mqwOc*nRY9=?Sx4OOeVIfI|0V(V2 zBrW#G7Ss9wvzr@>H*`r>zE z+e8bOBgqIgldUJlG(YUDviMB`9+DH8n-s9SXRLyJHO1!=wY^79WYZMTa(wiZ!zP66 zA~!21vmF3H2{ngD;+`6j#~6j;$*f*G_2ZD1E;9(yaw7d-QnSCpK(cR1zU3qU0000< KMNUMnLSTYoA~SLT literal 0 HcmV?d00001 diff --git a/doc/html/bc_sd.png b/doc/html/bc_sd.png new file mode 100644 index 0000000000000000000000000000000000000000..31ca888dc71049713b35c351933a8d0f36180bf1 GIT binary patch literal 635 zcmV->0)+jEP)Jwi0r1~gdSq#w{Bu1q z`craw(p2!hu$4C_$Oc3X(sI6e=9QSTwPt{G) z=htT&^~&c~L2~e{r5_5SYe7#Is-$ln>~Kd%$F#tC65?{LvQ}8O`A~RBB0N~`2M+waajO;5>3B&-viHGJeEK2TQOiPRa zfDKyqwMc4wfaEh4jt>H`nW_Zidwk@Bowp`}(VUaj-pSI(-1L>FJVsX}Yl9~JsqgsZ zUD9(rMwf23Gez6KPa|wwInZodP-2}9@fK0Ga_9{8SOjU&4l`pH4@qlQp83>>HT$xW zER^U>)MyV%t(Lu=`d=Y?{k1@}&r7ZGkFQ%z%N+sE9BtYjovzxyxCPxN6&@wLK{soQ zSmkj$aLI}miuE^p@~4}mg9OjDfGEkgY4~^XzLRUBB*O{+&vq<3v(E%+k_i%=`~j%{ Vj14gnt9}3g002ovPDHLkV1n!oC4m3{ literal 0 HcmV?d00001 diff --git a/doc/html/bdwn.png b/doc/html/bdwn.png new file mode 100644 index 0000000000000000000000000000000000000000..940a0b950443a0bb1b216ac03c45b8a16c955452 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)H!3HEvS)PKZC{Gv1kP61Pb5HX&C2wk~_T + + + + + + +libfuse: lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+ + +
enum fuse_buf_flags flags
+
void * mem
+ +
off_t pos
+
size_t size
+ + + +
struct fuse_buf buf[1]
+ +
+ + + + diff --git a/doc/html/build-debian_2config_8h_source.html b/doc/html/build-debian_2config_8h_source.html new file mode 100644 index 0000000..b0cf45e --- /dev/null +++ b/doc/html/build-debian_2config_8h_source.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: build-debian/config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
config.h
+
+
+
1 /*
+
2  * Autogenerated by the Meson build system.
+
3  * Do not edit, your changes will be lost.
+
4  */
+
5 
+
6 #pragma once
+
7 
+
8 #define HAVE_COPY_FILE_RANGE
+
9 
+
10 #define HAVE_FALLOCATE
+
11 
+
12 #define HAVE_FDATASYNC
+
13 
+
14 #define HAVE_FORK
+
15 
+
16 #define HAVE_FSTATAT
+
17 
+
18 #define HAVE_ICONV
+
19 
+
20 #define HAVE_OPENAT
+
21 
+
22 #define HAVE_PIPE2
+
23 
+
24 #define HAVE_POSIX_FALLOCATE
+
25 
+
26 #define HAVE_READLINKAT
+
27 
+
28 #define HAVE_SETXATTR
+
29 
+
30 #define HAVE_SPLICE
+
31 
+
32 #define HAVE_STRUCT_STAT_ST_ATIM
+
33 
+
34 #undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
35 
+
36 #define HAVE_UTIMENSAT
+
37 
+
38 #define HAVE_VMSPLICE
+
39 
+
40 #define PACKAGE_VERSION "3.10.5"
+
41 
+
+ + + + diff --git a/doc/html/build-debian_2meson-private_2sanitycheckc_8c_source.html b/doc/html/build-debian_2meson-private_2sanitycheckc_8c_source.html new file mode 100644 index 0000000..a3fdc04 --- /dev/null +++ b/doc/html/build-debian_2meson-private_2sanitycheckc_8c_source.html @@ -0,0 +1,56 @@ + + + + + + + +libfuse: build-debian/meson-private/sanitycheckc.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
sanitycheckc.c
+
+
+
1 int main(void) { int class=0; return class; }
+
+ + + + diff --git a/doc/html/build-rhel8_2config_8h_source.html b/doc/html/build-rhel8_2config_8h_source.html new file mode 100644 index 0000000..aeb48d1 --- /dev/null +++ b/doc/html/build-rhel8_2config_8h_source.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: build-rhel8/config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
config.h
+
+
+
1 /*
+
2  * Autogenerated by the Meson build system.
+
3  * Do not edit, your changes will be lost.
+
4  */
+
5 
+
6 #pragma once
+
7 
+
8 #define HAVE_COPY_FILE_RANGE
+
9 
+
10 #define HAVE_FALLOCATE
+
11 
+
12 #define HAVE_FDATASYNC
+
13 
+
14 #define HAVE_FORK
+
15 
+
16 #define HAVE_FSTATAT
+
17 
+
18 #define HAVE_ICONV
+
19 
+
20 #define HAVE_OPENAT
+
21 
+
22 #define HAVE_PIPE2
+
23 
+
24 #define HAVE_POSIX_FALLOCATE
+
25 
+
26 #define HAVE_READLINKAT
+
27 
+
28 #define HAVE_SETXATTR
+
29 
+
30 #define HAVE_SPLICE
+
31 
+
32 #define HAVE_STRUCT_STAT_ST_ATIM
+
33 
+
34 #undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
35 
+
36 #define HAVE_UTIMENSAT
+
37 
+
38 #define HAVE_VMSPLICE
+
39 
+
40 #define PACKAGE_VERSION "3.11.0"
+
41 
+
+ + + + diff --git a/doc/html/build-rhel8_2meson-private_2sanitycheckc_8c_source.html b/doc/html/build-rhel8_2meson-private_2sanitycheckc_8c_source.html new file mode 100644 index 0000000..816240b --- /dev/null +++ b/doc/html/build-rhel8_2meson-private_2sanitycheckc_8c_source.html @@ -0,0 +1,56 @@ + + + + + + + +libfuse: build-rhel8/meson-private/sanitycheckc.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
sanitycheckc.c
+
+
+
1 int main(void) { int class=0; return class; }
+
+ + + + diff --git a/doc/html/build-ubuntu_2config_8h_source.html b/doc/html/build-ubuntu_2config_8h_source.html new file mode 100644 index 0000000..6636879 --- /dev/null +++ b/doc/html/build-ubuntu_2config_8h_source.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: build-ubuntu/config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
config.h
+
+
+
1 /*
+
2  * Autogenerated by the Meson build system.
+
3  * Do not edit, your changes will be lost.
+
4  */
+
5 
+
6 #pragma once
+
7 
+
8 #define HAVE_COPY_FILE_RANGE
+
9 
+
10 #define HAVE_FALLOCATE
+
11 
+
12 #define HAVE_FDATASYNC
+
13 
+
14 #define HAVE_FORK
+
15 
+
16 #define HAVE_FSTATAT
+
17 
+
18 #define HAVE_ICONV
+
19 
+
20 #define HAVE_OPENAT
+
21 
+
22 #define HAVE_PIPE2
+
23 
+
24 #define HAVE_POSIX_FALLOCATE
+
25 
+
26 #define HAVE_READLINKAT
+
27 
+
28 #define HAVE_SETXATTR
+
29 
+
30 #define HAVE_SPLICE
+
31 
+
32 #define HAVE_STRUCT_STAT_ST_ATIM
+
33 
+
34 #undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
35 
+
36 #define HAVE_UTIMENSAT
+
37 
+
38 #define HAVE_VMSPLICE
+
39 
+
40 #define PACKAGE_VERSION "3.12.0"
+
41 
+
+ + + + diff --git a/doc/html/build-ubuntu_2fuse__config_8h_source.html b/doc/html/build-ubuntu_2fuse__config_8h_source.html new file mode 100644 index 0000000..f4aa1f7 --- /dev/null +++ b/doc/html/build-ubuntu_2fuse__config_8h_source.html @@ -0,0 +1,105 @@ + + + + + + + +libfuse: build-ubuntu/fuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define HAVE_BACKTRACE
+
9
+
10#define HAVE_CLOSE_RANGE
+
11
+
12#define HAVE_COPY_FILE_RANGE
+
13
+
14#define HAVE_FALLOCATE
+
15
+
16#define HAVE_FDATASYNC
+
17
+
18#define HAVE_FORK
+
19
+
20#define HAVE_FSTATAT
+
21
+
22#define HAVE_ICONV
+
23
+
24#define HAVE_OPENAT
+
25
+
26#define HAVE_PIPE2
+
27
+
28#define HAVE_POSIX_FALLOCATE
+
29
+
30#define HAVE_READLINKAT
+
31
+
32#define HAVE_SETXATTR
+
33
+
34#define HAVE_SPLICE
+
35
+
36#define HAVE_STRUCT_STAT_ST_ATIM
+
37
+
38#undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
39
+
40#define HAVE_UTIMENSAT
+
41
+
42#define HAVE_VMSPLICE
+
43
+
44#define PACKAGE_VERSION "3.17.1-rc1"
+
45
+
+ + + + diff --git a/doc/html/build-ubuntu_2libfuse__config_8h_source.html b/doc/html/build-ubuntu_2libfuse__config_8h_source.html new file mode 100644 index 0000000..dad4663 --- /dev/null +++ b/doc/html/build-ubuntu_2libfuse__config_8h_source.html @@ -0,0 +1,77 @@ + + + + + + + +libfuse: build-ubuntu/libfuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
libfuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define FUSE_HOTFIX_VERSION 1
+
9
+
10#define FUSE_MAJOR_VERSION 3
+
11
+
12#define FUSE_MINOR_VERSION 17
+
13
+
14#define FUSE_RC_VERSION rc1
+
15
+
16#define LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS 1
+
17
+
+ + + + diff --git a/doc/html/build-ubuntu_2meson-private_2sanitycheckc_8c_source.html b/doc/html/build-ubuntu_2meson-private_2sanitycheckc_8c_source.html new file mode 100644 index 0000000..68cfeaa --- /dev/null +++ b/doc/html/build-ubuntu_2meson-private_2sanitycheckc_8c_source.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: build-ubuntu/meson-private/sanitycheckc.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
sanitycheckc.c
+
+
+
1int main(void) { int class=0; return class; }
+
+ + + + diff --git a/doc/html/classes.html b/doc/html/classes.html new file mode 100644 index 0000000..85715d7 --- /dev/null +++ b/doc/html/classes.html @@ -0,0 +1,60 @@ + + + + + + + +libfuse: Data Structure Index + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Data Structure Index
+
+ + + + + diff --git a/doc/html/closed.png b/doc/html/closed.png new file mode 100644 index 0000000000000000000000000000000000000000..98cc2c909da37a6df914fbf67780eebd99c597f5 GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{V-kvUwAr*{o@8{^CZMh(5KoB^r_<4^zF@3)Cp&&t3hdujKf f*?bjBoY!V+E))@{xMcbjXe@)LtDnm{r-UW|*e5JT literal 0 HcmV?d00001 diff --git a/doc/html/compat_8c_source.html b/doc/html/compat_8c_source.html new file mode 100644 index 0000000..cfde43c --- /dev/null +++ b/doc/html/compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/cuse_8c.html b/doc/html/cuse_8c.html new file mode 100644 index 0000000..adadf4c --- /dev/null +++ b/doc/html/cuse_8c.html @@ -0,0 +1,411 @@ + + + + + + + +libfuse: example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/cuse_8c_source.html b/doc/html/cuse_8c_source.html new file mode 100644 index 0000000..b87e1e0 --- /dev/null +++ b/doc/html/cuse_8c_source.html @@ -0,0 +1,396 @@ + + + + + + + +libfuse: example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/cuse__client_8c.html b/doc/html/cuse__client_8c.html new file mode 100644 index 0000000..a4d7988 --- /dev/null +++ b/doc/html/cuse__client_8c.html @@ -0,0 +1,215 @@ + + + + + + + +libfuse: example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+ +
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/cuse__client_8c_source.html b/doc/html/cuse__client_8c_source.html new file mode 100644 index 0000000..9b23fe0 --- /dev/null +++ b/doc/html/cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/cuse__lowlevel_8c_source.html b/doc/html/cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..cb1fe49 --- /dev/null +++ b/doc/html/cuse__lowlevel_8c_source.html @@ -0,0 +1,452 @@ + + + + + + + +libfuse: lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
196{
+
197 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
198 struct cuse_init_out outarg;
+
199 struct fuse_session *se = req->se;
+
200 struct cuse_data *cd = se->cuse_data;
+
201 size_t bufsize = se->bufsize;
+
202 struct cuse_lowlevel_ops *clop = req_clop(req);
+
203
+
204 (void) nodeid;
+
205 if (se->debug) {
+
206 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
207 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
208 }
+
209 se->conn.proto_major = arg->major;
+
210 se->conn.proto_minor = arg->minor;
+
211
+
212 /* XXX This is not right.*/
+
213 se->conn.capable_ext = 0;
+
214 se->conn.want_ext = 0;
+
215
+
216 if (arg->major < 7) {
+
217 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
218 arg->major, arg->minor);
+
219 fuse_reply_err(req, EPROTO);
+
220 return;
+
221 }
+
222
+
223 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
224 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
225 bufsize);
+
226 bufsize = FUSE_MIN_READ_BUFFER;
+
227 }
+
228
+
229 bufsize -= 4096;
+
230 if (bufsize < se->conn.max_write)
+
231 se->conn.max_write = bufsize;
+
232
+
233 se->got_init = 1;
+
234 if (se->op.init)
+
235 se->op.init(se->userdata, &se->conn);
+
236
+
237 memset(&outarg, 0, sizeof(outarg));
+
238 outarg.major = FUSE_KERNEL_VERSION;
+
239 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
240 outarg.flags = cd->flags;
+
241 outarg.max_read = cd->max_read;
+
242 outarg.max_write = se->conn.max_write;
+
243 outarg.dev_major = cd->dev_major;
+
244 outarg.dev_minor = cd->dev_minor;
+
245
+
246 if (se->debug) {
+
247 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
248 outarg.major, outarg.minor);
+
249 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
250 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
251 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
252 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
253 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
255 cd->dev_info);
+
256 }
+
257
+
258 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
259
+
260 if (clop->init_done)
+
261 clop->init_done(se->userdata);
+
262
+
263 fuse_free_req(req);
+
264}
+
265
+
266struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
267 const struct cuse_info *ci,
+
268 const struct cuse_lowlevel_ops *clop,
+
269 int *multithreaded, void *userdata)
+
270{
+
271 const char *devname = "/dev/cuse";
+
272 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
275 };
+
276 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
277 struct fuse_session *se;
+
278 struct fuse_cmdline_opts opts;
+
279 int fd;
+
280 int res;
+
281
+
282 if (fuse_parse_cmdline(&args, &opts) == -1)
+
283 return NULL;
+
284 *multithreaded = !opts.singlethread;
+
285
+
286 /* Remove subtype= option */
+
287 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
288 if (res == -1)
+
289 goto out1;
+
290
+
291 /*
+
292 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
293 * would ensue.
+
294 */
+
295 do {
+
296 fd = open("/dev/null", O_RDWR);
+
297 if (fd > 2)
+
298 close(fd);
+
299 } while (fd >= 0 && fd <= 2);
+
300
+
301 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
302 if (se == NULL)
+
303 goto out1;
+
304
+
305 fd = open(devname, O_RDWR);
+
306 if (fd == -1) {
+
307 if (errno == ENODEV || errno == ENOENT)
+
308 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
309 else
+
310 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
311 devname, strerror(errno));
+
312 goto err_se;
+
313 }
+
314 se->fd = fd;
+
315
+
316 res = fuse_set_signal_handlers(se);
+
317 if (res == -1)
+
318 goto err_se;
+
319
+
320 res = fuse_daemonize(opts.foreground);
+
321 if (res == -1)
+
322 goto err_sig;
+
323
+
324 fuse_opt_free_args(&args);
+
325 return se;
+
326
+
327err_sig:
+ +
329err_se:
+ +
331out1:
+
332 free(opts.mountpoint);
+
333 fuse_opt_free_args(&args);
+
334 return NULL;
+
335}
+
336
+
337void cuse_lowlevel_teardown(struct fuse_session *se)
+
338{
+ + +
341}
+
342
+
343int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
344 const struct cuse_lowlevel_ops *clop, void *userdata)
+
345{
+
346 struct fuse_session *se;
+
347 int multithreaded;
+
348 int res;
+
349
+
350 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
351 userdata);
+
352 if (se == NULL)
+
353 return 1;
+
354
+
355 if (multithreaded) {
+
356 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
357 res = fuse_session_loop_mt(se, config);
+
358 fuse_loop_cfg_destroy(config);
+
359 }
+
360 else
+
361 res = fuse_session_loop(se);
+
362
+
363 cuse_lowlevel_teardown(se);
+
364 if (res == -1)
+
365 return 1;
+
366
+
367 return 0;
+
368}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+ +
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/cuse__lowlevel_8h_source.html b/doc/html/cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..1d1d301 --- /dev/null +++ b/doc/html/cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+ +
struct fuse_req * fuse_req_t
+ + + +
+ + + + diff --git a/doc/html/dir_0dc60d031bb972b0c2e26fefb85d65c3.html b/doc/html/dir_0dc60d031bb972b0c2e26fefb85d65c3.html new file mode 100644 index 0000000..73300ed --- /dev/null +++ b/doc/html/dir_0dc60d031bb972b0c2e26fefb85d65c3.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_13e138d54eb8818da29c3992edef070a.html b/doc/html/dir_13e138d54eb8818da29c3992edef070a.html new file mode 100644 index 0000000..7f0545b --- /dev/null +++ b/doc/html/dir_13e138d54eb8818da29c3992edef070a.html @@ -0,0 +1,77 @@ + + + + + + + +libfuse: test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + +

+Files

 hello.c
 
 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_setattr.c
 
 test_syscalls.c
 
 test_want_conversion.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_1dfeff730c7aaf81ae73022af75d725f.html b/doc/html/dir_1dfeff730c7aaf81ae73022af75d725f.html new file mode 100644 index 0000000..9c36a9d --- /dev/null +++ b/doc/html/dir_1dfeff730c7aaf81ae73022af75d725f.html @@ -0,0 +1,102 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html b/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html new file mode 100644 index 0000000..5bfb506 --- /dev/null +++ b/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_2ee0fcdc098dd79f81f9944140524a5f.html b/doc/html/dir_2ee0fcdc098dd79f81f9944140524a5f.html new file mode 100644 index 0000000..a933dd8 --- /dev/null +++ b/doc/html/dir_2ee0fcdc098dd79f81f9944140524a5f.html @@ -0,0 +1,68 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/build Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
build Directory Reference
+
+
+ + + + +

+Directories

 meson-private
 
+ + + + + +

+Files

 fuse_config.h
 
 libfuse_config.h
 
+
+ + + + diff --git a/doc/html/dir_398a6172cfdb51678e55b54be73d5fa5.html b/doc/html/dir_398a6172cfdb51678e55b54be73d5fa5.html new file mode 100644 index 0000000..3f0c312 --- /dev/null +++ b/doc/html/dir_398a6172cfdb51678e55b54be73d5fa5.html @@ -0,0 +1,102 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_3bd2abc0f0eca4df2097a8b21b48c13b.html b/doc/html/dir_3bd2abc0f0eca4df2097a8b21b48c13b.html new file mode 100644 index 0000000..288d171 --- /dev/null +++ b/doc/html/dir_3bd2abc0f0eca4df2097a8b21b48c13b.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_3f3840dea793d3e59c45e9730587a31c.html b/doc/html/dir_3f3840dea793d3e59c45e9730587a31c.html new file mode 100644 index 0000000..7311d98 --- /dev/null +++ b/doc/html/dir_3f3840dea793d3e59c45e9730587a31c.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/doc Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
doc Directory Reference
+
+
+
+ + + + diff --git a/doc/html/dir_71fd611d49b15b397aa830250b7fdc25.html b/doc/html/dir_71fd611d49b15b397aa830250b7fdc25.html new file mode 100644 index 0000000..1a749e2 --- /dev/null +++ b/doc/html/dir_71fd611d49b15b397aa830250b7fdc25.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_75ac0186611fae93a0a2599fafee7b59.html b/doc/html/dir_75ac0186611fae93a0a2599fafee7b59.html new file mode 100644 index 0000000..dcec66f --- /dev/null +++ b/doc/html/dir_75ac0186611fae93a0a2599fafee7b59.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/doc Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
doc Directory Reference
+
+
+
+ + + + diff --git a/doc/html/dir_7c10f509f528fa84faee6538d3d65d31.html b/doc/html/dir_7c10f509f528fa84faee6538d3d65d31.html new file mode 100644 index 0000000..e97d4d3 --- /dev/null +++ b/doc/html/dir_7c10f509f528fa84faee6538d3d65d31.html @@ -0,0 +1,69 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1 Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
fuse-3.17.1-rc1 Directory Reference
+
+
+ + + + + + + + + + + + +

+Directories

 example
 
 include
 
 lib
 
 test
 
 util
 
+
+ + + + diff --git a/doc/html/dir_812ebf512ecd184ed1d39ebdcddff7ee.html b/doc/html/dir_812ebf512ecd184ed1d39ebdcddff7ee.html new file mode 100644 index 0000000..4336bd9 --- /dev/null +++ b/doc/html/dir_812ebf512ecd184ed1d39ebdcddff7ee.html @@ -0,0 +1,68 @@ + + + + + + + +libfuse: build-ubuntu Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
build-ubuntu Directory Reference
+
+
+ + + + +

+Directories

 meson-private
 
+ + + + + +

+Files

 fuse_config.h
 
 libfuse_config.h
 
+
+ + + + diff --git a/doc/html/dir_8e18dfdf7be85b959157088a64d07c40.html b/doc/html/dir_8e18dfdf7be85b959157088a64d07c40.html new file mode 100644 index 0000000..68fdefe --- /dev/null +++ b/doc/html/dir_8e18dfdf7be85b959157088a64d07c40.html @@ -0,0 +1,99 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 cuse.c
 
 cuse_client.c
 
 hello.c
 
 hello_ll.c
 
 hello_ll_uds.c
 
 invalidate_path.c
 
 ioctl.c
 
 ioctl.h
 
 ioctl_client.c
 
 notify_inval_entry.c
 
 notify_inval_inode.c
 
 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_8e4bcc66b5040e6cd82b8756da72da10.html b/doc/html/dir_8e4bcc66b5040e6cd82b8756da72da10.html new file mode 100644 index 0000000..c4620a8 --- /dev/null +++ b/doc/html/dir_8e4bcc66b5040e6cd82b8756da72da10.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html b/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html new file mode 100644 index 0000000..9a290f6 --- /dev/null +++ b/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html @@ -0,0 +1,102 @@ + + + + + + + +libfuse: lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_9bd412e6c6ed29227c4172ad8b62b221.html b/doc/html/dir_9bd412e6c6ed29227c4172ad8b62b221.html new file mode 100644 index 0000000..4e3775f --- /dev/null +++ b/doc/html/dir_9bd412e6c6ed29227c4172ad8b62b221.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: build-ubuntu/meson-private Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
meson-private Directory Reference
+
+
+ + + + +

+Files

 sanitycheckc.c
 
+
+ + + + diff --git a/doc/html/dir_9ce3b66b67a1b1b09c830f7629696f5b.html b/doc/html/dir_9ce3b66b67a1b1b09c830f7629696f5b.html new file mode 100644 index 0000000..08f4120 --- /dev/null +++ b/doc/html/dir_9ce3b66b67a1b1b09c830f7629696f5b.html @@ -0,0 +1,102 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
lib Directory Reference
+
+
+ + + + +

+Directories

 modules
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 buffer.c
 
 compat.c
 
 cuse_lowlevel.c
 
 fuse.c
 
 fuse_i.h
 
 fuse_log.c
 
 fuse_loop.c
 
 fuse_loop_mt.c
 
 fuse_lowlevel.c
 
 fuse_misc.h
 
 fuse_opt.c
 
 fuse_signals.c
 
 helper.c
 
 mount.c
 
 mount_bsd.c
 
 mount_util.c
 
 mount_util.h
 
 util.c
 
 util.h
 
+
+ + + + diff --git a/doc/html/dir_9e890e4e051b7ad89260eb605ee3a3c0.html b/doc/html/dir_9e890e4e051b7ad89260eb605ee3a3c0.html new file mode 100644 index 0000000..cd5b212 --- /dev/null +++ b/doc/html/dir_9e890e4e051b7ad89260eb605ee3a3c0.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/doc Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
doc Directory Reference
+
+
+
+ + + + diff --git a/doc/html/dir_a320cda2ad42f6d84fc7a0968b7fa58c.html b/doc/html/dir_a320cda2ad42f6d84fc7a0968b7fa58c.html new file mode 100644 index 0000000..dba8755 --- /dev/null +++ b/doc/html/dir_a320cda2ad42f6d84fc7a0968b7fa58c.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_a58186fda3c587e14871c11a193bc78f.html b/doc/html/dir_a58186fda3c587e14871c11a193bc78f.html new file mode 100644 index 0000000..e3302cd --- /dev/null +++ b/doc/html/dir_a58186fda3c587e14871c11a193bc78f.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/build/meson-private Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
meson-private Directory Reference
+
+
+ + + + +

+Files

 sanitycheckc.c
 
+
+ + + + diff --git a/doc/html/dir_a8feaf0c066680727663edb1c44e1229.html b/doc/html/dir_a8feaf0c066680727663edb1c44e1229.html new file mode 100644 index 0000000..32ebcab --- /dev/null +++ b/doc/html/dir_a8feaf0c066680727663edb1c44e1229.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_a9479a6a9e1f41fd821cf4117f966d1e.html b/doc/html/dir_a9479a6a9e1f41fd821cf4117f966d1e.html new file mode 100644 index 0000000..9997cba --- /dev/null +++ b/doc/html/dir_a9479a6a9e1f41fd821cf4117f966d1e.html @@ -0,0 +1,99 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 cuse.c
 
 cuse_client.c
 
 hello.c
 
 hello_ll.c
 
 hello_ll_uds.c
 
 invalidate_path.c
 
 ioctl.c
 
 ioctl.h
 
 ioctl_client.c
 
 notify_inval_entry.c
 
 notify_inval_inode.c
 
 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_acab6aac461847b02973b7a47d1d14e5.html b/doc/html/dir_acab6aac461847b02973b7a47d1d14e5.html new file mode 100644 index 0000000..e61dafa --- /dev/null +++ b/doc/html/dir_acab6aac461847b02973b7a47d1d14e5.html @@ -0,0 +1,73 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + +

+Files

 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_setattr.c
 
 test_syscalls.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_afd1a5f89d59cae8c3bf3e89c0f7b45e.html b/doc/html/dir_afd1a5f89d59cae8c3bf3e89c0f7b45e.html new file mode 100644 index 0000000..3010751 --- /dev/null +++ b/doc/html/dir_afd1a5f89d59cae8c3bf3e89c0f7b45e.html @@ -0,0 +1,68 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/build Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
build Directory Reference
+
+
+ + + + +

+Directories

 meson-private
 
+ + + + + +

+Files

 fuse_config.h
 
 libfuse_config.h
 
+
+ + + + diff --git a/doc/html/dir_b103db43dffcdee3de8bd6a6d79f8618.html b/doc/html/dir_b103db43dffcdee3de8bd6a6d79f8618.html new file mode 100644 index 0000000..66ab602 --- /dev/null +++ b/doc/html/dir_b103db43dffcdee3de8bd6a6d79f8618.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/util Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
util Directory Reference
+
+
+ + + + + + +

+Files

 fusermount.c
 
 mount.fuse.c
 
+
+ + + + diff --git a/doc/html/dir_bc297b8ee9a3081b188fc7e3fa93504a.html b/doc/html/dir_bc297b8ee9a3081b188fc7e3fa93504a.html new file mode 100644 index 0000000..d764469 --- /dev/null +++ b/doc/html/dir_bc297b8ee9a3081b188fc7e3fa93504a.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: build-rhel8/meson-private Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
meson-private Directory Reference
+
+
+
+ + + + diff --git a/doc/html/dir_bf10854db571cf772d4a6211be1232f2.html b/doc/html/dir_bf10854db571cf772d4a6211be1232f2.html new file mode 100644 index 0000000..fef82a4 --- /dev/null +++ b/doc/html/dir_bf10854db571cf772d4a6211be1232f2.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: build-debian/meson-private Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
meson-private Directory Reference
+
+
+
+ + + + diff --git a/doc/html/dir_cb081b49593532e6f6dadd15ccd82ab4.html b/doc/html/dir_cb081b49593532e6f6dadd15ccd82ab4.html new file mode 100644 index 0000000..bcf8209 --- /dev/null +++ b/doc/html/dir_cb081b49593532e6f6dadd15ccd82ab4.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_cd1412f659f324388a4b25bf5c39d8d8.html b/doc/html/dir_cd1412f659f324388a4b25bf5c39d8d8.html new file mode 100644 index 0000000..2566aa0 --- /dev/null +++ b/doc/html/dir_cd1412f659f324388a4b25bf5c39d8d8.html @@ -0,0 +1,59 @@ + + + + + + + +libfuse: build-rhel8 Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
build-rhel8 Directory Reference
+
+
+ + +

+Directories

+
+ + + + diff --git a/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html b/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html new file mode 100644 index 0000000..a86b7c4 --- /dev/null +++ b/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html @@ -0,0 +1,99 @@ + + + + + + + +libfuse: example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 cuse.c
 
 cuse_client.c
 
 hello.c
 
 hello_ll.c
 
 hello_ll_uds.c
 
 invalidate_path.c
 
 ioctl.c
 
 ioctl.h
 
 ioctl_client.c
 
 notify_inval_entry.c
 
 notify_inval_inode.c
 
 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_d0f078c01bc3ce273c081eaac57e44d3.html b/doc/html/dir_d0f078c01bc3ce273c081eaac57e44d3.html new file mode 100644 index 0000000..74a54b9 --- /dev/null +++ b/doc/html/dir_d0f078c01bc3ce273c081eaac57e44d3.html @@ -0,0 +1,71 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0 Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
fuse-3.17.1-rc0 Directory Reference
+
+
+ + + + + + + + + + + + + + +

+Directories

 build
 
 example
 
 include
 
 lib
 
 test
 
 util
 
+
+ + + + diff --git a/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html b/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html new file mode 100644 index 0000000..2d34ca2 --- /dev/null +++ b/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: include Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
include Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 cuse_lowlevel.h
 
 fuse.h
 
 fuse_common.h
 
 fuse_kernel.h
 
 fuse_log.h
 
 fuse_lowlevel.h
 
 fuse_mount_compat.h
 
 fuse_opt.h
 
+
+ + + + diff --git a/doc/html/dir_d66497626fcb891eefdcc285752aeb54.html b/doc/html/dir_d66497626fcb891eefdcc285752aeb54.html new file mode 100644 index 0000000..6a70175 --- /dev/null +++ b/doc/html/dir_d66497626fcb891eefdcc285752aeb54.html @@ -0,0 +1,99 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
example Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Files

 cuse.c
 
 cuse_client.c
 
 hello.c
 
 hello_ll.c
 
 hello_ll_uds.c
 
 invalidate_path.c
 
 ioctl.c
 
 ioctl.h
 
 ioctl_client.c
 
 notify_inval_entry.c
 
 notify_inval_inode.c
 
 notify_store_retrieve.c
 
 null.c
 
 passthrough.c
 
 passthrough_fh.c
 
 passthrough_helpers.h
 
 passthrough_ll.c
 
 poll.c
 
 poll_client.c
 
 printcap.c
 
+
+ + + + diff --git a/doc/html/dir_d709b210fbffdc23d8da2b53f1d32b88.html b/doc/html/dir_d709b210fbffdc23d8da2b53f1d32b88.html new file mode 100644 index 0000000..0491193 --- /dev/null +++ b/doc/html/dir_d709b210fbffdc23d8da2b53f1d32b88.html @@ -0,0 +1,59 @@ + + + + + + + +libfuse: build-debian Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
build-debian Directory Reference
+
+
+ + +

+Directories

+
+ + + + diff --git a/doc/html/dir_d726b7599710abf1b6039c9dcf7cf687.html b/doc/html/dir_d726b7599710abf1b6039c9dcf7cf687.html new file mode 100644 index 0000000..408dcb9 --- /dev/null +++ b/doc/html/dir_d726b7599710abf1b6039c9dcf7cf687.html @@ -0,0 +1,71 @@ + + + + + + + +libfuse: fuse-3.17.1.dir Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
fuse-3.17.1.dir Directory Reference
+
+
+ + + + + + + + + + + + + + +

+Directories

 build
 
 example
 
 include
 
 lib
 
 test
 
 util
 
+
+ + + + diff --git a/doc/html/dir_d7aafa31aa39c71b1ecb09f780e2341f.html b/doc/html/dir_d7aafa31aa39c71b1ecb09f780e2341f.html new file mode 100644 index 0000000..a729086 --- /dev/null +++ b/doc/html/dir_d7aafa31aa39c71b1ecb09f780e2341f.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_db9b1b9045937e68e72f268df7b71cec.html b/doc/html/dir_db9b1b9045937e68e72f268df7b71cec.html new file mode 100644 index 0000000..c1a4a1c --- /dev/null +++ b/doc/html/dir_db9b1b9045937e68e72f268df7b71cec.html @@ -0,0 +1,75 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + + + +

+Files

 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_setattr.c
 
 test_syscalls.c
 
 test_want_conversion.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_de142c35e6fa30e4531b188c66688c85.html b/doc/html/dir_de142c35e6fa30e4531b188c66688c85.html new file mode 100644 index 0000000..aea9875 --- /dev/null +++ b/doc/html/dir_de142c35e6fa30e4531b188c66688c85.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/build/meson-private Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
meson-private Directory Reference
+
+
+ + + + +

+Files

 sanitycheckc.c
 
+
+ + + + diff --git a/doc/html/dir_e0b70151b30581f59638c2f3a5122668.html b/doc/html/dir_e0b70151b30581f59638c2f3a5122668.html new file mode 100644 index 0000000..7377f52 --- /dev/null +++ b/doc/html/dir_e0b70151b30581f59638c2f3a5122668.html @@ -0,0 +1,73 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + + + + +

+Files

 readdir_inode.c
 
 release_unlink_race.c
 
 stracedecode.c
 
 test_setattr.c
 
 test_syscalls.c
 
 test_write_cache.c
 
 wrong_command.c
 
+
+ + + + diff --git a/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html b/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html new file mode 100644 index 0000000..8e5534b --- /dev/null +++ b/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: lib/modules Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
modules Directory Reference
+
+
+ + + + + + +

+Files

 iconv.c
 
 subdir.c
 
+
+ + + + diff --git a/doc/html/dir_e68e8157741866f444e17edd764ebbae.html b/doc/html/dir_e68e8157741866f444e17edd764ebbae.html new file mode 100644 index 0000000..6ab75b5 --- /dev/null +++ b/doc/html/dir_e68e8157741866f444e17edd764ebbae.html @@ -0,0 +1,55 @@ + + + + + + + +libfuse: doc Directory Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
doc Directory Reference
+
+
+
+ + + + diff --git a/doc/html/doc.png b/doc/html/doc.png new file mode 100644 index 0000000000000000000000000000000000000000..17edabff95f7b8da13c9516a04efe05493c29501 GIT binary patch literal 746 zcmV7=@pnbNXRFEm&G8P!&WHG=d)>K?YZ1bzou)2{$)) zumDct!>4SyxL;zgaG>wy`^Hv*+}0kUfCrz~BCOViSb$_*&;{TGGn2^x9K*!Sf0=lV zpP=7O;GA0*Jm*tTYj$IoXvimpnV4S1Z5f$p*f$Db2iq2zrVGQUz~yq`ahn7ck(|CE z7Gz;%OP~J6)tEZWDzjhL9h2hdfoU2)Nd%T<5Kt;Y0XLt&<@6pQx!nw*5`@bq#?l*?3z{Hlzoc=Pr>oB5(9i6~_&-}A(4{Q$>c>%rV&E|a(r&;?i5cQB=} zYSDU5nXG)NS4HEs0it2AHe2>shCyr7`6@4*6{r@8fXRbTA?=IFVWAQJL&H5H{)DpM#{W(GL+Idzf^)uRV@oB8u$ z8v{MfJbTiiRg4bza<41NAzrl{=3fl_D+$t+^!xlQ8S}{UtY`e z;;&9UhyZqQRN%2pot{*Ei0*4~hSF_3AH2@fKU!$NSflS>{@tZpDT4`M2WRTTVH+D? z)GFlEGGHe?koB}i|1w45!BF}N_q&^HJ&-tyR{(afC6H7|aml|tBBbv}55C5DNP8p3 z)~jLEO4Z&2hZmP^i-e%(@d!(E|KRafiU8Q5u(wU((j8un3OR*Hvj+t literal 0 HcmV?d00001 diff --git a/doc/html/doc.svg b/doc/html/doc.svg new file mode 100644 index 0000000..0b928a5 --- /dev/null +++ b/doc/html/doc.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/html/docd.svg b/doc/html/docd.svg new file mode 100644 index 0000000..ac18b27 --- /dev/null +++ b/doc/html/docd.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/html/doxygen.css b/doc/html/doxygen.css new file mode 100644 index 0000000..009a9b5 --- /dev/null +++ b/doc/html/doxygen.css @@ -0,0 +1,2045 @@ +/* The standard CSS for doxygen 1.9.8*/ + +html { +/* page base colors */ +--page-background-color: white; +--page-foreground-color: black; +--page-link-color: #3D578C; +--page-visited-link-color: #4665A2; + +/* index */ +--index-odd-item-bg-color: #F8F9FC; +--index-even-item-bg-color: white; +--index-header-color: black; +--index-separator-color: #A0A0A0; + +/* header */ +--header-background-color: #F9FAFC; +--header-separator-color: #C4CFE5; +--header-gradient-image: url('nav_h.png'); +--group-header-separator-color: #879ECB; +--group-header-color: #354C7B; +--inherit-header-color: gray; + +--footer-foreground-color: #2A3D61; +--footer-logo-width: 104px; +--citation-label-color: #334975; +--glow-color: cyan; + +--title-background-color: white; +--title-separator-color: #5373B4; +--directory-separator-color: #9CAFD4; +--separator-color: #4A6AAA; + +--blockquote-background-color: #F7F8FB; +--blockquote-border-color: #9CAFD4; + +--scrollbar-thumb-color: #9CAFD4; +--scrollbar-background-color: #F9FAFC; + +--icon-background-color: #728DC1; +--icon-foreground-color: white; +--icon-doc-image: url('doc.svg'); +--icon-folder-open-image: url('folderopen.svg'); +--icon-folder-closed-image: url('folderclosed.svg'); + +/* brief member declaration list */ +--memdecl-background-color: #F9FAFC; +--memdecl-separator-color: #DEE4F0; +--memdecl-foreground-color: #555; +--memdecl-template-color: #4665A2; + +/* detailed member list */ +--memdef-border-color: #A8B8D9; +--memdef-title-background-color: #E2E8F2; +--memdef-title-gradient-image: url('nav_f.png'); +--memdef-proto-background-color: #DFE5F1; +--memdef-proto-text-color: #253555; +--memdef-proto-text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--memdef-doc-background-color: white; +--memdef-param-name-color: #602020; +--memdef-template-color: #4665A2; + +/* tables */ +--table-cell-border-color: #2D4068; +--table-header-background-color: #374F7F; +--table-header-foreground-color: #FFFFFF; + +/* labels */ +--label-background-color: #728DC1; +--label-left-top-border-color: #5373B4; +--label-right-bottom-border-color: #C4CFE5; +--label-foreground-color: white; + +/** navigation bar/tree/menu */ +--nav-background-color: #F9FAFC; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_b.png'); +--nav-gradient-hover-image: url('tab_h.png'); +--nav-gradient-active-image: url('tab_a.png'); +--nav-gradient-active-image-parent: url("../tab_a.png"); +--nav-separator-image: url('tab_s.png'); +--nav-breadcrumb-image: url('bc_s.png'); +--nav-breadcrumb-border-color: #C2CDE4; +--nav-splitbar-image: url('splitbar.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #283A5D; +--nav-text-hover-color: white; +--nav-text-active-color: white; +--nav-text-normal-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #364D7C; +--nav-menu-background-color: white; +--nav-menu-foreground-color: #555555; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.5); +--nav-arrow-color: #9CAFD4; +--nav-arrow-selected-color: #9CAFD4; + +/* table of contents */ +--toc-background-color: #F4F6FA; +--toc-border-color: #D8DFEE; +--toc-header-color: #4665A2; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + +/** search field */ +--search-background-color: white; +--search-foreground-color: #909090; +--search-magnification-image: url('mag.svg'); +--search-magnification-select-image: url('mag_sel.svg'); +--search-active-color: black; +--search-filter-background-color: #F9FAFC; +--search-filter-foreground-color: black; +--search-filter-border-color: #90A5CE; +--search-filter-highlight-text-color: white; +--search-filter-highlight-bg-color: #3D578C; +--search-results-foreground-color: #425E97; +--search-results-background-color: #EEF1F7; +--search-results-border-color: black; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #555; + +/** code fragments */ +--code-keyword-color: #008000; +--code-type-keyword-color: #604020; +--code-flow-keyword-color: #E08000; +--code-comment-color: #800000; +--code-preprocessor-color: #806020; +--code-string-literal-color: #002080; +--code-char-literal-color: #008080; +--code-xml-cdata-color: black; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #000000; +--code-vhdl-keyword-color: #700070; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #4665A2; +--code-external-link-color: #4665A2; +--fragment-foreground-color: black; +--fragment-background-color: #FBFCFD; +--fragment-border-color: #C4CFE5; +--fragment-lineno-border-color: #00FF00; +--fragment-lineno-background-color: #E8E8E8; +--fragment-lineno-foreground-color: black; +--fragment-lineno-link-fg-color: #4665A2; +--fragment-lineno-link-bg-color: #D8D8D8; +--fragment-lineno-link-hover-fg-color: #4665A2; +--fragment-lineno-link-hover-bg-color: #C8C8C8; +--tooltip-foreground-color: black; +--tooltip-background-color: white; +--tooltip-border-color: gray; +--tooltip-doc-color: grey; +--tooltip-declaration-color: #006318; +--tooltip-link-color: #4665A2; +--tooltip-shadow: 1px 1px 7px gray; +--fold-line-color: #808080; +--fold-minus-image: url('minus.svg'); +--fold-plus-image: url('plus.svg'); +--fold-minus-image-relpath: url('../../minus.svg'); +--fold-plus-image-relpath: url('../../plus.svg'); + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) { + color-scheme: dark; + +/* page base colors */ +--page-background-color: black; +--page-foreground-color: #C9D1D9; +--page-link-color: #90A5CE; +--page-visited-link-color: #A3B4D7; + +/* index */ +--index-odd-item-bg-color: #0B101A; +--index-even-item-bg-color: black; +--index-header-color: #C4CFE5; +--index-separator-color: #334975; + +/* header */ +--header-background-color: #070B11; +--header-separator-color: #141C2E; +--header-gradient-image: url('nav_hd.png'); +--group-header-separator-color: #283A5D; +--group-header-color: #90A5CE; +--inherit-header-color: #A0A0A0; + +--footer-foreground-color: #5B7AB7; +--footer-logo-width: 60px; +--citation-label-color: #90A5CE; +--glow-color: cyan; + +--title-background-color: #090D16; +--title-separator-color: #354C79; +--directory-separator-color: #283A5D; +--separator-color: #283A5D; + +--blockquote-background-color: #101826; +--blockquote-border-color: #283A5D; + +--scrollbar-thumb-color: #283A5D; +--scrollbar-background-color: #070B11; + +--icon-background-color: #334975; +--icon-foreground-color: #C4CFE5; +--icon-doc-image: url('docd.svg'); +--icon-folder-open-image: url('folderopend.svg'); +--icon-folder-closed-image: url('folderclosedd.svg'); + +/* brief member declaration list */ +--memdecl-background-color: #0B101A; +--memdecl-separator-color: #2C3F65; +--memdecl-foreground-color: #BBB; +--memdecl-template-color: #7C95C6; + +/* detailed member list */ +--memdef-border-color: #233250; +--memdef-title-background-color: #1B2840; +--memdef-title-gradient-image: url('nav_fd.png'); +--memdef-proto-background-color: #19243A; +--memdef-proto-text-color: #9DB0D4; +--memdef-proto-text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.9); +--memdef-doc-background-color: black; +--memdef-param-name-color: #D28757; +--memdef-template-color: #7C95C6; + +/* tables */ +--table-cell-border-color: #283A5D; +--table-header-background-color: #283A5D; +--table-header-foreground-color: #C4CFE5; + +/* labels */ +--label-background-color: #354C7B; +--label-left-top-border-color: #4665A2; +--label-right-bottom-border-color: #283A5D; +--label-foreground-color: #CCCCCC; + +/** navigation bar/tree/menu */ +--nav-background-color: #101826; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_bd.png'); +--nav-gradient-hover-image: url('tab_hd.png'); +--nav-gradient-active-image: url('tab_ad.png'); +--nav-gradient-active-image-parent: url("../tab_ad.png"); +--nav-separator-image: url('tab_sd.png'); +--nav-breadcrumb-image: url('bc_sd.png'); +--nav-breadcrumb-border-color: #2A3D61; +--nav-splitbar-image: url('splitbard.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #B6C4DF; +--nav-text-hover-color: #DCE2EF; +--nav-text-active-color: #DCE2EF; +--nav-text-normal-shadow: 0px 1px 1px black; +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #B6C4DF; +--nav-menu-background-color: #05070C; +--nav-menu-foreground-color: #BBBBBB; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.2); +--nav-arrow-color: #334975; +--nav-arrow-selected-color: #90A5CE; + +/* table of contents */ +--toc-background-color: #151E30; +--toc-border-color: #202E4A; +--toc-header-color: #A3B4D7; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + +/** search field */ +--search-background-color: black; +--search-foreground-color: #C5C5C5; +--search-magnification-image: url('mag_d.svg'); +--search-magnification-select-image: url('mag_seld.svg'); +--search-active-color: #C5C5C5; +--search-filter-background-color: #101826; +--search-filter-foreground-color: #90A5CE; +--search-filter-border-color: #7C95C6; +--search-filter-highlight-text-color: #BCC9E2; +--search-filter-highlight-bg-color: #283A5D; +--search-results-background-color: #101826; +--search-results-foreground-color: #90A5CE; +--search-results-border-color: #7C95C6; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #2F436C; + +/** code fragments */ +--code-keyword-color: #CC99CD; +--code-type-keyword-color: #AB99CD; +--code-flow-keyword-color: #E08000; +--code-comment-color: #717790; +--code-preprocessor-color: #65CABE; +--code-string-literal-color: #7EC699; +--code-char-literal-color: #00E0F0; +--code-xml-cdata-color: #C9D1D9; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #C0C0C0; +--code-vhdl-keyword-color: #CF53C9; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #79C0FF; +--code-external-link-color: #79C0FF; +--fragment-foreground-color: #C9D1D9; +--fragment-background-color: black; +--fragment-border-color: #30363D; +--fragment-lineno-border-color: #30363D; +--fragment-lineno-background-color: black; +--fragment-lineno-foreground-color: #6E7681; +--fragment-lineno-link-fg-color: #6E7681; +--fragment-lineno-link-bg-color: #303030; +--fragment-lineno-link-hover-fg-color: #8E96A1; +--fragment-lineno-link-hover-bg-color: #505050; +--tooltip-foreground-color: #C9D1D9; +--tooltip-background-color: #202020; +--tooltip-border-color: #C9D1D9; +--tooltip-doc-color: #D9E1E9; +--tooltip-declaration-color: #20C348; +--tooltip-link-color: #79C0FF; +--tooltip-shadow: none; +--fold-line-color: #808080; +--fold-minus-image: url('minusd.svg'); +--fold-plus-image: url('plusd.svg'); +--fold-minus-image-relpath: url('../../minusd.svg'); +--fold-plus-image-relpath: url('../../plusd.svg'); + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +}} +body { + background-color: var(--page-background-color); + color: var(--page-foreground-color); +} + +body, table, div, p, dl { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 22px; +} + +/* @group Heading Levels */ + +.title { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 28px; + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +h1.groupheader { + font-size: 150%; +} + +h2.groupheader { + border-bottom: 1px solid var(--group-header-separator-color); + color: var(--group-header-color); + font-size: 150%; + font-weight: normal; + margin-top: 1.75em; + padding-top: 8px; + padding-bottom: 4px; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px var(--glow-color); +} + +dt { + font-weight: bold; +} + +p.startli, p.startdd { + margin-top: 2px; +} + +th p.starttd, th p.intertd, th p.endtd { + font-size: 100%; + font-weight: 700; +} + +p.starttd { + margin-top: 0px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +p.interli { +} + +p.interdd { +} + +p.intertd { +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.navtab { + padding-right: 15px; + text-align: right; + line-height: 110%; +} + +div.navtab table { + border-spacing: 0; +} + +td.navtab { + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL { + background-image: var(--nav-gradient-active-image); + background-repeat:repeat-x; + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL a, td.navtabHL a:visited { + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +a.navtab { + font-weight: bold; +} + +div.qindex{ + text-align: center; + width: 100%; + line-height: 140%; + font-size: 130%; + color: var(--index-separator-color); +} + +#main-menu a:focus { + outline: auto; + z-index: 10; + position: relative; +} + +dt.alphachar{ + font-size: 180%; + font-weight: bold; +} + +.alphachar a{ + color: var(--index-header-color); +} + +.alphachar a:hover, .alphachar a:visited{ + text-decoration: none; +} + +.classindex dl { + padding: 25px; + column-count:1 +} + +.classindex dd { + display:inline-block; + margin-left: 50px; + width: 90%; + line-height: 1.15em; +} + +.classindex dl.even { + background-color: var(--index-even-item-bg-color); +} + +.classindex dl.odd { + background-color: var(--index-odd-item-bg-color); +} + +@media(min-width: 1120px) { + .classindex dl { + column-count:2 + } +} + +@media(min-width: 1320px) { + .classindex dl { + column-count:3 + } +} + + +/* @group Link Styling */ + +a { + color: var(--page-link-color); + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: var(--page-visited-link-color); +} + +a:hover { + text-decoration: underline; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code, a.code:visited, a.line, a.line:visited { + color: var(--code-link-color); +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: var(--code-external-link-color); +} + +a.code.hl_class { /* style for links to class names in code snippets */ } +a.code.hl_struct { /* style for links to struct names in code snippets */ } +a.code.hl_union { /* style for links to union names in code snippets */ } +a.code.hl_interface { /* style for links to interface names in code snippets */ } +a.code.hl_protocol { /* style for links to protocol names in code snippets */ } +a.code.hl_category { /* style for links to category names in code snippets */ } +a.code.hl_exception { /* style for links to exception names in code snippets */ } +a.code.hl_service { /* style for links to service names in code snippets */ } +a.code.hl_singleton { /* style for links to singleton names in code snippets */ } +a.code.hl_concept { /* style for links to concept names in code snippets */ } +a.code.hl_namespace { /* style for links to namespace names in code snippets */ } +a.code.hl_package { /* style for links to package names in code snippets */ } +a.code.hl_define { /* style for links to macro names in code snippets */ } +a.code.hl_function { /* style for links to function names in code snippets */ } +a.code.hl_variable { /* style for links to variable names in code snippets */ } +a.code.hl_typedef { /* style for links to typedef names in code snippets */ } +a.code.hl_enumvalue { /* style for links to enum value names in code snippets */ } +a.code.hl_enumeration { /* style for links to enumeration names in code snippets */ } +a.code.hl_signal { /* style for links to Qt signal names in code snippets */ } +a.code.hl_slot { /* style for links to Qt slot names in code snippets */ } +a.code.hl_friend { /* style for links to friend names in code snippets */ } +a.code.hl_dcop { /* style for links to KDE3 DCOP names in code snippets */ } +a.code.hl_property { /* style for links to property names in code snippets */ } +a.code.hl_event { /* style for links to event names in code snippets */ } +a.code.hl_sequence { /* style for links to sequence names in code snippets */ } +a.code.hl_dictionary { /* style for links to dictionary names in code snippets */ } + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +ul { + overflow: visible; +} + +ul.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; + column-count: 3; + list-style-type: none; +} + +#side-nav ul { + overflow: visible; /* reset ul rule for scroll bar in GENERATE_TREEVIEW window */ +} + +#main-nav ul { + overflow: visible; /* reset ul rule for the navigation bar drop down lists */ +} + +.fragment { + text-align: left; + direction: ltr; + overflow-x: auto; /*Fixed: fragment lines overlap floating elements*/ + overflow-y: hidden; +} + +pre.fragment { + border: 1px solid var(--fragment-border-color); + background-color: var(--fragment-background-color); + color: var(--fragment-foreground-color); + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; + font-family: var(--font-family-monospace); + font-size: 105%; +} + +div.fragment { + padding: 0 0 1px 0; /*Fixed: last line underline overlap border*/ + margin: 4px 8px 4px 2px; + color: var(--fragment-foreground-color); + background-color: var(--fragment-background-color); + border: 1px solid var(--fragment-border-color); +} + +div.line { + font-family: var(--font-family-monospace); + font-size: 13px; + min-height: 13px; + line-height: 1.2; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line:after { + content:"\000A"; + white-space: pre; +} + +div.line.glow { + background-color: var(--glow-color); + box-shadow: 0 0 10px var(--glow-color); +} + +span.fold { + margin-left: 5px; + margin-right: 1px; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; + display: inline-block; + width: 12px; + height: 12px; + background-repeat:no-repeat; + background-position:center; +} + +span.lineno { + padding-right: 4px; + margin-right: 9px; + text-align: right; + border-right: 2px solid var(--fragment-lineno-border-color); + color: var(--fragment-lineno-foreground-color); + background-color: var(--fragment-lineno-background-color); + white-space: pre; +} +span.lineno a, span.lineno a:visited { + color: var(--fragment-lineno-link-fg-color); + background-color: var(--fragment-lineno-link-bg-color); +} + +span.lineno a:hover { + color: var(--fragment-lineno-link-hover-fg-color); + background-color: var(--fragment-lineno-link-hover-bg-color); +} + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.classindex ul { + list-style: none; + padding-left: 0; +} + +div.classindex span.ai { + display: inline-block; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + color: var(--page-foreground-color); + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 12px; + margin-right: 8px; +} + +p.formulaDsp { + text-align: center; +} + +img.dark-mode-visible { + display: none; +} +img.light-mode-visible { + display: none; +} + +img.formulaDsp { + +} + +img.formulaInl, img.inline { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; + width: var(--footer-logo-width); +} + +.compoundTemplParams { + color: var(--memdecl-template-color); + font-size: 80%; + line-height: 120%; +} + +/* @group Code Colorization */ + +span.keyword { + color: var(--code-keyword-color); +} + +span.keywordtype { + color: var(--code-type-keyword-color); +} + +span.keywordflow { + color: var(--code-flow-keyword-color); +} + +span.comment { + color: var(--code-comment-color); +} + +span.preprocessor { + color: var(--code-preprocessor-color); +} + +span.stringliteral { + color: var(--code-string-literal-color); +} + +span.charliteral { + color: var(--code-char-literal-color); +} + +span.xmlcdata { + color: var(--code-xml-cdata-color); +} + +span.vhdldigit { + color: var(--code-vhdl-digit-color); +} + +span.vhdlchar { + color: var(--code-vhdl-char-color); +} + +span.vhdlkeyword { + color: var(--code-vhdl-keyword-color); +} + +span.vhdllogic { + color: var(--code-vhdl-logic-color); +} + +blockquote { + background-color: var(--blockquote-background-color); + border-left: 2px solid var(--blockquote-border-color); + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid var(--table-cell-border-color); +} + +th.dirtab { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid var(--separator-color); +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--glow-color); + box-shadow: 0 0 15px var(--glow-color); +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: var(--memdecl-background-color); + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: var(--memdecl-foreground-color); +} + +.memSeparator { + border-bottom: 1px solid var(--memdecl-separator-color); + line-height: 1px; + margin: 0px; + padding: 0px; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight, .memTemplItemRight { + width: 100%; +} + +.memTemplParams { + color: var(--memdecl-template-color); + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 8px; + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + border-top-right-radius: 4px; + border-top-left-radius: 4px; + margin-bottom: -1px; + background-image: var(--memdef-title-gradient-image); + background-repeat: repeat-x; + background-color: var(--memdef-title-background-color); + line-height: 1.25; + font-weight: 300; + float:left; +} + +.permalink +{ + font-size: 65%; + display: inline-block; + vertical-align: middle; +} + +.memtemplate { + font-size: 80%; + color: var(--memdef-template-color); + font-weight: normal; + margin-left: 9px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; +} + +.memitem.glow { + box-shadow: 0 0 15px var(--glow-color); +} + +.memname { + font-weight: 400; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 0px 6px 0px; + color: var(--memdef-proto-text-color); + font-weight: bold; + text-shadow: var(--memdef-proto-text-shadow); + background-color: var(--memdef-proto-background-color); + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 4px; +} + +.overload { + font-family: var(--font-family-monospace); + font-size: 65%; +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 10px 2px 10px; + border-top-width: 0; + background-image:url('nav_g.png'); + background-repeat:repeat-x; + background-color: var(--memdef-doc-background-color); + /* opera specific markup */ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: var(--memdef-param-name-color); + white-space: nowrap; +} +.paramname em { + font-style: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname, .tparams .paramname, .exception .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype, .tparams .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir, .tparams .paramdir { + font-family: var(--font-family-monospace); + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: var(--label-background-color); + border-top:1px solid var(--label-left-top-border-color); + border-left:1px solid var(--label-left-top-border-color); + border-right:1px solid var(--label-right-bottom-border-color); + border-bottom:1px solid var(--label-right-bottom-border-color); + text-shadow: none; + color: var(--label-foreground-color); + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view inside a (index) page */ + +div.directory { + margin: 10px 0px; + border-top: 1px solid var(--directory-separator-color); + border-bottom: 1px solid var(--directory-separator-color); + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline:none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.odd { + padding-left: 6px; + background-color: var(--index-odd-item-bg-color); +} + +.directory tr.even { + padding-left: 6px; + background-color: var(--index-even-item-bg-color); +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: var(--page-link-color); +} + +.arrow { + color: var(--nav-arrow-color); + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 22px; +} + +.icon { + font-family: var(--font-family-icon); + line-height: normal; + font-weight: bold; + font-size: 12px; + height: 14px; + width: 16px; + display: inline-block; + background-color: var(--icon-background-color); + color: var(--icon-foreground-color); + text-align: center; + border-radius: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.icona { + width: 24px; + height: 22px; + display: inline-block; +} + +.iconfopen { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-folder-open-image); + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.iconfclosed { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-folder-closed-image); + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.icondoc { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-doc-image); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: var(--footer-foreground-color); +} + +table.doxtable caption { + caption-side: top; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid var(--memdef-border-color); + border-spacing: 0px; + border-radius: 4px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid var(--memdef-border-color); + border-bottom: 1px solid var(--memdef-border-color); + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--memdef-border-color); +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image: var(--memdef-title-gradient-image); + background-repeat:repeat-x; + background-color: var(--memdef-title-background-color); + font-size: 90%; + color: var(--memdef-proto-text-color); + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + font-weight: 400; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid var(--memdef-border-color); +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: var(--nav-gradient-image); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + background-image: var(--nav-gradient-image); + background-repeat:repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color:var(--nav-text-normal-color); + border:solid 1px var(--nav-breadcrumb-border-color); + overflow:hidden; + margin:0px; + padding:0px; +} + +.navpath li +{ + list-style-type:none; + float:left; + padding-left:10px; + padding-right:15px; + background-image:var(--nav-breadcrumb-image); + background-repeat:no-repeat; + background-position:right; + color: var(--nav-foreground-color); +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; + color: var(--nav-text-normal-color); + font-family: var(--font-family-nav); + text-shadow: var(--nav-text-normal-shadow); + text-decoration: none; +} + +.navpath li.navelem a:hover +{ + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + color: var(--footer-foreground-color); + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +table.classindex +{ + margin: 10px; + white-space: nowrap; + margin-left: 3%; + margin-right: 3%; + width: 94%; + border: 0; + border-spacing: 0; + padding: 0; +} + +div.ingroups +{ + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + background-image: var(--header-gradient-image); + background-repeat:repeat-x; + background-color: var(--header-background-color); + margin: 0px; + border-bottom: 1px solid var(--header-separator-color); +} + +div.headertitle +{ + padding: 5px 5px 5px 10px; +} + +.PageDocRTL-title div.headertitle { + text-align: right; + direction: rtl; +} + +dl { + padding: 0 0 0 0; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug, dl.examples */ +dl.section { + margin-left: 0px; + padding-left: 0px; +} + +dl.note { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #D0C000; +} + +dl.warning, dl.attention { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00D000; +} + +dl.deprecated { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #505050; +} + +dl.todo { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00C0E0; +} + +dl.test { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #3030E0; +} + +dl.bug { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #C08050; +} + +dl.section dd { + margin-bottom: 6px; +} + + +#projectrow +{ + height: 56px; +} + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectalign +{ + vertical-align: middle; + padding-left: 0.5em; +} + +#projectname +{ + font-size: 200%; + font-family: var(--font-family-title); + margin: 0px; + padding: 2px 0px; +} + +#projectbrief +{ + font-size: 90%; + font-family: var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#projectnumber +{ + font-size: 50%; + font-family: 50% var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 1px solid var(--title-separator-color); + background-color: var(--title-background-color); +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.plantumlgraph +{ + text-align: center; +} + +.diagraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:var(--citation-label-color); + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; + text-align:right; + width:52px; +} + +dl.citelist dd { + margin:2px 0 2px 72px; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: var(--toc-background-color); + border: 1px solid var(--toc-border-color); + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 8px 10px 10px; + width: 200px; +} + +div.toc li { + background: var(--toc-down-arrow-image) no-repeat scroll 0 5px transparent; + font: 10px/1.2 var(--font-family-toc); + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 var(--font-family-toc); + color: var(--toc-header-color); + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 15px; +} + +div.toc li.level4 { + margin-left: 15px; +} + +span.emoji { + /* font family used at the site: https://unicode.org/emoji/charts/full-emoji-list.html + * font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort; + */ +} + +span.obfuscator { + display: none; +} + +.inherit_header { + font-weight: bold; + color: var(--inherit-header-color); + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 4px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + /*white-space: nowrap;*/ + color: var(--tooltip-foreground-color); + background-color: var(--tooltip-background-color); + border: 1px solid var(--tooltip-border-color); + border-radius: 4px 4px 4px 4px; + box-shadow: var(--tooltip-shadow); + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: var(--tooltip-doc-color); + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip a { + color: var(--tooltip-link-color); +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: var(--tooltip-declaration-color); +} + +#powerTip div { + margin: 0px; + padding: 0px; + font-size: 12px; + font-family: var(--font-family-tooltip); + line-height: 16px; +} + +#powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.s:after, #powerTip.s:before, +#powerTip.w:after, #powerTip.w:before, +#powerTip.e:after, #powerTip.e:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.nw:after, #powerTip.nw:before, +#powerTip.sw:after, #powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, #powerTip.s:after, +#powerTip.w:after, #powerTip.e:after, +#powerTip.nw:after, #powerTip.ne:after, +#powerTip.sw:after, #powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, #powerTip.s:before, +#powerTip.w:before, #powerTip.e:before, +#powerTip.nw:before, #powerTip.ne:before, +#powerTip.sw:before, #powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.nw:after, #powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { + border-top-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} +#powerTip.n:before, #powerTip.ne:before, #powerTip.nw:before { + border-top-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} +#powerTip.n:after, #powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, #powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, #powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, #powerTip.s:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.sw:after, #powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { + border-bottom-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { + border-bottom-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, #powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, #powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, #powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, #powerTip.e:before { + left: 100%; +} +#powerTip.e:after { + border-left-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.e:before { + border-left-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, #powerTip.w:before { + right: 100%; +} +#powerTip.w:after { + border-right-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.w:before { + border-right-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + +/* @group Markdown */ + +table.markdownTable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, table.markdownTable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.markdownTable tr { +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft, td.markdownTableBodyLeft { + text-align: left +} + +th.markdownTableHeadRight, td.markdownTableBodyRight { + text-align: right +} + +th.markdownTableHeadCenter, td.markdownTableBodyCenter { + text-align: center +} + +tt, code, kbd, samp +{ + display: inline-block; +} +/* @end */ + +u { + text-decoration: underline; +} + +details>summary { + list-style-type: none; +} + +details > summary::-webkit-details-marker { + display: none; +} + +details>summary::before { + content: "\25ba"; + padding-right:4px; + font-size: 80%; +} + +details[open]>summary::before { + content: "\25bc"; + padding-right:4px; + font-size: 80%; +} + +body { + scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-background-color); +} + +::-webkit-scrollbar { + background-color: var(--scrollbar-background-color); + height: 12px; + width: 12px; +} +::-webkit-scrollbar-thumb { + border-radius: 6px; + box-shadow: inset 0 0 12px 12px var(--scrollbar-thumb-color); + border: solid 2px transparent; +} +::-webkit-scrollbar-corner { + background-color: var(--scrollbar-background-color); +} + diff --git a/doc/html/doxygen.svg b/doc/html/doxygen.svg new file mode 100644 index 0000000..79a7635 --- /dev/null +++ b/doc/html/doxygen.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/html/dynsections.js b/doc/html/dynsections.js new file mode 100644 index 0000000..9b28156 --- /dev/null +++ b/doc/html/dynsections.js @@ -0,0 +1,199 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function toggleVisibility(linkObj) +{ + var base = $(linkObj).attr('id'); + var summary = $('#'+base+'-summary'); + var content = $('#'+base+'-content'); + var trigger = $('#'+base+'-trigger'); + var src=$(trigger).attr('src'); + if (content.is(':visible')===true) { + content.hide(); + summary.show(); + $(linkObj).addClass('closed').removeClass('opened'); + $(trigger).attr('src',src.substring(0,src.length-8)+'closed.png'); + } else { + content.show(); + summary.hide(); + $(linkObj).removeClass('closed').addClass('opened'); + $(trigger).attr('src',src.substring(0,src.length-10)+'open.png'); + } + return false; +} + +function updateStripes() +{ + $('table.directory tr'). + removeClass('even').filter(':visible:even').addClass('even'); + $('table.directory tr'). + removeClass('odd').filter(':visible:odd').addClass('odd'); +} + +function toggleLevel(level) +{ + $('table.directory tr').each(function() { + var l = this.id.split('_').length-1; + var i = $('#img'+this.id.substring(3)); + var a = $('#arr'+this.id.substring(3)); + if (l'); + // add vertical lines to other rows + $('span[class=lineno]').not(':eq(0)').append(''); + // add toggle controls to lines with fold divs + $('div[class=foldopen]').each(function() { + // extract specific id to use + var id = $(this).attr('id').replace('foldopen',''); + // extract start and end foldable fragment attributes + var start = $(this).attr('data-start'); + var end = $(this).attr('data-end'); + // replace normal fold span with controls for the first line of a foldable fragment + $(this).find('span[class=fold]:first').replaceWith(''); + // append div for folded (closed) representation + $(this).after(''); + // extract the first line from the "open" section to represent closed content + var line = $(this).children().first().clone(); + // remove any glow that might still be active on the original line + $(line).removeClass('glow'); + if (start) { + // if line already ends with a start marker (e.g. trailing {), remove it + $(line).html($(line).html().replace(new RegExp('\\s*'+start+'\\s*$','g'),'')); + } + // replace minus with plus symbol + $(line).find('span[class=fold]').css('background-image',plusImg[relPath]); + // append ellipsis + $(line).append(' '+start+''+end); + // insert constructed line into closed div + $('#foldclosed'+id).html(line); + }); +} + +/* @license-end */ +$(document).ready(function() { + $('.code,.codeRef').each(function() { + $(this).data('powertip',$('#a'+$(this).attr('href').replace(/.*\//,'').replace(/[^a-z_A-Z0-9]/g,'_')).html()); + $.fn.powerTip.smartPlacementLists.s = [ 's', 'n', 'ne', 'se' ]; + $(this).powerTip({ placement: 's', smartPlacement: true, mouseOnToPopup: true }); + }); +}); diff --git a/doc/html/example_2cuse_8c.html b/doc/html/example_2cuse_8c.html new file mode 100644 index 0000000..2243b29 --- /dev/null +++ b/doc/html/example_2cuse_8c.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/example_2cuse_8c_source.html b/doc/html/example_2cuse_8c_source.html new file mode 100644 index 0000000..15c0a44 --- /dev/null +++ b/doc/html/example_2cuse_8c_source.html @@ -0,0 +1,395 @@ + + + + + + + +libfuse: example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+ +
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/example_2cuse__client_8c.html b/doc/html/example_2cuse__client_8c.html new file mode 100644 index 0000000..743bb0b --- /dev/null +++ b/doc/html/example_2cuse__client_8c.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/example_2cuse__client_8c_source.html b/doc/html/example_2cuse__client_8c_source.html new file mode 100644 index 0000000..6095c35 --- /dev/null +++ b/doc/html/example_2cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/example_2hello_8c.html b/doc/html/example_2hello_8c.html new file mode 100644 index 0000000..ffcdc32 --- /dev/null +++ b/doc/html/example_2hello_8c.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+
fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+
fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/example_2hello_8c_source.html b/doc/html/example_2hello_8c_source.html new file mode 100644 index 0000000..83a47d0 --- /dev/null +++ b/doc/html/example_2hello_8c_source.html @@ -0,0 +1,253 @@ + + + + + + + +libfuse: example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60
+
61 /* Test setting flags the old way */
+
62 fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+
63 fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+
64
+
65 return NULL;
+
66}
+
67
+
68static int hello_getattr(const char *path, struct stat *stbuf,
+
69 struct fuse_file_info *fi)
+
70{
+
71 (void) fi;
+
72 int res = 0;
+
73
+
74 memset(stbuf, 0, sizeof(struct stat));
+
75 if (strcmp(path, "/") == 0) {
+
76 stbuf->st_mode = S_IFDIR | 0755;
+
77 stbuf->st_nlink = 2;
+
78 } else if (strcmp(path+1, options.filename) == 0) {
+
79 stbuf->st_mode = S_IFREG | 0444;
+
80 stbuf->st_nlink = 1;
+
81 stbuf->st_size = strlen(options.contents);
+
82 } else
+
83 res = -ENOENT;
+
84
+
85 return res;
+
86}
+
87
+
88static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
89 off_t offset, struct fuse_file_info *fi,
+
90 enum fuse_readdir_flags flags)
+
91{
+
92 (void) offset;
+
93 (void) fi;
+
94 (void) flags;
+
95
+
96 if (strcmp(path, "/") != 0)
+
97 return -ENOENT;
+
98
+
99 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
100 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
101 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
102
+
103 return 0;
+
104}
+
105
+
106static int hello_open(const char *path, struct fuse_file_info *fi)
+
107{
+
108 if (strcmp(path+1, options.filename) != 0)
+
109 return -ENOENT;
+
110
+
111 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
112 return -EACCES;
+
113
+
114 return 0;
+
115}
+
116
+
117static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
118 struct fuse_file_info *fi)
+
119{
+
120 size_t len;
+
121 (void) fi;
+
122 if(strcmp(path+1, options.filename) != 0)
+
123 return -ENOENT;
+
124
+
125 len = strlen(options.contents);
+
126 if (offset < len) {
+
127 if (offset + size > len)
+
128 size = len - offset;
+
129 memcpy(buf, options.contents + offset, size);
+
130 } else
+
131 size = 0;
+
132
+
133 return size;
+
134}
+
135
+
136static const struct fuse_operations hello_oper = {
+
137 .init = hello_init,
+
138 .getattr = hello_getattr,
+
139 .readdir = hello_readdir,
+
140 .open = hello_open,
+
141 .read = hello_read,
+
142};
+
143
+
144static void show_help(const char *progname)
+
145{
+
146 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
147 printf("File-system specific options:\n"
+
148 " --name=<s> Name of the \"hello\" file\n"
+
149 " (default: \"hello\")\n"
+
150 " --contents=<s> Contents \"hello\" file\n"
+
151 " (default \"Hello, World!\\n\")\n"
+
152 "\n");
+
153}
+
154
+
155int main(int argc, char *argv[])
+
156{
+
157 int ret;
+
158 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
159
+
160 /* Set defaults -- we have to use strdup so that
+
161 fuse_opt_parse can free the defaults if other
+
162 values are specified */
+
163 options.filename = strdup("hello");
+
164 options.contents = strdup("Hello World!\n");
+
165
+
166 /* Parse options */
+
167 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
168 return 1;
+
169
+
170 /* When --help is specified, first print our own file-system
+
171 specific help text, then signal fuse_main to show
+
172 additional help (by adding `--help` to the options again)
+
173 without usage: line (by setting argv[0] to the empty
+
174 string) */
+
175 if (options.show_help) {
+
176 show_help(argv[0]);
+
177 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
178 args.argv[0][0] = '\0';
+
179 }
+
180
+
181 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
182 fuse_opt_free_args(&args);
+
183 return ret;
+
184}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/example_2hello__ll_8c.html b/doc/html/example_2hello__ll_8c.html new file mode 100644 index 0000000..ef509bb --- /dev/null +++ b/doc/html/example_2hello__ll_8c.html @@ -0,0 +1,388 @@ + + + + + + + +libfuse: example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/example_2hello__ll_8c_source.html b/doc/html/example_2hello__ll_8c_source.html new file mode 100644 index 0000000..06409e5 --- /dev/null +++ b/doc/html/example_2hello__ll_8c_source.html @@ -0,0 +1,376 @@ + + + + + + + +libfuse: example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
21#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
22
+
23#include <fuse_lowlevel.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <unistd.h>
+
30#include <assert.h>
+
31
+
32static const char *hello_str = "Hello World!\n";
+
33static const char *hello_name = "hello";
+
34
+
35static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
36{
+
37 stbuf->st_ino = ino;
+
38 switch (ino) {
+
39 case 1:
+
40 stbuf->st_mode = S_IFDIR | 0755;
+
41 stbuf->st_nlink = 2;
+
42 break;
+
43
+
44 case 2:
+
45 stbuf->st_mode = S_IFREG | 0444;
+
46 stbuf->st_nlink = 1;
+
47 stbuf->st_size = strlen(hello_str);
+
48 break;
+
49
+
50 default:
+
51 return -1;
+
52 }
+
53 return 0;
+
54}
+
55
+
56static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
57{
+
58 (void)userdata;
+
59
+
60 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
61 conn->no_interrupt = 1;
+
62
+
63 /* Test setting flags the old way */
+ +
65 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
66}
+
67
+
68static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
69 struct fuse_file_info *fi)
+
70{
+
71 struct stat stbuf;
+
72
+
73 (void) fi;
+
74
+
75 memset(&stbuf, 0, sizeof(stbuf));
+
76 if (hello_stat(ino, &stbuf) == -1)
+
77 fuse_reply_err(req, ENOENT);
+
78 else
+
79 fuse_reply_attr(req, &stbuf, 1.0);
+
80}
+
81
+
82static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
83{
+
84 struct fuse_entry_param e;
+
85
+
86 if (parent != 1 || strcmp(name, hello_name) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else {
+
89 memset(&e, 0, sizeof(e));
+
90 e.ino = 2;
+
91 e.attr_timeout = 1.0;
+
92 e.entry_timeout = 1.0;
+
93 hello_stat(e.ino, &e.attr);
+
94
+
95 fuse_reply_entry(req, &e);
+
96 }
+
97}
+
98
+
99struct dirbuf {
+
100 char *p;
+
101 size_t size;
+
102};
+
103
+
104static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
105 fuse_ino_t ino)
+
106{
+
107 struct stat stbuf;
+
108 size_t oldsize = b->size;
+
109 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
110 b->p = (char *) realloc(b->p, b->size);
+
111 memset(&stbuf, 0, sizeof(stbuf));
+
112 stbuf.st_ino = ino;
+
113 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
114 b->size);
+
115}
+
116
+
117#define min(x, y) ((x) < (y) ? (x) : (y))
+
118
+
119static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
120 off_t off, size_t maxsize)
+
121{
+
122 if (off < bufsize)
+
123 return fuse_reply_buf(req, buf + off,
+
124 min(bufsize - off, maxsize));
+
125 else
+
126 return fuse_reply_buf(req, NULL, 0);
+
127}
+
128
+
129static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
130 off_t off, struct fuse_file_info *fi)
+
131{
+
132 (void) fi;
+
133
+
134 if (ino != 1)
+
135 fuse_reply_err(req, ENOTDIR);
+
136 else {
+
137 struct dirbuf b;
+
138
+
139 memset(&b, 0, sizeof(b));
+
140 dirbuf_add(req, &b, ".", 1);
+
141 dirbuf_add(req, &b, "..", 1);
+
142 dirbuf_add(req, &b, hello_name, 2);
+
143 reply_buf_limited(req, b.p, b.size, off, size);
+
144 free(b.p);
+
145 }
+
146}
+
147
+
148static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
149 struct fuse_file_info *fi)
+
150{
+
151 if (ino != 2)
+
152 fuse_reply_err(req, EISDIR);
+
153 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
154 fuse_reply_err(req, EACCES);
+
155 else
+
156 fuse_reply_open(req, fi);
+
157}
+
158
+
159static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
160 off_t off, struct fuse_file_info *fi)
+
161{
+
162 (void) fi;
+
163
+
164 assert(ino == 2);
+
165 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
166}
+
167
+
168static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
169 size_t size)
+
170{
+
171 (void)size;
+
172 assert(ino == 1 || ino == 2);
+
173 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
174 {
+
175 const char *buf = "hello_ll_getxattr_value";
+
176 fuse_reply_buf(req, buf, strlen(buf));
+
177 }
+
178 else
+
179 {
+
180 fuse_reply_err(req, ENOTSUP);
+
181 }
+
182}
+
183
+
184static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
185 const char *value, size_t size, int flags)
+
186{
+
187 (void)flags;
+
188 (void)size;
+
189 assert(ino == 1 || ino == 2);
+
190 const char* exp_val = "hello_ll_setxattr_value";
+
191 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
192 strlen(exp_val) == size &&
+
193 strncmp(value, exp_val, size) == 0)
+
194 {
+
195 fuse_reply_err(req, 0);
+
196 }
+
197 else
+
198 {
+
199 fuse_reply_err(req, ENOTSUP);
+
200 }
+
201}
+
202
+
203static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
204{
+
205 assert(ino == 1 || ino == 2);
+
206 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
207 {
+
208 fuse_reply_err(req, 0);
+
209 }
+
210 else
+
211 {
+
212 fuse_reply_err(req, ENOTSUP);
+
213 }
+
214}
+
215
+
216static const struct fuse_lowlevel_ops hello_ll_oper = {
+
217 .init = hello_ll_init,
+
218 .lookup = hello_ll_lookup,
+
219 .getattr = hello_ll_getattr,
+
220 .readdir = hello_ll_readdir,
+
221 .open = hello_ll_open,
+
222 .read = hello_ll_read,
+
223 .setxattr = hello_ll_setxattr,
+
224 .getxattr = hello_ll_getxattr,
+
225 .removexattr = hello_ll_removexattr,
+
226};
+
227
+
228int main(int argc, char *argv[])
+
229{
+
230 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
231 struct fuse_session *se;
+
232 struct fuse_cmdline_opts opts;
+
233 struct fuse_loop_config *config;
+
234 int ret = -1;
+
235
+
236 if (fuse_parse_cmdline(&args, &opts) != 0)
+
237 return 1;
+
238 if (opts.show_help) {
+
239 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
242 ret = 0;
+
243 goto err_out1;
+
244 } else if (opts.show_version) {
+
245 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
247 ret = 0;
+
248 goto err_out1;
+
249 }
+
250
+
251 if(opts.mountpoint == NULL) {
+
252 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
253 printf(" %s --help\n", argv[0]);
+
254 ret = 1;
+
255 goto err_out1;
+
256 }
+
257
+
258 se = fuse_session_new(&args, &hello_ll_oper,
+
259 sizeof(hello_ll_oper), NULL);
+
260 if (se == NULL)
+
261 goto err_out1;
+
262
+
263 if (fuse_set_signal_handlers(se) != 0)
+
264 goto err_out2;
+
265
+
266 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
267 goto err_out3;
+
268
+
269 fuse_daemonize(opts.foreground);
+
270
+
271 /* Block until ctrl+c or fusermount -u */
+
272 if (opts.singlethread)
+
273 ret = fuse_session_loop(se);
+
274 else {
+
275 config = fuse_loop_cfg_create();
+
276 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
277 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
278 ret = fuse_session_loop_mt(se, config);
+
279 fuse_loop_cfg_destroy(config);
+
280 config = NULL;
+
281 }
+
282
+ +
284err_out3:
+ +
286err_out2:
+ +
288err_out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291
+
292 return ret ? 1 : 0;
+
293}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/example_2hello__ll__uds_8c.html b/doc/html/example_2hello__ll__uds_8c.html new file mode 100644 index 0000000..2b59894 --- /dev/null +++ b/doc/html/example_2hello__ll__uds_8c.html @@ -0,0 +1,391 @@ + + + + + + + +libfuse: example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/example_2hello__ll__uds_8c_source.html b/doc/html/example_2hello__ll__uds_8c_source.html new file mode 100644 index 0000000..ba5448a --- /dev/null +++ b/doc/html/example_2hello__ll__uds_8c_source.html @@ -0,0 +1,442 @@ + + + + + + + +libfuse: example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %zu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/example_2invalidate__path_8c.html b/doc/html/example_2invalidate__path_8c.html new file mode 100644 index 0000000..e27045a --- /dev/null +++ b/doc/html/example_2invalidate__path_8c.html @@ -0,0 +1,390 @@ + + + + + + + +libfuse: example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/example_2invalidate__path_8c_source.html b/doc/html/example_2invalidate__path_8c_source.html new file mode 100644 index 0000000..c7604a0 --- /dev/null +++ b/doc/html/example_2invalidate__path_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/example_2ioctl_8c.html b/doc/html/example_2ioctl_8c.html new file mode 100644 index 0000000..8ab8538 --- /dev/null +++ b/doc/html/example_2ioctl_8c.html @@ -0,0 +1,294 @@ + + + + + + + +libfuse: example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/example_2ioctl_8c_source.html b/doc/html/example_2ioctl_8c_source.html new file mode 100644 index 0000000..b4f32a4 --- /dev/null +++ b/doc/html/example_2ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/example_2ioctl_8h.html b/doc/html/example_2ioctl_8h.html new file mode 100644 index 0000000..90bf0b8 --- /dev/null +++ b/doc/html/example_2ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/example_2ioctl_8h_source.html b/doc/html/example_2ioctl_8h_source.html new file mode 100644 index 0000000..6b0b4d1 --- /dev/null +++ b/doc/html/example_2ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/example_2ioctl__client_8c.html b/doc/html/example_2ioctl__client_8c.html new file mode 100644 index 0000000..bfad949 --- /dev/null +++ b/doc/html/example_2ioctl__client_8c.html @@ -0,0 +1,138 @@ + + + + + + + +libfuse: example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program tests the ioctl.c example file systsem.
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/example_2ioctl__client_8c_source.html b/doc/html/example_2ioctl__client_8c_source.html new file mode 100644 index 0000000..00b3676 --- /dev/null +++ b/doc/html/example_2ioctl__client_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program tests the ioctl.c example file systsem.
+
7
+
8 This program can be distributed under the terms of the GNU GPLv2.
+
9 See the file COPYING.
+
10*/
+
11
+
24#include <sys/types.h>
+
25#include <fcntl.h>
+
26#include <sys/stat.h>
+
27#include <sys/ioctl.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <ctype.h>
+
31#include <errno.h>
+
32#include <unistd.h>
+
33#include "ioctl.h"
+
34
+
35const char *usage =
+
36"Usage: fioclient FIOC_FILE [size]\n"
+
37"\n"
+
38"Get size if <size> is omitted, set size otherwise\n"
+
39"\n";
+
40
+
41int main(int argc, char **argv)
+
42{
+
43 size_t size;
+
44 int fd;
+
45 int ret = 0;
+
46
+
47 if (argc < 2) {
+
48 fprintf(stderr, "%s", usage);
+
49 return 1;
+
50 }
+
51
+
52 fd = open(argv[1], O_RDWR);
+
53 if (fd < 0) {
+
54 perror("open");
+
55 return 1;
+
56 }
+
57
+
58 if (argc == 2) {
+
59 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
60 perror("ioctl");
+
61 ret = 1;
+
62 goto out;
+
63 }
+
64 printf("%zu\n", size);
+
65 } else {
+
66 size = strtoul(argv[2], NULL, 0);
+
67 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
68 perror("ioctl");
+
69 ret = 1;
+
70 goto out;
+
71 }
+
72 }
+
73out:
+
74 close(fd);
+
75 return ret;
+
76}
+ +
+ + + + diff --git a/doc/html/example_2notify__inval__entry_8c.html b/doc/html/example_2notify__inval__entry_8c.html new file mode 100644 index 0000000..d954a9b --- /dev/null +++ b/doc/html/example_2notify__inval__entry_8c.html @@ -0,0 +1,474 @@ + + + + + + + +libfuse: example/notify_inval_entry.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_entry.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

+

It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

+

To see the effect, first start the file system with the --no-notify

$ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
+

Observe that ls always prints the correct directory contents (since readdir output is not cached)::

$ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+Time_is_15h_48m_33s  current_time
+Time_is_15h_48m_34s  current_time
+Time_is_15h_48m_35s  current_time
+

However, if you try to access a file by name the kernel will report that it still exists:

$ file=$(ls mnt/); echo $file
+Time_is_15h_50m_09s
+$ sleep 5; stat mnt/$file
+  File: ‘mnt/Time_is_15h_50m_09s’
+  Size: 32                Blocks: 0          IO Block: 4096   regular file
+Device: 2ah/42d     Inode: 3           Links: 1
+Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+

Only once the kernel cache timeout has been reached will the stat call fail:

$ sleep 30; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+

In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

$ notify_inval_entry --update-interval=1 --timeout=30 mnt/
+$ file=$(ls mnt/); stat mnt/$file
+  File: ‘mnt/Time_is_20h_42m_11s’
+  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+Device: 2ah/42d     Inode: 2           Links: 1
+Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+$ sleep 1; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+

To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

+

+Compilation

+
gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <signal.h>
+
#include <stddef.h>
+
#include <sys/stat.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
#define MAX_STR_LEN 128
+
static char file_name[MAX_STR_LEN];
+
static fuse_ino_t file_ino = 2;
+
static int lookup_cnt = 0;
+
static pthread_t main_thread;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
float timeout;
+
int update_interval;
+
int only_expire;
+
};
+
static struct options options = {
+
.timeout = 5,
+
.no_notify = 0,
+
.update_interval = 1,
+
.only_expire = 0,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+
OPTION("--timeout=%f", timeout),
+
OPTION("--only-expire", only_expire),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == file_ino) {
+
stbuf->st_mode = S_IFREG | 0000;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = 0;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, file_name) == 0) {
+
e.ino = file_ino;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = options.timeout;
+
e.entry_timeout = options.timeout;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == file_ino)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, options.timeout);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, file_name, file_ino);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
time_t t;
+
struct tm *now;
+
ssize_t ret;
+
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
ret = strftime(file_name, MAX_STR_LEN,
+
"Time_is_%Hh_%Mm_%Ss", now);
+
assert(ret != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
char *old_name;
+
+
+
while(!fuse_session_exited(se)) {
+
old_name = strdup(file_name);
+
update_fs();
+
+
if (!options.no_notify && lookup_cnt) {
+
if(options.only_expire) { // expire entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
+
// no kernel support
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
printf("Exiting...\n");
+
+ +
// Make sure to exit now, rather than on next request from userspace
+
pthread_kill(main_thread, SIGPIPE);
+
+
break;
+
}
+
// 1) ret == 0: successful expire of an existing entry
+
// 2) ret == -ENOENT: kernel has already expired the entry /
+
// entry does not exist anymore in the kernel
+
assert(ret == 0 || ret == -ENOENT);
+
} else { // invalidate entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
}
+
}
+
free(old_name);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --timeout=<secs> Timeout for kernel caches\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
" --only-expire Expire entries instead of invalidating them\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), &se);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
// Needed to ensure that the main thread continues/restarts processing as soon
+
// as the fuse session ends (immediately after calling fuse_session_exit() )
+
// and not only on the next request from userspace
+
main_thread = pthread_self();
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread) {
+
ret = fuse_session_loop(se);
+
} else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_entry.c.

+
+ + + + diff --git a/doc/html/example_2notify__inval__entry_8c_source.html b/doc/html/example_2notify__inval__entry_8c_source.html new file mode 100644 index 0000000..47e2bd8 --- /dev/null +++ b/doc/html/example_2notify__inval__entry_8c_source.html @@ -0,0 +1,424 @@ + + + + + + + +libfuse: example/notify_inval_entry.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_entry.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
79#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
80
+
81#include <fuse_lowlevel.h>
+
82#include <stdio.h>
+
83#include <stdlib.h>
+
84#include <string.h>
+
85#include <errno.h>
+
86#include <fcntl.h>
+
87#include <assert.h>
+
88#include <signal.h>
+
89#include <stddef.h>
+
90#include <sys/stat.h>
+
91#include <unistd.h>
+
92#include <pthread.h>
+
93
+
94#define MAX_STR_LEN 128
+
95static char file_name[MAX_STR_LEN];
+
96static fuse_ino_t file_ino = 2;
+
97static int lookup_cnt = 0;
+
98static pthread_t main_thread;
+
99
+
100/* Command line parsing */
+
101struct options {
+
102 int no_notify;
+
103 float timeout;
+
104 int update_interval;
+
105 int only_expire;
+
106};
+
107static struct options options = {
+
108 .timeout = 5,
+
109 .no_notify = 0,
+
110 .update_interval = 1,
+
111 .only_expire = 0,
+
112};
+
113
+
114#define OPTION(t, p) \
+
115 { t, offsetof(struct options, p), 1 }
+
116static const struct fuse_opt option_spec[] = {
+
117 OPTION("--no-notify", no_notify),
+
118 OPTION("--update-interval=%d", update_interval),
+
119 OPTION("--timeout=%f", timeout),
+
120 OPTION("--only-expire", only_expire),
+ +
122};
+
123
+
124static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
125 stbuf->st_ino = ino;
+
126 if (ino == FUSE_ROOT_ID) {
+
127 stbuf->st_mode = S_IFDIR | 0755;
+
128 stbuf->st_nlink = 1;
+
129 }
+
130
+
131 else if (ino == file_ino) {
+
132 stbuf->st_mode = S_IFREG | 0000;
+
133 stbuf->st_nlink = 1;
+
134 stbuf->st_size = 0;
+
135 }
+
136
+
137 else
+
138 return -1;
+
139
+
140 return 0;
+
141}
+
142
+
143static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
144 (void)userdata;
+
145
+
146 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
147 conn->no_interrupt = 1;
+
148}
+
149
+
150static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
151 const char *name) {
+
152 struct fuse_entry_param e;
+
153 memset(&e, 0, sizeof(e));
+
154
+
155 if (parent != FUSE_ROOT_ID)
+
156 goto err_out;
+
157 else if (strcmp(name, file_name) == 0) {
+
158 e.ino = file_ino;
+
159 lookup_cnt++;
+
160 } else
+
161 goto err_out;
+
162
+
163 e.attr_timeout = options.timeout;
+
164 e.entry_timeout = options.timeout;
+
165 if (tfs_stat(e.ino, &e.attr) != 0)
+
166 goto err_out;
+
167 fuse_reply_entry(req, &e);
+
168 return;
+
169
+
170err_out:
+
171 fuse_reply_err(req, ENOENT);
+
172}
+
173
+
174static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
175 uint64_t nlookup) {
+
176 (void) req;
+
177 if(ino == file_ino)
+
178 lookup_cnt -= nlookup;
+
179 else
+
180 assert(ino == FUSE_ROOT_ID);
+
181 fuse_reply_none(req);
+
182}
+
183
+
184static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
185 struct fuse_file_info *fi) {
+
186 struct stat stbuf;
+
187
+
188 (void) fi;
+
189
+
190 memset(&stbuf, 0, sizeof(stbuf));
+
191 if (tfs_stat(ino, &stbuf) != 0)
+
192 fuse_reply_err(req, ENOENT);
+
193 else
+
194 fuse_reply_attr(req, &stbuf, options.timeout);
+
195}
+
196
+
197struct dirbuf {
+
198 char *p;
+
199 size_t size;
+
200};
+
201
+
202static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
203 fuse_ino_t ino) {
+
204 struct stat stbuf;
+
205 size_t oldsize = b->size;
+
206 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
207 b->p = (char *) realloc(b->p, b->size);
+
208 memset(&stbuf, 0, sizeof(stbuf));
+
209 stbuf.st_ino = ino;
+
210 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
211 b->size);
+
212}
+
213
+
214#define min(x, y) ((x) < (y) ? (x) : (y))
+
215
+
216static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
217 off_t off, size_t maxsize) {
+
218 if (off < bufsize)
+
219 return fuse_reply_buf(req, buf + off,
+
220 min(bufsize - off, maxsize));
+
221 else
+
222 return fuse_reply_buf(req, NULL, 0);
+
223}
+
224
+
225static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
226 off_t off, struct fuse_file_info *fi) {
+
227 (void) fi;
+
228
+
229 if (ino != FUSE_ROOT_ID)
+
230 fuse_reply_err(req, ENOTDIR);
+
231 else {
+
232 struct dirbuf b;
+
233
+
234 memset(&b, 0, sizeof(b));
+
235 dirbuf_add(req, &b, file_name, file_ino);
+
236 reply_buf_limited(req, b.p, b.size, off, size);
+
237 free(b.p);
+
238 }
+
239}
+
240
+
241static const struct fuse_lowlevel_ops tfs_oper = {
+
242 .init = tfs_init,
+
243 .lookup = tfs_lookup,
+
244 .getattr = tfs_getattr,
+
245 .readdir = tfs_readdir,
+
246 .forget = tfs_forget,
+
247};
+
248
+
249static void update_fs(void) {
+
250 time_t t;
+
251 struct tm *now;
+
252 ssize_t ret;
+
253
+
254 t = time(NULL);
+
255 now = localtime(&t);
+
256 assert(now != NULL);
+
257
+
258 ret = strftime(file_name, MAX_STR_LEN,
+
259 "Time_is_%Hh_%Mm_%Ss", now);
+
260 assert(ret != 0);
+
261}
+
262
+
263static void* update_fs_loop(void *data) {
+
264 struct fuse_session *se = (struct fuse_session*) data;
+
265 char *old_name;
+
266
+
267
+
268 while(!fuse_session_exited(se)) {
+
269 old_name = strdup(file_name);
+
270 update_fs();
+
271
+
272 if (!options.no_notify && lookup_cnt) {
+
273 if(options.only_expire) { // expire entry
+ +
275 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
276
+
277 // no kernel support
+
278 if (ret == -ENOSYS) {
+
279 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
280 printf("Exiting...\n");
+
281
+ +
283 // Make sure to exit now, rather than on next request from userspace
+
284 pthread_kill(main_thread, SIGPIPE);
+
285
+
286 break;
+
287 }
+
288 // 1) ret == 0: successful expire of an existing entry
+
289 // 2) ret == -ENOENT: kernel has already expired the entry /
+
290 // entry does not exist anymore in the kernel
+
291 assert(ret == 0 || ret == -ENOENT);
+
292 } else { // invalidate entry
+ +
294 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
295 }
+
296 }
+
297 free(old_name);
+
298 sleep(options.update_interval);
+
299 }
+
300 return NULL;
+
301}
+
302
+
303static void show_help(const char *progname)
+
304{
+
305 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
306 printf("File-system specific options:\n"
+
307 " --timeout=<secs> Timeout for kernel caches\n"
+
308 " --update-interval=<secs> Update-rate of file system contents\n"
+
309 " --no-notify Disable kernel notifications\n"
+
310 " --only-expire Expire entries instead of invalidating them\n"
+
311 "\n");
+
312}
+
313
+
314int main(int argc, char *argv[]) {
+
315 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
316 struct fuse_session *se;
+
317 struct fuse_cmdline_opts opts;
+
318 struct fuse_loop_config *config;
+
319 pthread_t updater;
+
320 int ret = -1;
+
321
+
322 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
323 return 1;
+
324
+
325 if (fuse_parse_cmdline(&args, &opts) != 0)
+
326 return 1;
+
327 if (opts.show_help) {
+
328 show_help(argv[0]);
+ + +
331 ret = 0;
+
332 goto err_out1;
+
333 } else if (opts.show_version) {
+
334 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
336 ret = 0;
+
337 goto err_out1;
+
338 }
+
339
+
340 /* Initial contents */
+
341 update_fs();
+
342
+
343 se = fuse_session_new(&args, &tfs_oper,
+
344 sizeof(tfs_oper), &se);
+
345 if (se == NULL)
+
346 goto err_out1;
+
347
+
348 if (fuse_set_signal_handlers(se) != 0)
+
349 goto err_out2;
+
350
+
351 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
352 goto err_out3;
+
353
+
354 fuse_daemonize(opts.foreground);
+
355
+
356 // Needed to ensure that the main thread continues/restarts processing as soon
+
357 // as the fuse session ends (immediately after calling fuse_session_exit() )
+
358 // and not only on the next request from userspace
+
359 main_thread = pthread_self();
+
360
+
361 /* Start thread to update file contents */
+
362 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
363 if (ret != 0) {
+
364 fprintf(stderr, "pthread_create failed with %s\n",
+
365 strerror(ret));
+
366 goto err_out3;
+
367 }
+
368
+
369 /* Block until ctrl+c or fusermount -u */
+
370 if (opts.singlethread) {
+
371 ret = fuse_session_loop(se);
+
372 } else {
+
373 config = fuse_loop_cfg_create();
+
374 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
375 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
376 ret = fuse_session_loop_mt(se, config);
+
377 fuse_loop_cfg_destroy(config);
+
378 config = NULL;
+
379 }
+
380
+ +
382err_out3:
+ +
384err_out2:
+ +
386err_out1:
+
387 free(opts.mountpoint);
+
388 fuse_opt_free_args(&args);
+
389
+
390 return ret ? 1 : 0;
+
391}
+
392
+
393
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2notify__inval__inode_8c.html b/doc/html/example_2notify__inval__inode_8c.html new file mode 100644 index 0000000..55758dc --- /dev/null +++ b/doc/html/example_2notify__inval__inode_8c.html @@ -0,0 +1,483 @@ + + + + + + + +libfuse: example/notify_inval_inode.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_inode.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

+

To see the effect, first start the file system with the --no-notify option:

+

$ notify_inval_inode –update-interval=1 –no-notify mnt/

+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

 $ notify_inval_inode --update-interval=1 mnt/
+ $ for i in 1 2 3 4 5; do
+ >     cat mnt/current_time
+ >     sleep 1
+ > done
+ The current time is 15:58:40
+ The current time is 15:58:41
+ The current time is 15:58:42
+ The current time is 15:58:43
+ The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
#include <stdatomic.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static size_t file_size;
+
static _Atomic bool is_stop = false;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_destroy(void *userarg)
+
{
+
(void)userarg;
+
+
is_stop = true;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO)
+
fuse_reply_open(req, fi);
+
else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.destroy = tfs_destroy,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
+
while(!is_stop) {
+
update_fs();
+
if (!options.no_notify && lookup_cnt) {
+
/* Only send notification if the kernel is aware of the inode */
+
+
/* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
int ret =
+
fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
if ((ret != 0 && !is_stop) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0) {
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+ +
free(opts.mountpoint);
+
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_inode.c.

+
+ + + + diff --git a/doc/html/example_2notify__inval__inode_8c_source.html b/doc/html/example_2notify__inval__inode_8c_source.html new file mode 100644 index 0000000..fbe7f23 --- /dev/null +++ b/doc/html/example_2notify__inval__inode_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: example/notify_inval_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_inode.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
63
+
64#include <fuse_lowlevel.h>
+
65#include <stdio.h>
+
66#include <stdlib.h>
+
67#include <string.h>
+
68#include <errno.h>
+
69#include <fcntl.h>
+
70#include <assert.h>
+
71#include <stddef.h>
+
72#include <unistd.h>
+
73#include <pthread.h>
+
74#include <stdbool.h>
+
75#include <stdatomic.h>
+
76
+
77/* We can't actually tell the kernel that there is no
+
78 timeout, so we just send a big value */
+
79#define NO_TIMEOUT 500000
+
80
+
81#define MAX_STR_LEN 128
+
82#define FILE_INO 2
+
83#define FILE_NAME "current_time"
+
84static char file_contents[MAX_STR_LEN];
+
85static int lookup_cnt = 0;
+
86static size_t file_size;
+
87static _Atomic bool is_stop = false;
+
88
+
89/* Command line parsing */
+
90struct options {
+
91 int no_notify;
+
92 int update_interval;
+
93};
+
94static struct options options = {
+
95 .no_notify = 0,
+
96 .update_interval = 1,
+
97};
+
98
+
99#define OPTION(t, p) \
+
100 { t, offsetof(struct options, p), 1 }
+
101static const struct fuse_opt option_spec[] = {
+
102 OPTION("--no-notify", no_notify),
+
103 OPTION("--update-interval=%d", update_interval),
+ +
105};
+
106
+
107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
108 stbuf->st_ino = ino;
+
109 if (ino == FUSE_ROOT_ID) {
+
110 stbuf->st_mode = S_IFDIR | 0755;
+
111 stbuf->st_nlink = 1;
+
112 }
+
113
+
114 else if (ino == FILE_INO) {
+
115 stbuf->st_mode = S_IFREG | 0444;
+
116 stbuf->st_nlink = 1;
+
117 stbuf->st_size = file_size;
+
118 }
+
119
+
120 else
+
121 return -1;
+
122
+
123 return 0;
+
124}
+
125
+
126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
127 (void)userdata;
+
128
+
129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
130 conn->no_interrupt = 1;
+
131}
+
132
+
133static void tfs_destroy(void *userarg)
+
134{
+
135 (void)userarg;
+
136
+
137 is_stop = true;
+
138}
+
139
+
140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
141 const char *name) {
+
142 struct fuse_entry_param e;
+
143 memset(&e, 0, sizeof(e));
+
144
+
145 if (parent != FUSE_ROOT_ID)
+
146 goto err_out;
+
147 else if (strcmp(name, FILE_NAME) == 0) {
+
148 e.ino = FILE_INO;
+
149 lookup_cnt++;
+
150 } else
+
151 goto err_out;
+
152
+
153 e.attr_timeout = NO_TIMEOUT;
+
154 e.entry_timeout = NO_TIMEOUT;
+
155 if (tfs_stat(e.ino, &e.attr) != 0)
+
156 goto err_out;
+
157 fuse_reply_entry(req, &e);
+
158 return;
+
159
+
160err_out:
+
161 fuse_reply_err(req, ENOENT);
+
162}
+
163
+
164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
165 uint64_t nlookup) {
+
166 (void) req;
+
167 if(ino == FILE_INO)
+
168 lookup_cnt -= nlookup;
+
169 else
+
170 assert(ino == FUSE_ROOT_ID);
+
171 fuse_reply_none(req);
+
172}
+
173
+
174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
175 struct fuse_file_info *fi) {
+
176 struct stat stbuf;
+
177
+
178 (void) fi;
+
179
+
180 memset(&stbuf, 0, sizeof(stbuf));
+
181 if (tfs_stat(ino, &stbuf) != 0)
+
182 fuse_reply_err(req, ENOENT);
+
183 else
+
184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
185}
+
186
+
187struct dirbuf {
+
188 char *p;
+
189 size_t size;
+
190};
+
191
+
192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
193 fuse_ino_t ino) {
+
194 struct stat stbuf;
+
195 size_t oldsize = b->size;
+
196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
197 b->p = (char *) realloc(b->p, b->size);
+
198 memset(&stbuf, 0, sizeof(stbuf));
+
199 stbuf.st_ino = ino;
+
200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
201 b->size);
+
202}
+
203
+
204#define min(x, y) ((x) < (y) ? (x) : (y))
+
205
+
206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
207 off_t off, size_t maxsize) {
+
208 if (off < bufsize)
+
209 return fuse_reply_buf(req, buf + off,
+
210 min(bufsize - off, maxsize));
+
211 else
+
212 return fuse_reply_buf(req, NULL, 0);
+
213}
+
214
+
215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
216 off_t off, struct fuse_file_info *fi) {
+
217 (void) fi;
+
218
+
219 if (ino != FUSE_ROOT_ID)
+
220 fuse_reply_err(req, ENOTDIR);
+
221 else {
+
222 struct dirbuf b;
+
223
+
224 memset(&b, 0, sizeof(b));
+
225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
226 reply_buf_limited(req, b.p, b.size, off, size);
+
227 free(b.p);
+
228 }
+
229}
+
230
+
231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
232 struct fuse_file_info *fi) {
+
233
+
234 /* Make cache persistent even if file is closed,
+
235 this makes it easier to see the effects */
+
236 fi->keep_cache = 1;
+
237
+
238 if (ino == FUSE_ROOT_ID)
+
239 fuse_reply_err(req, EISDIR);
+
240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
241 fuse_reply_err(req, EACCES);
+
242 else if (ino == FILE_INO)
+
243 fuse_reply_open(req, fi);
+
244 else {
+
245 // This should not happen
+
246 fprintf(stderr, "Got open for non-existing inode!\n");
+
247 fuse_reply_err(req, ENOENT);
+
248 }
+
249}
+
250
+
251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
252 off_t off, struct fuse_file_info *fi) {
+
253 (void) fi;
+
254
+
255 assert(ino == FILE_INO);
+
256 reply_buf_limited(req, file_contents, file_size, off, size);
+
257}
+
258
+
259static const struct fuse_lowlevel_ops tfs_oper = {
+
260 .init = tfs_init,
+
261 .destroy = tfs_destroy,
+
262 .lookup = tfs_lookup,
+
263 .getattr = tfs_getattr,
+
264 .readdir = tfs_readdir,
+
265 .open = tfs_open,
+
266 .read = tfs_read,
+
267 .forget = tfs_forget,
+
268};
+
269
+
270static void update_fs(void) {
+
271 struct tm *now;
+
272 time_t t;
+
273 t = time(NULL);
+
274 now = localtime(&t);
+
275 assert(now != NULL);
+
276
+
277 file_size = strftime(file_contents, MAX_STR_LEN,
+
278 "The current time is %H:%M:%S\n", now);
+
279 assert(file_size != 0);
+
280}
+
281
+
282static void* update_fs_loop(void *data) {
+
283 struct fuse_session *se = (struct fuse_session*) data;
+
284
+
285 while(!is_stop) {
+
286 update_fs();
+
287 if (!options.no_notify && lookup_cnt) {
+
288 /* Only send notification if the kernel is aware of the inode */
+
289
+
290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
291 * might come up during umount, when kernel side already releases
+
292 * all inodes, but does not send FUSE_DESTROY yet.
+
293 */
+
294 int ret =
+
295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
296 if ((ret != 0 && !is_stop) &&
+
297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
298 fprintf(stderr,
+
299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
300 strerror(-ret), -ret);
+
301 abort();
+
302 }
+
303 }
+
304 sleep(options.update_interval);
+
305 }
+
306 return NULL;
+
307}
+
308
+
309static void show_help(const char *progname)
+
310{
+
311 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
312 printf("File-system specific options:\n"
+
313 " --update-interval=<secs> Update-rate of file system contents\n"
+
314 " --no-notify Disable kernel notifications\n"
+
315 "\n");
+
316}
+
317
+
318int main(int argc, char *argv[]) {
+
319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
320 struct fuse_session *se;
+
321 struct fuse_cmdline_opts opts;
+
322 struct fuse_loop_config *config;
+
323 pthread_t updater;
+
324 int ret = -1;
+
325
+
326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
327 return 1;
+
328
+
329 if (fuse_parse_cmdline(&args, &opts) != 0) {
+
330 ret = 1;
+
331 goto err_out1;
+
332 }
+
333
+
334 if (opts.show_help) {
+
335 show_help(argv[0]);
+ + +
338 ret = 0;
+
339 goto err_out1;
+
340 } else if (opts.show_version) {
+
341 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
343 ret = 0;
+
344 goto err_out1;
+
345 }
+
346
+
347 /* Initial contents */
+
348 update_fs();
+
349
+
350 se = fuse_session_new(&args, &tfs_oper,
+
351 sizeof(tfs_oper), NULL);
+
352 if (se == NULL)
+
353 goto err_out1;
+
354
+
355 if (fuse_set_signal_handlers(se) != 0)
+
356 goto err_out2;
+
357
+
358 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
359 goto err_out3;
+
360
+
361 fuse_daemonize(opts.foreground);
+
362
+
363 /* Start thread to update file contents */
+
364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
365 if (ret != 0) {
+
366 fprintf(stderr, "pthread_create failed with %s\n",
+
367 strerror(ret));
+
368 goto err_out3;
+
369 }
+
370
+
371 /* Block until ctrl+c or fusermount -u */
+
372 if (opts.singlethread)
+
373 ret = fuse_session_loop(se);
+
374 else {
+
375 config = fuse_loop_cfg_create();
+
376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
378 ret = fuse_session_loop_mt(se, config);
+
379 fuse_loop_cfg_destroy(config);
+
380 config = NULL;
+
381 }
+
382
+ +
384err_out3:
+ +
386err_out2:
+ +
388err_out1:
+
389 fuse_opt_free_args(&args);
+
390 free(opts.mountpoint);
+
391
+
392 return ret ? 1 : 0;
+
393}
+
394
+
395
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2notify__store__retrieve_8c.html b/doc/html/example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..5305782 --- /dev/null +++ b/doc/html/example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/example_2notify__store__retrieve_8c_source.html b/doc/html/example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..c27615e --- /dev/null +++ b/doc/html/example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2null_8c.html b/doc/html/example_2null_8c.html new file mode 100644 index 0000000..201d41d --- /dev/null +++ b/doc/html/example_2null_8c.html @@ -0,0 +1,763 @@ + + + + + + + +libfuse: example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/example_2null_8c_source.html b/doc/html/example_2null_8c_source.html new file mode 100644 index 0000000..508fc69 --- /dev/null +++ b/doc/html/example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/example_2passthrough_8c.html b/doc/html/example_2passthrough_8c.html new file mode 100644 index 0000000..7d362e5 --- /dev/null +++ b/doc/html/example_2passthrough_8c.html @@ -0,0 +1,663 @@ + + + + + + + +libfuse: example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#ifdef __FreeBSD__
+
#include <sys/socket.h>
+
#include <sys/un.h>
+
#endif
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = -posix_fallocate(fd, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/example_2passthrough_8c_source.html b/doc/html/example_2passthrough_8c_source.html new file mode 100644 index 0000000..1867db2 --- /dev/null +++ b/doc/html/example_2passthrough_8c_source.html @@ -0,0 +1,649 @@ + + + + + + + +libfuse: example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#ifdef __FreeBSD__
+
44#include <sys/socket.h>
+
45#include <sys/un.h>
+
46#endif
+
47#include <sys/time.h>
+
48#ifdef HAVE_SETXATTR
+
49#include <sys/xattr.h>
+
50#endif
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54static int fill_dir_plus = 0;
+
55
+
56static void *xmp_init(struct fuse_conn_info *conn,
+
57 struct fuse_config *cfg)
+
58{
+
59 (void) conn;
+
60 cfg->use_ino = 1;
+
61
+
62 /* parallel_direct_writes feature depends on direct_io features.
+
63 To make parallel_direct_writes valid, need either set cfg->direct_io
+
64 in current function (recommended in high level API) or set fi->direct_io
+
65 in xmp_create() or xmp_open(). */
+
66 // cfg->direct_io = 1;
+ +
68
+
69 /* Pick up changes from lower filesystem right away. This is
+
70 also necessary for better hardlink support. When the kernel
+
71 calls the unlink() handler, it does not know the inode of
+
72 the to-be-removed entry and can therefore not invalidate
+
73 the cache of the associated inode - resulting in an
+
74 incorrect st_nlink value being reported for any remaining
+
75 hardlinks to this inode. */
+
76 if (!cfg->auto_cache) {
+
77 cfg->entry_timeout = 0;
+
78 cfg->attr_timeout = 0;
+
79 cfg->negative_timeout = 0;
+
80 }
+
81
+
82 return NULL;
+
83}
+
84
+
85static int xmp_getattr(const char *path, struct stat *stbuf,
+
86 struct fuse_file_info *fi)
+
87{
+
88 (void) fi;
+
89 int res;
+
90
+
91 res = lstat(path, stbuf);
+
92 if (res == -1)
+
93 return -errno;
+
94
+
95 return 0;
+
96}
+
97
+
98static int xmp_access(const char *path, int mask)
+
99{
+
100 int res;
+
101
+
102 res = access(path, mask);
+
103 if (res == -1)
+
104 return -errno;
+
105
+
106 return 0;
+
107}
+
108
+
109static int xmp_readlink(const char *path, char *buf, size_t size)
+
110{
+
111 int res;
+
112
+
113 res = readlink(path, buf, size - 1);
+
114 if (res == -1)
+
115 return -errno;
+
116
+
117 buf[res] = '\0';
+
118 return 0;
+
119}
+
120
+
121
+
122static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
123 off_t offset, struct fuse_file_info *fi,
+
124 enum fuse_readdir_flags flags)
+
125{
+
126 DIR *dp;
+
127 struct dirent *de;
+
128
+
129 (void) offset;
+
130 (void) fi;
+
131 (void) flags;
+
132
+
133 dp = opendir(path);
+
134 if (dp == NULL)
+
135 return -errno;
+
136
+
137 while ((de = readdir(dp)) != NULL) {
+
138 struct stat st;
+
139 if (fill_dir_plus) {
+
140 fstatat(dirfd(dp), de->d_name, &st,
+
141 AT_SYMLINK_NOFOLLOW);
+
142 } else {
+
143 memset(&st, 0, sizeof(st));
+
144 st.st_ino = de->d_ino;
+
145 st.st_mode = de->d_type << 12;
+
146 }
+
147 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
148 break;
+
149 }
+
150
+
151 closedir(dp);
+
152 return 0;
+
153}
+
154
+
155static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
156{
+
157 int res;
+
158
+
159 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
160 if (res == -1)
+
161 return -errno;
+
162
+
163 return 0;
+
164}
+
165
+
166static int xmp_mkdir(const char *path, mode_t mode)
+
167{
+
168 int res;
+
169
+
170 res = mkdir(path, mode);
+
171 if (res == -1)
+
172 return -errno;
+
173
+
174 return 0;
+
175}
+
176
+
177static int xmp_unlink(const char *path)
+
178{
+
179 int res;
+
180
+
181 res = unlink(path);
+
182 if (res == -1)
+
183 return -errno;
+
184
+
185 return 0;
+
186}
+
187
+
188static int xmp_rmdir(const char *path)
+
189{
+
190 int res;
+
191
+
192 res = rmdir(path);
+
193 if (res == -1)
+
194 return -errno;
+
195
+
196 return 0;
+
197}
+
198
+
199static int xmp_symlink(const char *from, const char *to)
+
200{
+
201 int res;
+
202
+
203 res = symlink(from, to);
+
204 if (res == -1)
+
205 return -errno;
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
211{
+
212 int res;
+
213
+
214 if (flags)
+
215 return -EINVAL;
+
216
+
217 res = rename(from, to);
+
218 if (res == -1)
+
219 return -errno;
+
220
+
221 return 0;
+
222}
+
223
+
224static int xmp_link(const char *from, const char *to)
+
225{
+
226 int res;
+
227
+
228 res = link(from, to);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_chmod(const char *path, mode_t mode,
+
236 struct fuse_file_info *fi)
+
237{
+
238 (void) fi;
+
239 int res;
+
240
+
241 res = chmod(path, mode);
+
242 if (res == -1)
+
243 return -errno;
+
244
+
245 return 0;
+
246}
+
247
+
248static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
249 struct fuse_file_info *fi)
+
250{
+
251 (void) fi;
+
252 int res;
+
253
+
254 res = lchown(path, uid, gid);
+
255 if (res == -1)
+
256 return -errno;
+
257
+
258 return 0;
+
259}
+
260
+
261static int xmp_truncate(const char *path, off_t size,
+
262 struct fuse_file_info *fi)
+
263{
+
264 int res;
+
265
+
266 if (fi != NULL)
+
267 res = ftruncate(fi->fh, size);
+
268 else
+
269 res = truncate(path, size);
+
270 if (res == -1)
+
271 return -errno;
+
272
+
273 return 0;
+
274}
+
275
+
276#ifdef HAVE_UTIMENSAT
+
277static int xmp_utimens(const char *path, const struct timespec ts[2],
+
278 struct fuse_file_info *fi)
+
279{
+
280 (void) fi;
+
281 int res;
+
282
+
283 /* don't use utime/utimes since they follow symlinks */
+
284 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
285 if (res == -1)
+
286 return -errno;
+
287
+
288 return 0;
+
289}
+
290#endif
+
291
+
292static int xmp_create(const char *path, mode_t mode,
+
293 struct fuse_file_info *fi)
+
294{
+
295 int res;
+
296
+
297 res = open(path, fi->flags, mode);
+
298 if (res == -1)
+
299 return -errno;
+
300
+
301 fi->fh = res;
+
302 return 0;
+
303}
+
304
+
305static int xmp_open(const char *path, struct fuse_file_info *fi)
+
306{
+
307 int res;
+
308
+
309 res = open(path, fi->flags);
+
310 if (res == -1)
+
311 return -errno;
+
312
+
313 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
314 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
315 for writes to the same file). */
+
316 if (fi->flags & O_DIRECT) {
+
317 fi->direct_io = 1;
+ +
319 }
+
320
+
321 fi->fh = res;
+
322 return 0;
+
323}
+
324
+
325static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
326 struct fuse_file_info *fi)
+
327{
+
328 int fd;
+
329 int res;
+
330
+
331 if(fi == NULL)
+
332 fd = open(path, O_RDONLY);
+
333 else
+
334 fd = fi->fh;
+
335
+
336 if (fd == -1)
+
337 return -errno;
+
338
+
339 res = pread(fd, buf, size, offset);
+
340 if (res == -1)
+
341 res = -errno;
+
342
+
343 if(fi == NULL)
+
344 close(fd);
+
345 return res;
+
346}
+
347
+
348static int xmp_write(const char *path, const char *buf, size_t size,
+
349 off_t offset, struct fuse_file_info *fi)
+
350{
+
351 int fd;
+
352 int res;
+
353
+
354 (void) fi;
+
355 if(fi == NULL)
+
356 fd = open(path, O_WRONLY);
+
357 else
+
358 fd = fi->fh;
+
359
+
360 if (fd == -1)
+
361 return -errno;
+
362
+
363 res = pwrite(fd, buf, size, offset);
+
364 if (res == -1)
+
365 res = -errno;
+
366
+
367 if(fi == NULL)
+
368 close(fd);
+
369 return res;
+
370}
+
371
+
372static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
373{
+
374 int res;
+
375
+
376 res = statvfs(path, stbuf);
+
377 if (res == -1)
+
378 return -errno;
+
379
+
380 return 0;
+
381}
+
382
+
383static int xmp_release(const char *path, struct fuse_file_info *fi)
+
384{
+
385 (void) path;
+
386 close(fi->fh);
+
387 return 0;
+
388}
+
389
+
390static int xmp_fsync(const char *path, int isdatasync,
+
391 struct fuse_file_info *fi)
+
392{
+
393 /* Just a stub. This method is optional and can safely be left
+
394 unimplemented */
+
395
+
396 (void) path;
+
397 (void) isdatasync;
+
398 (void) fi;
+
399 return 0;
+
400}
+
401
+
402#ifdef HAVE_POSIX_FALLOCATE
+
403static int xmp_fallocate(const char *path, int mode,
+
404 off_t offset, off_t length, struct fuse_file_info *fi)
+
405{
+
406 int fd;
+
407 int res;
+
408
+
409 (void) fi;
+
410
+
411 if (mode)
+
412 return -EOPNOTSUPP;
+
413
+
414 if(fi == NULL)
+
415 fd = open(path, O_WRONLY);
+
416 else
+
417 fd = fi->fh;
+
418
+
419 if (fd == -1)
+
420 return -errno;
+
421
+
422 res = -posix_fallocate(fd, offset, length);
+
423
+
424 if(fi == NULL)
+
425 close(fd);
+
426 return res;
+
427}
+
428#endif
+
429
+
430#ifdef HAVE_SETXATTR
+
431/* xattr operations are optional and can safely be left unimplemented */
+
432static int xmp_setxattr(const char *path, const char *name, const char *value,
+
433 size_t size, int flags)
+
434{
+
435 int res = lsetxattr(path, name, value, size, flags);
+
436 if (res == -1)
+
437 return -errno;
+
438 return 0;
+
439}
+
440
+
441static int xmp_getxattr(const char *path, const char *name, char *value,
+
442 size_t size)
+
443{
+
444 int res = lgetxattr(path, name, value, size);
+
445 if (res == -1)
+
446 return -errno;
+
447 return res;
+
448}
+
449
+
450static int xmp_listxattr(const char *path, char *list, size_t size)
+
451{
+
452 int res = llistxattr(path, list, size);
+
453 if (res == -1)
+
454 return -errno;
+
455 return res;
+
456}
+
457
+
458static int xmp_removexattr(const char *path, const char *name)
+
459{
+
460 int res = lremovexattr(path, name);
+
461 if (res == -1)
+
462 return -errno;
+
463 return 0;
+
464}
+
465#endif /* HAVE_SETXATTR */
+
466
+
467#ifdef HAVE_COPY_FILE_RANGE
+
468static ssize_t xmp_copy_file_range(const char *path_in,
+
469 struct fuse_file_info *fi_in,
+
470 off_t offset_in, const char *path_out,
+
471 struct fuse_file_info *fi_out,
+
472 off_t offset_out, size_t len, int flags)
+
473{
+
474 int fd_in, fd_out;
+
475 ssize_t res;
+
476
+
477 if(fi_in == NULL)
+
478 fd_in = open(path_in, O_RDONLY);
+
479 else
+
480 fd_in = fi_in->fh;
+
481
+
482 if (fd_in == -1)
+
483 return -errno;
+
484
+
485 if(fi_out == NULL)
+
486 fd_out = open(path_out, O_WRONLY);
+
487 else
+
488 fd_out = fi_out->fh;
+
489
+
490 if (fd_out == -1) {
+
491 close(fd_in);
+
492 return -errno;
+
493 }
+
494
+
495 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
496 flags);
+
497 if (res == -1)
+
498 res = -errno;
+
499
+
500 if (fi_out == NULL)
+
501 close(fd_out);
+
502 if (fi_in == NULL)
+
503 close(fd_in);
+
504
+
505 return res;
+
506}
+
507#endif
+
508
+
509static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
510{
+
511 int fd;
+
512 off_t res;
+
513
+
514 if (fi == NULL)
+
515 fd = open(path, O_RDONLY);
+
516 else
+
517 fd = fi->fh;
+
518
+
519 if (fd == -1)
+
520 return -errno;
+
521
+
522 res = lseek(fd, off, whence);
+
523 if (res == -1)
+
524 res = -errno;
+
525
+
526 if (fi == NULL)
+
527 close(fd);
+
528 return res;
+
529}
+
530
+
531static const struct fuse_operations xmp_oper = {
+
532 .init = xmp_init,
+
533 .getattr = xmp_getattr,
+
534 .access = xmp_access,
+
535 .readlink = xmp_readlink,
+
536 .readdir = xmp_readdir,
+
537 .mknod = xmp_mknod,
+
538 .mkdir = xmp_mkdir,
+
539 .symlink = xmp_symlink,
+
540 .unlink = xmp_unlink,
+
541 .rmdir = xmp_rmdir,
+
542 .rename = xmp_rename,
+
543 .link = xmp_link,
+
544 .chmod = xmp_chmod,
+
545 .chown = xmp_chown,
+
546 .truncate = xmp_truncate,
+
547#ifdef HAVE_UTIMENSAT
+
548 .utimens = xmp_utimens,
+
549#endif
+
550 .open = xmp_open,
+
551 .create = xmp_create,
+
552 .read = xmp_read,
+
553 .write = xmp_write,
+
554 .statfs = xmp_statfs,
+
555 .release = xmp_release,
+
556 .fsync = xmp_fsync,
+
557#ifdef HAVE_POSIX_FALLOCATE
+
558 .fallocate = xmp_fallocate,
+
559#endif
+
560#ifdef HAVE_SETXATTR
+
561 .setxattr = xmp_setxattr,
+
562 .getxattr = xmp_getxattr,
+
563 .listxattr = xmp_listxattr,
+
564 .removexattr = xmp_removexattr,
+
565#endif
+
566#ifdef HAVE_COPY_FILE_RANGE
+
567 .copy_file_range = xmp_copy_file_range,
+
568#endif
+
569 .lseek = xmp_lseek,
+
570};
+
571
+
572int main(int argc, char *argv[])
+
573{
+
574 enum { MAX_ARGS = 10 };
+
575 int i,new_argc;
+
576 char *new_argv[MAX_ARGS];
+
577
+
578 umask(0);
+
579 /* Process the "--plus" option apart */
+
580 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
581 if (!strcmp(argv[i], "--plus")) {
+
582 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
583 } else {
+
584 new_argv[new_argc++] = argv[i];
+
585 }
+
586 }
+
587 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
588}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/example_2passthrough__fh_8c.html b/doc/html/example_2passthrough__fh_8c.html new file mode 100644 index 0000000..36a4d54 --- /dev/null +++ b/doc/html/example_2passthrough__fh_8c.html @@ -0,0 +1,766 @@ + + + + + + + +libfuse: example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/example_2passthrough__fh_8c_source.html b/doc/html/example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..ca73b4b --- /dev/null +++ b/doc/html/example_2passthrough__fh_8c_source.html @@ -0,0 +1,751 @@ + + + + + + + +libfuse: example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50static void *xmp_init(struct fuse_conn_info *conn,
+
51 struct fuse_config *cfg)
+
52{
+
53 (void) conn;
+
54 cfg->use_ino = 1;
+
55 cfg->nullpath_ok = 1;
+
56
+
57 /* parallel_direct_writes feature depends on direct_io features.
+
58 To make parallel_direct_writes valid, need either set cfg->direct_io
+
59 in current function (recommended in high level API) or set fi->direct_io
+
60 in xmp_create() or xmp_open(). */
+
61 // cfg->direct_io = 1;
+ +
63
+
64 /* Pick up changes from lower filesystem right away. This is
+
65 also necessary for better hardlink support. When the kernel
+
66 calls the unlink() handler, it does not know the inode of
+
67 the to-be-removed entry and can therefore not invalidate
+
68 the cache of the associated inode - resulting in an
+
69 incorrect st_nlink value being reported for any remaining
+
70 hardlinks to this inode. */
+
71 cfg->entry_timeout = 0;
+
72 cfg->attr_timeout = 0;
+
73 cfg->negative_timeout = 0;
+
74
+
75 return NULL;
+
76}
+
77
+
78static int xmp_getattr(const char *path, struct stat *stbuf,
+
79 struct fuse_file_info *fi)
+
80{
+
81 int res;
+
82
+
83 (void) path;
+
84
+
85 if(fi)
+
86 res = fstat(fi->fh, stbuf);
+
87 else
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118struct xmp_dirp {
+
119 DIR *dp;
+
120 struct dirent *entry;
+
121 off_t offset;
+
122};
+
123
+
124static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
125{
+
126 int res;
+
127 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
128 if (d == NULL)
+
129 return -ENOMEM;
+
130
+
131 d->dp = opendir(path);
+
132 if (d->dp == NULL) {
+
133 res = -errno;
+
134 free(d);
+
135 return res;
+
136 }
+
137 d->offset = 0;
+
138 d->entry = NULL;
+
139
+
140 fi->fh = (unsigned long) d;
+
141 return 0;
+
142}
+
143
+
144static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
145{
+
146 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
147}
+
148
+
149static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
150 off_t offset, struct fuse_file_info *fi,
+
151 enum fuse_readdir_flags flags)
+
152{
+
153 struct xmp_dirp *d = get_dirp(fi);
+
154
+
155 (void) path;
+
156 if (offset != d->offset) {
+
157#ifndef __FreeBSD__
+
158 seekdir(d->dp, offset);
+
159#else
+
160 /* Subtract the one that we add when calling
+
161 telldir() below */
+
162 seekdir(d->dp, offset-1);
+
163#endif
+
164 d->entry = NULL;
+
165 d->offset = offset;
+
166 }
+
167 while (1) {
+
168 struct stat st;
+
169 off_t nextoff;
+ +
171
+
172 if (!d->entry) {
+
173 d->entry = readdir(d->dp);
+
174 if (!d->entry)
+
175 break;
+
176 }
+
177#ifdef HAVE_FSTATAT
+
178 if (flags & FUSE_READDIR_PLUS) {
+
179 int res;
+
180
+
181 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
182 AT_SYMLINK_NOFOLLOW);
+
183 if (res != -1)
+
184 fill_flags |= FUSE_FILL_DIR_PLUS;
+
185 }
+
186#endif
+
187 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
188 memset(&st, 0, sizeof(st));
+
189 st.st_ino = d->entry->d_ino;
+
190 st.st_mode = d->entry->d_type << 12;
+
191 }
+
192 nextoff = telldir(d->dp);
+
193#ifdef __FreeBSD__
+
194 /* Under FreeBSD, telldir() may return 0 the first time
+
195 it is called. But for libfuse, an offset of zero
+
196 means that offsets are not supported, so we shift
+
197 everything by one. */
+
198 nextoff++;
+
199#endif
+
200 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
201 break;
+
202
+
203 d->entry = NULL;
+
204 d->offset = nextoff;
+
205 }
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
211{
+
212 struct xmp_dirp *d = get_dirp(fi);
+
213 (void) path;
+
214 closedir(d->dp);
+
215 free(d);
+
216 return 0;
+
217}
+
218
+
219static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
220{
+
221 int res;
+
222
+
223 if (S_ISFIFO(mode))
+
224 res = mkfifo(path, mode);
+
225 else
+
226 res = mknod(path, mode, rdev);
+
227 if (res == -1)
+
228 return -errno;
+
229
+
230 return 0;
+
231}
+
232
+
233static int xmp_mkdir(const char *path, mode_t mode)
+
234{
+
235 int res;
+
236
+
237 res = mkdir(path, mode);
+
238 if (res == -1)
+
239 return -errno;
+
240
+
241 return 0;
+
242}
+
243
+
244static int xmp_unlink(const char *path)
+
245{
+
246 int res;
+
247
+
248 res = unlink(path);
+
249 if (res == -1)
+
250 return -errno;
+
251
+
252 return 0;
+
253}
+
254
+
255static int xmp_rmdir(const char *path)
+
256{
+
257 int res;
+
258
+
259 res = rmdir(path);
+
260 if (res == -1)
+
261 return -errno;
+
262
+
263 return 0;
+
264}
+
265
+
266static int xmp_symlink(const char *from, const char *to)
+
267{
+
268 int res;
+
269
+
270 res = symlink(from, to);
+
271 if (res == -1)
+
272 return -errno;
+
273
+
274 return 0;
+
275}
+
276
+
277static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
278{
+
279 int res;
+
280
+
281 /* When we have renameat2() in libc, then we can implement flags */
+
282 if (flags)
+
283 return -EINVAL;
+
284
+
285 res = rename(from, to);
+
286 if (res == -1)
+
287 return -errno;
+
288
+
289 return 0;
+
290}
+
291
+
292static int xmp_link(const char *from, const char *to)
+
293{
+
294 int res;
+
295
+
296 res = link(from, to);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 return 0;
+
301}
+
302
+
303static int xmp_chmod(const char *path, mode_t mode,
+
304 struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 if(fi)
+
309 res = fchmod(fi->fh, mode);
+
310 else
+
311 res = chmod(path, mode);
+
312 if (res == -1)
+
313 return -errno;
+
314
+
315 return 0;
+
316}
+
317
+
318static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 int res;
+
322
+
323 if (fi)
+
324 res = fchown(fi->fh, uid, gid);
+
325 else
+
326 res = lchown(path, uid, gid);
+
327 if (res == -1)
+
328 return -errno;
+
329
+
330 return 0;
+
331}
+
332
+
333static int xmp_truncate(const char *path, off_t size,
+
334 struct fuse_file_info *fi)
+
335{
+
336 int res;
+
337
+
338 if(fi)
+
339 res = ftruncate(fi->fh, size);
+
340 else
+
341 res = truncate(path, size);
+
342
+
343 if (res == -1)
+
344 return -errno;
+
345
+
346 return 0;
+
347}
+
348
+
349#ifdef HAVE_UTIMENSAT
+
350static int xmp_utimens(const char *path, const struct timespec ts[2],
+
351 struct fuse_file_info *fi)
+
352{
+
353 int res;
+
354
+
355 /* don't use utime/utimes since they follow symlinks */
+
356 if (fi)
+
357 res = futimens(fi->fh, ts);
+
358 else
+
359 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
360 if (res == -1)
+
361 return -errno;
+
362
+
363 return 0;
+
364}
+
365#endif
+
366
+
367static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
368{
+
369 int fd;
+
370
+
371 fd = open(path, fi->flags, mode);
+
372 if (fd == -1)
+
373 return -errno;
+
374
+
375 fi->fh = fd;
+
376 return 0;
+
377}
+
378
+
379static int xmp_open(const char *path, struct fuse_file_info *fi)
+
380{
+
381 int fd;
+
382
+
383 fd = open(path, fi->flags);
+
384 if (fd == -1)
+
385 return -errno;
+
386
+
387 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
388 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
389 for writes to the same file). */
+
390 if (fi->flags & O_DIRECT) {
+
391 fi->direct_io = 1;
+ +
393 }
+
394
+
395 fi->fh = fd;
+
396 return 0;
+
397}
+
398
+
399static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
400 struct fuse_file_info *fi)
+
401{
+
402 int res;
+
403
+
404 (void) path;
+
405 res = pread(fi->fh, buf, size, offset);
+
406 if (res == -1)
+
407 res = -errno;
+
408
+
409 return res;
+
410}
+
411
+
412static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
413 size_t size, off_t offset, struct fuse_file_info *fi)
+
414{
+
415 struct fuse_bufvec *src;
+
416
+
417 (void) path;
+
418
+
419 src = malloc(sizeof(struct fuse_bufvec));
+
420 if (src == NULL)
+
421 return -ENOMEM;
+
422
+
423 *src = FUSE_BUFVEC_INIT(size);
+
424
+ +
426 src->buf[0].fd = fi->fh;
+
427 src->buf[0].pos = offset;
+
428
+
429 *bufp = src;
+
430
+
431 return 0;
+
432}
+
433
+
434static int xmp_write(const char *path, const char *buf, size_t size,
+
435 off_t offset, struct fuse_file_info *fi)
+
436{
+
437 int res;
+
438
+
439 (void) path;
+
440 res = pwrite(fi->fh, buf, size, offset);
+
441 if (res == -1)
+
442 res = -errno;
+
443
+
444 return res;
+
445}
+
446
+
447static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
448 off_t offset, struct fuse_file_info *fi)
+
449{
+
450 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
451
+
452 (void) path;
+
453
+ +
455 dst.buf[0].fd = fi->fh;
+
456 dst.buf[0].pos = offset;
+
457
+ +
459}
+
460
+
461static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
462{
+
463 int res;
+
464
+
465 res = statvfs(path, stbuf);
+
466 if (res == -1)
+
467 return -errno;
+
468
+
469 return 0;
+
470}
+
471
+
472static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
473{
+
474 int res;
+
475
+
476 (void) path;
+
477 /* This is called from every close on an open file, so call the
+
478 close on the underlying filesystem. But since flush may be
+
479 called multiple times for an open file, this must not really
+
480 close the file. This is important if used on a network
+
481 filesystem like NFS which flush the data/metadata on close() */
+
482 res = close(dup(fi->fh));
+
483 if (res == -1)
+
484 return -errno;
+
485
+
486 return 0;
+
487}
+
488
+
489static int xmp_release(const char *path, struct fuse_file_info *fi)
+
490{
+
491 (void) path;
+
492 close(fi->fh);
+
493
+
494 return 0;
+
495}
+
496
+
497static int xmp_fsync(const char *path, int isdatasync,
+
498 struct fuse_file_info *fi)
+
499{
+
500 int res;
+
501 (void) path;
+
502
+
503#ifndef HAVE_FDATASYNC
+
504 (void) isdatasync;
+
505#else
+
506 if (isdatasync)
+
507 res = fdatasync(fi->fh);
+
508 else
+
509#endif
+
510 res = fsync(fi->fh);
+
511 if (res == -1)
+
512 return -errno;
+
513
+
514 return 0;
+
515}
+
516
+
517#ifdef HAVE_POSIX_FALLOCATE
+
518static int xmp_fallocate(const char *path, int mode,
+
519 off_t offset, off_t length, struct fuse_file_info *fi)
+
520{
+
521 (void) path;
+
522
+
523 if (mode)
+
524 return -EOPNOTSUPP;
+
525
+
526 return -posix_fallocate(fi->fh, offset, length);
+
527}
+
528#endif
+
529
+
530#ifdef HAVE_SETXATTR
+
531/* xattr operations are optional and can safely be left unimplemented */
+
532static int xmp_setxattr(const char *path, const char *name, const char *value,
+
533 size_t size, int flags)
+
534{
+
535 int res = lsetxattr(path, name, value, size, flags);
+
536 if (res == -1)
+
537 return -errno;
+
538 return 0;
+
539}
+
540
+
541static int xmp_getxattr(const char *path, const char *name, char *value,
+
542 size_t size)
+
543{
+
544 int res = lgetxattr(path, name, value, size);
+
545 if (res == -1)
+
546 return -errno;
+
547 return res;
+
548}
+
549
+
550static int xmp_listxattr(const char *path, char *list, size_t size)
+
551{
+
552 int res = llistxattr(path, list, size);
+
553 if (res == -1)
+
554 return -errno;
+
555 return res;
+
556}
+
557
+
558static int xmp_removexattr(const char *path, const char *name)
+
559{
+
560 int res = lremovexattr(path, name);
+
561 if (res == -1)
+
562 return -errno;
+
563 return 0;
+
564}
+
565#endif /* HAVE_SETXATTR */
+
566
+
567#ifdef HAVE_LIBULOCKMGR
+
568static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
569 struct flock *lock)
+
570{
+
571 (void) path;
+
572
+
573 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
574 sizeof(fi->lock_owner));
+
575}
+
576#endif
+
577
+
578static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
579{
+
580 int res;
+
581 (void) path;
+
582
+
583 res = flock(fi->fh, op);
+
584 if (res == -1)
+
585 return -errno;
+
586
+
587 return 0;
+
588}
+
589
+
590#ifdef HAVE_COPY_FILE_RANGE
+
591static ssize_t xmp_copy_file_range(const char *path_in,
+
592 struct fuse_file_info *fi_in,
+
593 off_t off_in, const char *path_out,
+
594 struct fuse_file_info *fi_out,
+
595 off_t off_out, size_t len, int flags)
+
596{
+
597 ssize_t res;
+
598 (void) path_in;
+
599 (void) path_out;
+
600
+
601 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
602 flags);
+
603 if (res == -1)
+
604 return -errno;
+
605
+
606 return res;
+
607}
+
608#endif
+
609
+
610static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
611{
+
612 off_t res;
+
613 (void) path;
+
614
+
615 res = lseek(fi->fh, off, whence);
+
616 if (res == -1)
+
617 return -errno;
+
618
+
619 return res;
+
620}
+
621
+
622static const struct fuse_operations xmp_oper = {
+
623 .init = xmp_init,
+
624 .getattr = xmp_getattr,
+
625 .access = xmp_access,
+
626 .readlink = xmp_readlink,
+
627 .opendir = xmp_opendir,
+
628 .readdir = xmp_readdir,
+
629 .releasedir = xmp_releasedir,
+
630 .mknod = xmp_mknod,
+
631 .mkdir = xmp_mkdir,
+
632 .symlink = xmp_symlink,
+
633 .unlink = xmp_unlink,
+
634 .rmdir = xmp_rmdir,
+
635 .rename = xmp_rename,
+
636 .link = xmp_link,
+
637 .chmod = xmp_chmod,
+
638 .chown = xmp_chown,
+
639 .truncate = xmp_truncate,
+
640#ifdef HAVE_UTIMENSAT
+
641 .utimens = xmp_utimens,
+
642#endif
+
643 .create = xmp_create,
+
644 .open = xmp_open,
+
645 .read = xmp_read,
+
646 .read_buf = xmp_read_buf,
+
647 .write = xmp_write,
+
648 .write_buf = xmp_write_buf,
+
649 .statfs = xmp_statfs,
+
650 .flush = xmp_flush,
+
651 .release = xmp_release,
+
652 .fsync = xmp_fsync,
+
653#ifdef HAVE_POSIX_FALLOCATE
+
654 .fallocate = xmp_fallocate,
+
655#endif
+
656#ifdef HAVE_SETXATTR
+
657 .setxattr = xmp_setxattr,
+
658 .getxattr = xmp_getxattr,
+
659 .listxattr = xmp_listxattr,
+
660 .removexattr = xmp_removexattr,
+
661#endif
+
662#ifdef HAVE_LIBULOCKMGR
+
663 .lock = xmp_lock,
+
664#endif
+
665 .flock = xmp_flock,
+
666#ifdef HAVE_COPY_FILE_RANGE
+
667 .copy_file_range = xmp_copy_file_range,
+
668#endif
+
669 .lseek = xmp_lseek,
+
670};
+
671
+
672int main(int argc, char *argv[])
+
673{
+
674 umask(0);
+
675 return fuse_main(argc, argv, &xmp_oper, NULL);
+
676}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/example_2passthrough__helpers_8h_source.html b/doc/html/example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..d0a8a59 --- /dev/null +++ b/doc/html/example_2passthrough__helpers_8h_source.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26/*
+
27 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
28 * operation
+
29 */
+
30static int mknod_wrapper(int dirfd, const char *path, const char *link,
+
31 int mode, dev_t rdev)
+
32{
+
33 int res;
+
34
+
35 if (S_ISREG(mode)) {
+
36 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
37 if (res >= 0)
+
38 res = close(res);
+
39 } else if (S_ISDIR(mode)) {
+
40 res = mkdirat(dirfd, path, mode);
+
41 } else if (S_ISLNK(mode) && link != NULL) {
+
42 res = symlinkat(link, dirfd, path);
+
43 } else if (S_ISFIFO(mode)) {
+
44 res = mkfifoat(dirfd, path, mode);
+
45#ifdef __FreeBSD__
+
46 } else if (S_ISSOCK(mode)) {
+
47 struct sockaddr_un su;
+
48 int fd;
+
49
+
50 if (strlen(path) >= sizeof(su.sun_path)) {
+
51 errno = ENAMETOOLONG;
+
52 return -1;
+
53 }
+
54 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
55 if (fd >= 0) {
+
56 /*
+
57 * We must bind the socket to the underlying file
+
58 * system to create the socket file, even though
+
59 * we'll never listen on this socket.
+
60 */
+
61 su.sun_family = AF_UNIX;
+
62 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
63 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
64 sizeof(su));
+
65 if (res == 0)
+
66 close(fd);
+
67 } else {
+
68 res = -1;
+
69 }
+
70#endif
+
71 } else {
+
72 res = mknodat(dirfd, path, mode, rdev);
+
73 }
+
74
+
75 return res;
+
76}
+
+ + + + diff --git a/doc/html/example_2passthrough__ll_8c.html b/doc/html/example_2passthrough__ll_8c.html new file mode 100644 index 0000000..6a9a808 --- /dev/null +++ b/doc/html/example_2passthrough__ll_8c.html @@ -0,0 +1,1529 @@ + + + + + + + +libfuse: example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three, but many operations are not implemented. In particular, it is not possible to remove files (or directories) because the code necessary to defer actual removal until the file is not opened anymore would make the example much more complicated.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
ino, out_buf.buf[0].size, (unsigned long) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err = EOPNOTSUPP;
+
(void) ino;
+
+
#ifdef HAVE_FALLOCATE
+
err = fallocate(fi->fh, mode, offset, length);
+
if (err < 0)
+
err = errno;
+
+
#elif defined(HAVE_POSIX_FALLOCATE)
+
if (mode) {
+
fuse_reply_err(req, EOPNOTSUPP);
+
return;
+
}
+
+
err = posix_fallocate(fi->fh, offset, length);
+
#endif
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, size=%zd, flags=0x%x)\n",
+
ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/example_2passthrough__ll_8c_source.html b/doc/html/example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..521c10c --- /dev/null +++ b/doc/html/example_2passthrough__ll_8c_source.html @@ -0,0 +1,1508 @@ + + + + + + + +libfuse: example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
37#define _GNU_SOURCE
+
38#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
39
+
40#include <fuse_lowlevel.h>
+
41#include <unistd.h>
+
42#include <stdlib.h>
+
43#include <stdio.h>
+
44#include <stddef.h>
+
45#include <stdbool.h>
+
46#include <string.h>
+
47#include <limits.h>
+
48#include <dirent.h>
+
49#include <assert.h>
+
50#include <errno.h>
+
51#include <inttypes.h>
+
52#include <pthread.h>
+
53#include <sys/file.h>
+
54#include <sys/xattr.h>
+
55
+
56#include "passthrough_helpers.h"
+
57
+
58/* We are re-using pointers to our `struct lo_inode` and `struct
+
59 lo_dirp` elements as inodes. This means that we must be able to
+
60 store uintptr_t values in a fuse_ino_t variable. The following
+
61 incantation checks this condition at compile time. */
+
62#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
63_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
64 "fuse_ino_t too small to hold uintptr_t values!");
+
65#else
+
66struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
67 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
68 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
69#endif
+
70
+
71struct lo_inode {
+
72 struct lo_inode *next; /* protected by lo->mutex */
+
73 struct lo_inode *prev; /* protected by lo->mutex */
+
74 int fd;
+
75 ino_t ino;
+
76 dev_t dev;
+
77 uint64_t refcount; /* protected by lo->mutex */
+
78};
+
79
+
80enum {
+
81 CACHE_NEVER,
+
82 CACHE_NORMAL,
+
83 CACHE_ALWAYS,
+
84};
+
85
+
86struct lo_data {
+
87 pthread_mutex_t mutex;
+
88 int debug;
+
89 int writeback;
+
90 int flock;
+
91 int xattr;
+
92 char *source;
+
93 double timeout;
+
94 int cache;
+
95 int timeout_set;
+
96 struct lo_inode root; /* protected by lo->mutex */
+
97};
+
98
+
99static const struct fuse_opt lo_opts[] = {
+
100 { "writeback",
+
101 offsetof(struct lo_data, writeback), 1 },
+
102 { "no_writeback",
+
103 offsetof(struct lo_data, writeback), 0 },
+
104 { "source=%s",
+
105 offsetof(struct lo_data, source), 0 },
+
106 { "flock",
+
107 offsetof(struct lo_data, flock), 1 },
+
108 { "no_flock",
+
109 offsetof(struct lo_data, flock), 0 },
+
110 { "xattr",
+
111 offsetof(struct lo_data, xattr), 1 },
+
112 { "no_xattr",
+
113 offsetof(struct lo_data, xattr), 0 },
+
114 { "timeout=%lf",
+
115 offsetof(struct lo_data, timeout), 0 },
+
116 { "timeout=",
+
117 offsetof(struct lo_data, timeout_set), 1 },
+
118 { "cache=never",
+
119 offsetof(struct lo_data, cache), CACHE_NEVER },
+
120 { "cache=auto",
+
121 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
122 { "cache=always",
+
123 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
124
+ +
126};
+
127
+
128static void passthrough_ll_help(void)
+
129{
+
130 printf(
+
131" -o writeback Enable writeback\n"
+
132" -o no_writeback Disable write back\n"
+
133" -o source=/home/dir Source directory to be mounted\n"
+
134" -o flock Enable flock\n"
+
135" -o no_flock Disable flock\n"
+
136" -o xattr Enable xattr\n"
+
137" -o no_xattr Disable xattr\n"
+
138" -o timeout=1.0 Caching timeout\n"
+
139" -o timeout=0/1 Timeout is set\n"
+
140" -o cache=never Disable cache\n"
+
141" -o cache=auto Auto enable cache\n"
+
142" -o cache=always Cache always\n");
+
143}
+
144
+
145static struct lo_data *lo_data(fuse_req_t req)
+
146{
+
147 return (struct lo_data *) fuse_req_userdata(req);
+
148}
+
149
+
150static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
151{
+
152 if (ino == FUSE_ROOT_ID)
+
153 return &lo_data(req)->root;
+
154 else
+
155 return (struct lo_inode *) (uintptr_t) ino;
+
156}
+
157
+
158static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
159{
+
160 return lo_inode(req, ino)->fd;
+
161}
+
162
+
163static bool lo_debug(fuse_req_t req)
+
164{
+
165 return lo_data(req)->debug != 0;
+
166}
+
167
+
168static void lo_init(void *userdata,
+
169 struct fuse_conn_info *conn)
+
170{
+
171 struct lo_data *lo = (struct lo_data *)userdata;
+
172 bool has_flag;
+
173
+
174 if (lo->writeback) {
+
175 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
176 if (lo->debug && has_flag)
+
177 fuse_log(FUSE_LOG_DEBUG,
+
178 "lo_init: activating writeback\n");
+
179 }
+
180 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
181 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
182 if (lo->debug && has_flag)
+
183 fuse_log(FUSE_LOG_DEBUG,
+
184 "lo_init: activating flock locks\n");
+
185 }
+
186
+
187 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
188 conn->no_interrupt = 1;
+
189}
+
190
+
191static void lo_destroy(void *userdata)
+
192{
+
193 struct lo_data *lo = (struct lo_data*) userdata;
+
194
+
195 while (lo->root.next != &lo->root) {
+
196 struct lo_inode* next = lo->root.next;
+
197 lo->root.next = next->next;
+
198 close(next->fd);
+
199 free(next);
+
200 }
+
201}
+
202
+
203static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
204 struct fuse_file_info *fi)
+
205{
+
206 int res;
+
207 struct stat buf;
+
208 struct lo_data *lo = lo_data(req);
+
209 int fd = fi ? fi->fh : lo_fd(req, ino);
+
210
+
211 (void) fi;
+
212
+
213 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
214 if (res == -1)
+
215 return (void) fuse_reply_err(req, errno);
+
216
+
217 fuse_reply_attr(req, &buf, lo->timeout);
+
218}
+
219
+
220static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
221 int valid, struct fuse_file_info *fi)
+
222{
+
223 int saverr;
+
224 char procname[64];
+
225 struct lo_inode *inode = lo_inode(req, ino);
+
226 int ifd = inode->fd;
+
227 int res;
+
228
+
229 if (valid & FUSE_SET_ATTR_MODE) {
+
230 if (fi) {
+
231 res = fchmod(fi->fh, attr->st_mode);
+
232 } else {
+
233 sprintf(procname, "/proc/self/fd/%i", ifd);
+
234 res = chmod(procname, attr->st_mode);
+
235 }
+
236 if (res == -1)
+
237 goto out_err;
+
238 }
+
239 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
240 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
241 attr->st_uid : (uid_t) -1;
+
242 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
243 attr->st_gid : (gid_t) -1;
+
244
+
245 res = fchownat(ifd, "", uid, gid,
+
246 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
247 if (res == -1)
+
248 goto out_err;
+
249 }
+
250 if (valid & FUSE_SET_ATTR_SIZE) {
+
251 if (fi) {
+
252 res = ftruncate(fi->fh, attr->st_size);
+
253 } else {
+
254 sprintf(procname, "/proc/self/fd/%i", ifd);
+
255 res = truncate(procname, attr->st_size);
+
256 }
+
257 if (res == -1)
+
258 goto out_err;
+
259 }
+
260 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
261 struct timespec tv[2];
+
262
+
263 tv[0].tv_sec = 0;
+
264 tv[1].tv_sec = 0;
+
265 tv[0].tv_nsec = UTIME_OMIT;
+
266 tv[1].tv_nsec = UTIME_OMIT;
+
267
+
268 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
269 tv[0].tv_nsec = UTIME_NOW;
+
270 else if (valid & FUSE_SET_ATTR_ATIME)
+
271 tv[0] = attr->st_atim;
+
272
+
273 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
274 tv[1].tv_nsec = UTIME_NOW;
+
275 else if (valid & FUSE_SET_ATTR_MTIME)
+
276 tv[1] = attr->st_mtim;
+
277
+
278 if (fi)
+
279 res = futimens(fi->fh, tv);
+
280 else {
+
281 sprintf(procname, "/proc/self/fd/%i", ifd);
+
282 res = utimensat(AT_FDCWD, procname, tv, 0);
+
283 }
+
284 if (res == -1)
+
285 goto out_err;
+
286 }
+
287
+
288 return lo_getattr(req, ino, fi);
+
289
+
290out_err:
+
291 saverr = errno;
+
292 fuse_reply_err(req, saverr);
+
293}
+
294
+
295static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
296{
+
297 struct lo_inode *p;
+
298 struct lo_inode *ret = NULL;
+
299
+
300 pthread_mutex_lock(&lo->mutex);
+
301 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
302 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
303 assert(p->refcount > 0);
+
304 ret = p;
+
305 ret->refcount++;
+
306 break;
+
307 }
+
308 }
+
309 pthread_mutex_unlock(&lo->mutex);
+
310 return ret;
+
311}
+
312
+
313
+
314static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
315{
+
316 struct lo_inode *inode = NULL;
+
317 struct lo_inode *prev, *next;
+
318
+
319 inode = calloc(1, sizeof(struct lo_inode));
+
320 if (!inode)
+
321 return NULL;
+
322
+
323 inode->refcount = 1;
+
324 inode->fd = fd;
+
325 inode->ino = e->attr.st_ino;
+
326 inode->dev = e->attr.st_dev;
+
327
+
328 pthread_mutex_lock(&lo->mutex);
+
329 prev = &lo->root;
+
330 next = prev->next;
+
331 next->prev = inode;
+
332 inode->next = next;
+
333 inode->prev = prev;
+
334 prev->next = inode;
+
335 pthread_mutex_unlock(&lo->mutex);
+
336 return inode;
+
337}
+
338
+
339static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
340{
+
341 int res;
+
342 struct lo_data *lo = lo_data(req);
+
343
+
344 memset(e, 0, sizeof(*e));
+
345 e->attr_timeout = lo->timeout;
+
346 e->entry_timeout = lo->timeout;
+
347
+
348 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
349 if (res == -1)
+
350 return errno;
+
351
+
352 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
353
+
354 if (lo_debug(req))
+
355 fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
356 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
357
+
358 return 0;
+
359
+
360}
+
361
+
362static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
363 struct fuse_entry_param *e)
+
364{
+
365 int newfd;
+
366 int res;
+
367 int saverr;
+
368 struct lo_data *lo = lo_data(req);
+
369 struct lo_inode *inode;
+
370
+
371 memset(e, 0, sizeof(*e));
+
372 e->attr_timeout = lo->timeout;
+
373 e->entry_timeout = lo->timeout;
+
374
+
375 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
376 if (newfd == -1)
+
377 goto out_err;
+
378
+
379 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
380 if (res == -1)
+
381 goto out_err;
+
382
+
383 inode = lo_find(lo_data(req), &e->attr);
+
384 if (inode) {
+
385 close(newfd);
+
386 newfd = -1;
+
387 } else {
+
388 inode = create_new_inode(newfd, e, lo);
+
389 if (!inode)
+
390 goto out_err;
+
391 }
+
392 e->ino = (uintptr_t) inode;
+
393
+
394 if (lo_debug(req))
+
395 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
396 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
397
+
398 return 0;
+
399
+
400out_err:
+
401 saverr = errno;
+
402 if (newfd != -1)
+
403 close(newfd);
+
404 return saverr;
+
405}
+
406
+
407static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
408{
+
409 struct fuse_entry_param e;
+
410 int err;
+
411
+
412 if (lo_debug(req))
+
413 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
414 parent, name);
+
415
+
416 err = lo_do_lookup(req, parent, name, &e);
+
417 if (err)
+
418 fuse_reply_err(req, err);
+
419 else
+
420 fuse_reply_entry(req, &e);
+
421}
+
422
+
423static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
424 const char *name, mode_t mode, dev_t rdev,
+
425 const char *link)
+
426{
+
427 int res;
+
428 int saverr;
+
429 struct lo_inode *dir = lo_inode(req, parent);
+
430 struct fuse_entry_param e;
+
431
+
432 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
433
+
434 saverr = errno;
+
435 if (res == -1)
+
436 goto out;
+
437
+
438 saverr = lo_do_lookup(req, parent, name, &e);
+
439 if (saverr)
+
440 goto out;
+
441
+
442 if (lo_debug(req))
+
443 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
444 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
445
+
446 fuse_reply_entry(req, &e);
+
447 return;
+
448
+
449out:
+
450 fuse_reply_err(req, saverr);
+
451}
+
452
+
453static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
454 const char *name, mode_t mode, dev_t rdev)
+
455{
+
456 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
457}
+
458
+
459static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
460 mode_t mode)
+
461{
+
462 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
463}
+
464
+
465static void lo_symlink(fuse_req_t req, const char *link,
+
466 fuse_ino_t parent, const char *name)
+
467{
+
468 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
469}
+
470
+
471static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
472 const char *name)
+
473{
+
474 int res;
+
475 struct lo_data *lo = lo_data(req);
+
476 struct lo_inode *inode = lo_inode(req, ino);
+
477 struct fuse_entry_param e;
+
478 char procname[64];
+
479 int saverr;
+
480
+
481 memset(&e, 0, sizeof(struct fuse_entry_param));
+
482 e.attr_timeout = lo->timeout;
+
483 e.entry_timeout = lo->timeout;
+
484
+
485 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
486 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
487 AT_SYMLINK_FOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
492 if (res == -1)
+
493 goto out_err;
+
494
+
495 pthread_mutex_lock(&lo->mutex);
+
496 inode->refcount++;
+
497 pthread_mutex_unlock(&lo->mutex);
+
498 e.ino = (uintptr_t) inode;
+
499
+
500 if (lo_debug(req))
+
501 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
502 (unsigned long long) parent, name,
+
503 (unsigned long long) e.ino);
+
504
+
505 fuse_reply_entry(req, &e);
+
506 return;
+
507
+
508out_err:
+
509 saverr = errno;
+
510 fuse_reply_err(req, saverr);
+
511}
+
512
+
513static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
514{
+
515 int res;
+
516
+
517 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
518
+
519 fuse_reply_err(req, res == -1 ? errno : 0);
+
520}
+
521
+
522static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
523 fuse_ino_t newparent, const char *newname,
+
524 unsigned int flags)
+
525{
+
526 int res;
+
527
+
528 if (flags) {
+
529 fuse_reply_err(req, EINVAL);
+
530 return;
+
531 }
+
532
+
533 res = renameat(lo_fd(req, parent), name,
+
534 lo_fd(req, newparent), newname);
+
535
+
536 fuse_reply_err(req, res == -1 ? errno : 0);
+
537}
+
538
+
539static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
540{
+
541 int res;
+
542
+
543 res = unlinkat(lo_fd(req, parent), name, 0);
+
544
+
545 fuse_reply_err(req, res == -1 ? errno : 0);
+
546}
+
547
+
548static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
549{
+
550 if (!inode)
+
551 return;
+
552
+
553 pthread_mutex_lock(&lo->mutex);
+
554 assert(inode->refcount >= n);
+
555 inode->refcount -= n;
+
556 if (!inode->refcount) {
+
557 struct lo_inode *prev, *next;
+
558
+
559 prev = inode->prev;
+
560 next = inode->next;
+
561 next->prev = prev;
+
562 prev->next = next;
+
563
+
564 pthread_mutex_unlock(&lo->mutex);
+
565 close(inode->fd);
+
566 free(inode);
+
567
+
568 } else {
+
569 pthread_mutex_unlock(&lo->mutex);
+
570 }
+
571}
+
572
+
573static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
574{
+
575 struct lo_data *lo = lo_data(req);
+
576 struct lo_inode *inode = lo_inode(req, ino);
+
577
+
578 if (lo_debug(req)) {
+
579 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
580 (unsigned long long) ino,
+
581 (unsigned long long) inode->refcount,
+
582 (unsigned long long) nlookup);
+
583 }
+
584
+
585 unref_inode(lo, inode, nlookup);
+
586}
+
587
+
588static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
589{
+
590 lo_forget_one(req, ino, nlookup);
+
591 fuse_reply_none(req);
+
592}
+
593
+
594static void lo_forget_multi(fuse_req_t req, size_t count,
+
595 struct fuse_forget_data *forgets)
+
596{
+
597 int i;
+
598
+
599 for (i = 0; i < count; i++)
+
600 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
601 fuse_reply_none(req);
+
602}
+
603
+
604static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
605{
+
606 char buf[PATH_MAX + 1];
+
607 int res;
+
608
+
609 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
610 if (res == -1)
+
611 return (void) fuse_reply_err(req, errno);
+
612
+
613 if (res == sizeof(buf))
+
614 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
615
+
616 buf[res] = '\0';
+
617
+
618 fuse_reply_readlink(req, buf);
+
619}
+
620
+
621struct lo_dirp {
+
622 DIR *dp;
+
623 struct dirent *entry;
+
624 off_t offset;
+
625};
+
626
+
627static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
628{
+
629 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
630}
+
631
+
632static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
633{
+
634 int error = ENOMEM;
+
635 struct lo_data *lo = lo_data(req);
+
636 struct lo_dirp *d;
+
637 int fd = -1;
+
638
+
639 d = calloc(1, sizeof(struct lo_dirp));
+
640 if (d == NULL)
+
641 goto out_err;
+
642
+
643 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
644 if (fd == -1)
+
645 goto out_errno;
+
646
+
647 d->dp = fdopendir(fd);
+
648 if (d->dp == NULL)
+
649 goto out_errno;
+
650
+
651 d->offset = 0;
+
652 d->entry = NULL;
+
653
+
654 fi->fh = (uintptr_t) d;
+
655 if (lo->cache != CACHE_NEVER)
+
656 fi->cache_readdir = 1;
+
657 if (lo->cache == CACHE_ALWAYS)
+
658 fi->keep_cache = 1;
+
659 fuse_reply_open(req, fi);
+
660 return;
+
661
+
662out_errno:
+
663 error = errno;
+
664out_err:
+
665 if (d) {
+
666 if (fd != -1)
+
667 close(fd);
+
668 free(d);
+
669 }
+
670 fuse_reply_err(req, error);
+
671}
+
672
+
673static int is_dot_or_dotdot(const char *name)
+
674{
+
675 return name[0] == '.' && (name[1] == '\0' ||
+
676 (name[1] == '.' && name[2] == '\0'));
+
677}
+
678
+
679static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
680 off_t offset, struct fuse_file_info *fi, int plus)
+
681{
+
682 struct lo_dirp *d = lo_dirp(fi);
+
683 char *buf;
+
684 char *p;
+
685 size_t rem = size;
+
686 int err;
+
687
+
688 (void) ino;
+
689
+
690 buf = calloc(1, size);
+
691 if (!buf) {
+
692 err = ENOMEM;
+
693 goto error;
+
694 }
+
695 p = buf;
+
696
+
697 if (offset != d->offset) {
+
698 seekdir(d->dp, offset);
+
699 d->entry = NULL;
+
700 d->offset = offset;
+
701 }
+
702 while (1) {
+
703 size_t entsize;
+
704 off_t nextoff;
+
705 const char *name;
+
706
+
707 if (!d->entry) {
+
708 errno = 0;
+
709 d->entry = readdir(d->dp);
+
710 if (!d->entry) {
+
711 if (errno) { // Error
+
712 err = errno;
+
713 goto error;
+
714 } else { // End of stream
+
715 break;
+
716 }
+
717 }
+
718 }
+
719 nextoff = d->entry->d_off;
+
720 name = d->entry->d_name;
+
721 fuse_ino_t entry_ino = 0;
+
722 if (plus) {
+
723 struct fuse_entry_param e;
+
724 if (is_dot_or_dotdot(name)) {
+
725 e = (struct fuse_entry_param) {
+
726 .attr.st_ino = d->entry->d_ino,
+
727 .attr.st_mode = d->entry->d_type << 12,
+
728 };
+
729 } else {
+
730 err = lo_do_lookup(req, ino, name, &e);
+
731 if (err)
+
732 goto error;
+
733 entry_ino = e.ino;
+
734 }
+
735
+
736 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
737 &e, nextoff);
+
738 } else {
+
739 struct stat st = {
+
740 .st_ino = d->entry->d_ino,
+
741 .st_mode = d->entry->d_type << 12,
+
742 };
+
743 entsize = fuse_add_direntry(req, p, rem, name,
+
744 &st, nextoff);
+
745 }
+
746 if (entsize > rem) {
+
747 if (entry_ino != 0)
+
748 lo_forget_one(req, entry_ino, 1);
+
749 break;
+
750 }
+
751
+
752 p += entsize;
+
753 rem -= entsize;
+
754
+
755 d->entry = NULL;
+
756 d->offset = nextoff;
+
757 }
+
758
+
759 err = 0;
+
760error:
+
761 // If there's an error, we can only signal it if we haven't stored
+
762 // any entries yet - otherwise we'd end up with wrong lookup
+
763 // counts for the entries that are already in the buffer. So we
+
764 // return what we've collected until that point.
+
765 if (err && rem == size)
+
766 fuse_reply_err(req, err);
+
767 else
+
768 fuse_reply_buf(req, buf, size - rem);
+
769 free(buf);
+
770}
+
771
+
772static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
773 off_t offset, struct fuse_file_info *fi)
+
774{
+
775 lo_do_readdir(req, ino, size, offset, fi, 0);
+
776}
+
777
+
778static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
779 off_t offset, struct fuse_file_info *fi)
+
780{
+
781 lo_do_readdir(req, ino, size, offset, fi, 1);
+
782}
+
783
+
784static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
785{
+
786 struct lo_dirp *d = lo_dirp(fi);
+
787 (void) ino;
+
788 closedir(d->dp);
+
789 free(d);
+
790 fuse_reply_err(req, 0);
+
791}
+
792
+
793static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
794 mode_t mode, struct fuse_file_info *fi)
+
795{
+
796 int fd;
+
797 struct lo_data *lo = lo_data(req);
+
798 struct fuse_entry_param e;
+
799 int err;
+
800
+
801 if (lo_debug(req))
+
802 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
803 parent);
+
804
+
805 fd = openat(lo_fd(req, parent), ".",
+
806 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
807 if (fd == -1)
+
808 return (void) fuse_reply_err(req, errno);
+
809
+
810 fi->fh = fd;
+
811 if (lo->cache == CACHE_NEVER)
+
812 fi->direct_io = 1;
+
813 else if (lo->cache == CACHE_ALWAYS)
+
814 fi->keep_cache = 1;
+
815
+
816 /* parallel_direct_writes feature depends on direct_io features.
+
817 To make parallel_direct_writes valid, need set fi->direct_io
+
818 in current function. */
+
819 fi->parallel_direct_writes = 1;
+
820
+
821 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
822 if (err)
+
823 fuse_reply_err(req, err);
+
824 else
+
825 fuse_reply_create(req, &e, fi);
+
826}
+
827
+
828static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
829 mode_t mode, struct fuse_file_info *fi)
+
830{
+
831 int fd;
+
832 struct lo_data *lo = lo_data(req);
+
833 struct fuse_entry_param e;
+
834 int err;
+
835
+
836 if (lo_debug(req))
+
837 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
838 parent, name);
+
839
+
840 fd = openat(lo_fd(req, parent), name,
+
841 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
842 if (fd == -1)
+
843 return (void) fuse_reply_err(req, errno);
+
844
+
845 fi->fh = fd;
+
846 if (lo->cache == CACHE_NEVER)
+
847 fi->direct_io = 1;
+
848 else if (lo->cache == CACHE_ALWAYS)
+
849 fi->keep_cache = 1;
+
850
+
851 /* parallel_direct_writes feature depends on direct_io features.
+
852 To make parallel_direct_writes valid, need set fi->direct_io
+
853 in current function. */
+ +
855
+
856 err = lo_do_lookup(req, parent, name, &e);
+
857 if (err)
+
858 fuse_reply_err(req, err);
+
859 else
+
860 fuse_reply_create(req, &e, fi);
+
861}
+
862
+
863static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
864 struct fuse_file_info *fi)
+
865{
+
866 int res;
+
867 int fd = dirfd(lo_dirp(fi)->dp);
+
868 (void) ino;
+
869 if (datasync)
+
870 res = fdatasync(fd);
+
871 else
+
872 res = fsync(fd);
+
873 fuse_reply_err(req, res == -1 ? errno : 0);
+
874}
+
875
+
876static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
877{
+
878 int fd;
+
879 char buf[64];
+
880 struct lo_data *lo = lo_data(req);
+
881
+
882 if (lo_debug(req))
+
883 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
884 ino, fi->flags);
+
885
+
886 /* With writeback cache, kernel may send read requests even
+
887 when userspace opened write-only */
+
888 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
889 fi->flags &= ~O_ACCMODE;
+
890 fi->flags |= O_RDWR;
+
891 }
+
892
+
893 /* With writeback cache, O_APPEND is handled by the kernel.
+
894 This breaks atomicity (since the file may change in the
+
895 underlying filesystem, so that the kernel's idea of the
+
896 end of the file isn't accurate anymore). In this example,
+
897 we just accept that. A more rigorous filesystem may want
+
898 to return an error here */
+
899 if (lo->writeback && (fi->flags & O_APPEND))
+
900 fi->flags &= ~O_APPEND;
+
901
+
902 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
903 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
904 if (fd == -1)
+
905 return (void) fuse_reply_err(req, errno);
+
906
+
907 fi->fh = fd;
+
908 if (lo->cache == CACHE_NEVER)
+
909 fi->direct_io = 1;
+
910 else if (lo->cache == CACHE_ALWAYS)
+
911 fi->keep_cache = 1;
+
912
+
913 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
914 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
915 for writes to the same file in the kernel). */
+
916 if (fi->flags & O_DIRECT)
+
917 fi->direct_io = 1;
+
918
+
919 /* parallel_direct_writes feature depends on direct_io features.
+
920 To make parallel_direct_writes valid, need set fi->direct_io
+
921 in current function. */
+ +
923
+
924 fuse_reply_open(req, fi);
+
925}
+
926
+
927static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
928{
+
929 (void) ino;
+
930
+
931 close(fi->fh);
+
932 fuse_reply_err(req, 0);
+
933}
+
934
+
935static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
936{
+
937 int res;
+
938 (void) ino;
+
939 res = close(dup(fi->fh));
+
940 fuse_reply_err(req, res == -1 ? errno : 0);
+
941}
+
942
+
943static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
944 struct fuse_file_info *fi)
+
945{
+
946 int res;
+
947 (void) ino;
+
948 if (datasync)
+
949 res = fdatasync(fi->fh);
+
950 else
+
951 res = fsync(fi->fh);
+
952 fuse_reply_err(req, res == -1 ? errno : 0);
+
953}
+
954
+
955static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
956 off_t offset, struct fuse_file_info *fi)
+
957{
+
958 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
959
+
960 if (lo_debug(req))
+
961 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
962 "off=%lu)\n", ino, size, (unsigned long) offset);
+
963
+ +
965 buf.buf[0].fd = fi->fh;
+
966 buf.buf[0].pos = offset;
+
967
+ +
969}
+
970
+
971static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
972 struct fuse_bufvec *in_buf, off_t off,
+
973 struct fuse_file_info *fi)
+
974{
+
975 (void) ino;
+
976 ssize_t res;
+
977 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
978
+ +
980 out_buf.buf[0].fd = fi->fh;
+
981 out_buf.buf[0].pos = off;
+
982
+
983 if (lo_debug(req))
+
984 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
985 ino, out_buf.buf[0].size, (unsigned long) off);
+
986
+
987 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
988 if(res < 0)
+
989 fuse_reply_err(req, -res);
+
990 else
+
991 fuse_reply_write(req, (size_t) res);
+
992}
+
993
+
994static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
995{
+
996 int res;
+
997 struct statvfs stbuf;
+
998
+
999 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
1000 if (res == -1)
+
1001 fuse_reply_err(req, errno);
+
1002 else
+
1003 fuse_reply_statfs(req, &stbuf);
+
1004}
+
1005
+
1006static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1007 off_t offset, off_t length, struct fuse_file_info *fi)
+
1008{
+
1009 int err = EOPNOTSUPP;
+
1010 (void) ino;
+
1011
+
1012#ifdef HAVE_FALLOCATE
+
1013 err = fallocate(fi->fh, mode, offset, length);
+
1014 if (err < 0)
+
1015 err = errno;
+
1016
+
1017#elif defined(HAVE_POSIX_FALLOCATE)
+
1018 if (mode) {
+
1019 fuse_reply_err(req, EOPNOTSUPP);
+
1020 return;
+
1021 }
+
1022
+
1023 err = posix_fallocate(fi->fh, offset, length);
+
1024#endif
+
1025
+
1026 fuse_reply_err(req, err);
+
1027}
+
1028
+
1029static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1030 int op)
+
1031{
+
1032 int res;
+
1033 (void) ino;
+
1034
+
1035 res = flock(fi->fh, op);
+
1036
+
1037 fuse_reply_err(req, res == -1 ? errno : 0);
+
1038}
+
1039
+
1040static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1041 size_t size)
+
1042{
+
1043 char *value = NULL;
+
1044 char procname[64];
+
1045 struct lo_inode *inode = lo_inode(req, ino);
+
1046 ssize_t ret;
+
1047 int saverr;
+
1048
+
1049 saverr = ENOSYS;
+
1050 if (!lo_data(req)->xattr)
+
1051 goto out;
+
1052
+
1053 if (lo_debug(req)) {
+
1054 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1055 ino, name, size);
+
1056 }
+
1057
+
1058 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1059
+
1060 if (size) {
+
1061 value = malloc(size);
+
1062 if (!value)
+
1063 goto out_err;
+
1064
+
1065 ret = getxattr(procname, name, value, size);
+
1066 if (ret == -1)
+
1067 goto out_err;
+
1068 saverr = 0;
+
1069 if (ret == 0)
+
1070 goto out;
+
1071
+
1072 fuse_reply_buf(req, value, ret);
+
1073 } else {
+
1074 ret = getxattr(procname, name, NULL, 0);
+
1075 if (ret == -1)
+
1076 goto out_err;
+
1077
+
1078 fuse_reply_xattr(req, ret);
+
1079 }
+
1080out_free:
+
1081 free(value);
+
1082 return;
+
1083
+
1084out_err:
+
1085 saverr = errno;
+
1086out:
+
1087 fuse_reply_err(req, saverr);
+
1088 goto out_free;
+
1089}
+
1090
+
1091static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1092{
+
1093 char *value = NULL;
+
1094 char procname[64];
+
1095 struct lo_inode *inode = lo_inode(req, ino);
+
1096 ssize_t ret;
+
1097 int saverr;
+
1098
+
1099 saverr = ENOSYS;
+
1100 if (!lo_data(req)->xattr)
+
1101 goto out;
+
1102
+
1103 if (lo_debug(req)) {
+
1104 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1105 ino, size);
+
1106 }
+
1107
+
1108 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1109
+
1110 if (size) {
+
1111 value = malloc(size);
+
1112 if (!value)
+
1113 goto out_err;
+
1114
+
1115 ret = listxattr(procname, value, size);
+
1116 if (ret == -1)
+
1117 goto out_err;
+
1118 saverr = 0;
+
1119 if (ret == 0)
+
1120 goto out;
+
1121
+
1122 fuse_reply_buf(req, value, ret);
+
1123 } else {
+
1124 ret = listxattr(procname, NULL, 0);
+
1125 if (ret == -1)
+
1126 goto out_err;
+
1127
+
1128 fuse_reply_xattr(req, ret);
+
1129 }
+
1130out_free:
+
1131 free(value);
+
1132 return;
+
1133
+
1134out_err:
+
1135 saverr = errno;
+
1136out:
+
1137 fuse_reply_err(req, saverr);
+
1138 goto out_free;
+
1139}
+
1140
+
1141static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1142 const char *value, size_t size, int flags)
+
1143{
+
1144 char procname[64];
+
1145 struct lo_inode *inode = lo_inode(req, ino);
+
1146 ssize_t ret;
+
1147 int saverr;
+
1148
+
1149 saverr = ENOSYS;
+
1150 if (!lo_data(req)->xattr)
+
1151 goto out;
+
1152
+
1153 if (lo_debug(req)) {
+
1154 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1155 ino, name, value, size);
+
1156 }
+
1157
+
1158 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1159
+
1160 ret = setxattr(procname, name, value, size, flags);
+
1161 saverr = ret == -1 ? errno : 0;
+
1162
+
1163out:
+
1164 fuse_reply_err(req, saverr);
+
1165}
+
1166
+
1167static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1168{
+
1169 char procname[64];
+
1170 struct lo_inode *inode = lo_inode(req, ino);
+
1171 ssize_t ret;
+
1172 int saverr;
+
1173
+
1174 saverr = ENOSYS;
+
1175 if (!lo_data(req)->xattr)
+
1176 goto out;
+
1177
+
1178 if (lo_debug(req)) {
+
1179 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1180 ino, name);
+
1181 }
+
1182
+
1183 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1184
+
1185 ret = removexattr(procname, name);
+
1186 saverr = ret == -1 ? errno : 0;
+
1187
+
1188out:
+
1189 fuse_reply_err(req, saverr);
+
1190}
+
1191
+
1192#ifdef HAVE_COPY_FILE_RANGE
+
1193static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1194 struct fuse_file_info *fi_in,
+
1195 fuse_ino_t ino_out, off_t off_out,
+
1196 struct fuse_file_info *fi_out, size_t len,
+
1197 int flags)
+
1198{
+
1199 ssize_t res;
+
1200
+
1201 if (lo_debug(req))
+
1202 fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
1203 "off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
1204 "off=%lu, size=%zd, flags=0x%x)\n",
+
1205 ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
1206 len, flags);
+
1207
+
1208 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1209 flags);
+
1210 if (res < 0)
+
1211 fuse_reply_err(req, errno);
+
1212 else
+
1213 fuse_reply_write(req, res);
+
1214}
+
1215#endif
+
1216
+
1217static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 off_t res;
+
1221
+
1222 (void)ino;
+
1223 res = lseek(fi->fh, off, whence);
+
1224 if (res != -1)
+
1225 fuse_reply_lseek(req, res);
+
1226 else
+
1227 fuse_reply_err(req, errno);
+
1228}
+
1229
+
1230static const struct fuse_lowlevel_ops lo_oper = {
+
1231 .init = lo_init,
+
1232 .destroy = lo_destroy,
+
1233 .lookup = lo_lookup,
+
1234 .mkdir = lo_mkdir,
+
1235 .mknod = lo_mknod,
+
1236 .symlink = lo_symlink,
+
1237 .link = lo_link,
+
1238 .unlink = lo_unlink,
+
1239 .rmdir = lo_rmdir,
+
1240 .rename = lo_rename,
+
1241 .forget = lo_forget,
+
1242 .forget_multi = lo_forget_multi,
+
1243 .getattr = lo_getattr,
+
1244 .setattr = lo_setattr,
+
1245 .readlink = lo_readlink,
+
1246 .opendir = lo_opendir,
+
1247 .readdir = lo_readdir,
+
1248 .readdirplus = lo_readdirplus,
+
1249 .releasedir = lo_releasedir,
+
1250 .fsyncdir = lo_fsyncdir,
+
1251 .create = lo_create,
+
1252 .tmpfile = lo_tmpfile,
+
1253 .open = lo_open,
+
1254 .release = lo_release,
+
1255 .flush = lo_flush,
+
1256 .fsync = lo_fsync,
+
1257 .read = lo_read,
+
1258 .write_buf = lo_write_buf,
+
1259 .statfs = lo_statfs,
+
1260 .fallocate = lo_fallocate,
+
1261 .flock = lo_flock,
+
1262 .getxattr = lo_getxattr,
+
1263 .listxattr = lo_listxattr,
+
1264 .setxattr = lo_setxattr,
+
1265 .removexattr = lo_removexattr,
+
1266#ifdef HAVE_COPY_FILE_RANGE
+
1267 .copy_file_range = lo_copy_file_range,
+
1268#endif
+
1269 .lseek = lo_lseek,
+
1270};
+
1271
+
1272int main(int argc, char *argv[])
+
1273{
+
1274 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1275 struct fuse_session *se;
+
1276 struct fuse_cmdline_opts opts;
+
1277 struct fuse_loop_config *config;
+
1278 struct lo_data lo = { .debug = 0,
+
1279 .writeback = 0 };
+
1280 int ret = -1;
+
1281
+
1282 /* Don't mask creation mode, kernel already did that */
+
1283 umask(0);
+
1284
+
1285 pthread_mutex_init(&lo.mutex, NULL);
+
1286 lo.root.next = lo.root.prev = &lo.root;
+
1287 lo.root.fd = -1;
+
1288 lo.cache = CACHE_NORMAL;
+
1289
+
1290 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1291 return 1;
+
1292 if (opts.show_help) {
+
1293 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1296 passthrough_ll_help();
+
1297 ret = 0;
+
1298 goto err_out1;
+
1299 } else if (opts.show_version) {
+
1300 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1302 ret = 0;
+
1303 goto err_out1;
+
1304 }
+
1305
+
1306 if(opts.mountpoint == NULL) {
+
1307 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1308 printf(" %s --help\n", argv[0]);
+
1309 ret = 1;
+
1310 goto err_out1;
+
1311 }
+
1312
+
1313 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1314 return 1;
+
1315
+
1316 lo.debug = opts.debug;
+
1317 lo.root.refcount = 2;
+
1318 if (lo.source) {
+
1319 struct stat stat;
+
1320 int res;
+
1321
+
1322 res = lstat(lo.source, &stat);
+
1323 if (res == -1) {
+
1324 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1325 lo.source);
+
1326 exit(1);
+
1327 }
+
1328 if (!S_ISDIR(stat.st_mode)) {
+
1329 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1330 exit(1);
+
1331 }
+
1332
+
1333 } else {
+
1334 lo.source = strdup("/");
+
1335 if(!lo.source) {
+
1336 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1337 exit(1);
+
1338 }
+
1339 }
+
1340 if (!lo.timeout_set) {
+
1341 switch (lo.cache) {
+
1342 case CACHE_NEVER:
+
1343 lo.timeout = 0.0;
+
1344 break;
+
1345
+
1346 case CACHE_NORMAL:
+
1347 lo.timeout = 1.0;
+
1348 break;
+
1349
+
1350 case CACHE_ALWAYS:
+
1351 lo.timeout = 86400.0;
+
1352 break;
+
1353 }
+
1354 } else if (lo.timeout < 0) {
+
1355 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1356 lo.timeout);
+
1357 exit(1);
+
1358 }
+
1359
+
1360 lo.root.fd = open(lo.source, O_PATH);
+
1361 if (lo.root.fd == -1) {
+
1362 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1363 lo.source);
+
1364 exit(1);
+
1365 }
+
1366
+
1367 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1368 if (se == NULL)
+
1369 goto err_out1;
+
1370
+
1371 if (fuse_set_signal_handlers(se) != 0)
+
1372 goto err_out2;
+
1373
+
1374 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1375 goto err_out3;
+
1376
+
1377 fuse_daemonize(opts.foreground);
+
1378
+
1379 /* Block until ctrl+c or fusermount -u */
+
1380 if (opts.singlethread)
+
1381 ret = fuse_session_loop(se);
+
1382 else {
+
1383 config = fuse_loop_cfg_create();
+
1384 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1385 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1386 ret = fuse_session_loop_mt(se, config);
+
1387 fuse_loop_cfg_destroy(config);
+
1388 config = NULL;
+
1389 }
+
1390
+ +
1392err_out3:
+ +
1394err_out2:
+ +
1396err_out1:
+
1397 free(opts.mountpoint);
+
1398 fuse_opt_free_args(&args);
+
1399
+
1400 if (lo.root.fd >= 0)
+
1401 close(lo.root.fd);
+
1402
+
1403 free(lo.source);
+
1404 return ret ? 1 : 0;
+
1405}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/example_2poll_8c.html b/doc/html/example_2poll_8c.html new file mode 100644 index 0000000..71d3c0b --- /dev/null +++ b/doc/html/example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/example_2poll_8c_source.html b/doc/html/example_2poll_8c_source.html new file mode 100644 index 0000000..1f88a3e --- /dev/null +++ b/doc/html/example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/example_2poll__client_8c.html b/doc/html/example_2poll__client_8c.html new file mode 100644 index 0000000..39b4807 --- /dev/null +++ b/doc/html/example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/example_2poll__client_8c_source.html b/doc/html/example_2poll__client_8c_source.html new file mode 100644 index 0000000..befcd5a --- /dev/null +++ b/doc/html/example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/example_2printcap_8c.html b/doc/html/example_2printcap_8c.html new file mode 100644 index 0000000..b9e24ef --- /dev/null +++ b/doc/html/example_2printcap_8c.html @@ -0,0 +1,239 @@ + + + + + + + +libfuse: example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/example_2printcap_8c_source.html b/doc/html/example_2printcap_8c_source.html new file mode 100644 index 0000000..7100ff8 --- /dev/null +++ b/doc/html/example_2printcap_8c_source.html @@ -0,0 +1,230 @@ + + + + + + + +libfuse: example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fast17-vangoor.pdf b/doc/html/fast17-vangoor.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cef723762933f15b9b179794c7335574b96fa62f GIT binary patch literal 599157 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

fzRwn`Q%HHe<+Scq0Sk9mwO=g>hmrEO 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

spbq1Go#6A)= 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

A@uBNDhJn$S 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�$W;Igw!{^1IDv+^Yn!e9 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 z z>Lvjpx4ItU<;< zFi9f{9`oYHjA5SSZy?VUy$2w4C9Iz(K$j>eBQd0v0KpNh*z+MI^da32X{bov zKu1?8f_~-;A@byrDk_fSD|WLQIaiX0FHL>sD%f*fm$s3_j!B9Hu+#P-D`>v^6cCiX z=E;{6WlxnT_x14FUDoO8=fzUjGVE;D>&Gq!uL;@=r+nVIRiy=u`zFTQbB~E++tka` z9OHP4suHhZQ_L<#lO)Ot-$jn$b!@$&2f6s35v{2Zaljocl1YVv?G#J_Tc++T-Ue}@ zz}G&)p9ythBh+>H@K4cSH6HAN65`tjPjwkJ0%gX9w%Iv#(I2q^(i3HO4^{rg`5IN~)VB!U2$6=!jciTwH5wc2Lt73_Iz7GXHc$sU)&lE&d~V{~Hh5GHSs!jrfk zVJPli?>^vQABWf1F-Q&0Wn8;)92&3ZF~G`GiaCnb{i-xjhSR1&;mteJ>j~hbzWC}B zE6CSDqnaHh^K{TeN`=a(=={RhEncJ1(GIkD*xNn9J!(P>!5o+A6PKK)ETnGKTf4h3 zp8w?>5$9ETnqPqtjCv=(rdDD*jza1!+tcA#v-utIy{ zTqQ`q);F@ri8OqmpmKxYCAwgBOxuL25|qno!KI8rjiZA*eE4t6SqJ5|yZ!o25|z}6 zx%;TQFEu!T=%H&Ja~_AmC%$>@+T|)&UlHL!&K zDOEnNRd=&P(@T1bGx1ssiGo_2{8pI{^^e?~y^R#PmaX3;{~*;f(zBgnP|g%;t;0Ze(}8eq|D?Qd~pY?_;inXjvaU~nRM`RStFp$7wQn@Nv;&j z|H4mOT#2Y}*fc7`_(KM%DxV*WxHYw4*jN<3+8VA+$$S~>E3pa`N(*WY#>>-Vk*|47 z!iG1uueLn(1w}ZxXL}_lxwpbHrfvIemC2oGKi8isy%&$OY_yVptfNEp*)bhzU0%s# zj?=@!mrGk#U;0)lRzJ8l&t+5=g3Zpvo1sw~Uo*);OjJ(B;UakLda>=Sq2ngIS|}o{ z@!7C4{Kv~gGOPr~LOm|3j2myeGD6e{JS8)EPguNj{mPSTy9h=?ikIpBJi&6RQR%HC z8<)+~{@(o}g^Qg@RUI_6#`QS#UV+^Wct)BRdqyKFQq#=$-YxiUEjFQbMeN>M%PjqE>iyz6HGDlH* zz(z7EiDS}mYrsR-Vg4>Xm7#}U{Ak8!;sQG|PnkdX81Q^)M#2{$L^`n30JB-Ufnv+= zZvqGbo-a!K_0wN>u}$?5dI@#0jeovHa{V6{K?c%(Z_Uey?%EH4BnHw$QtB^(%y|4!lfr_0cl zCTKN$_!rpW=R4d6ah>YDWXt%Kd$UMy29*-gYP|q2As^@}BIg%ccA6cNP}}6+`ueQo z^z!c~p-BB|GigO&_Eu00x^l>fU|`?U0x(}?KYhWe8ZsB;z$4KZG}S=1WS>q?63t%# z>`tdUwiXvfHwcnDBG(*Zt!$_HW+sBKI|$-?c{8l~`V6(M-+byKz#B)xXx$vqIWK_f z2A+!2Y!@ppAD-y+pEv3^N?RgSx1x(S*?hwIXsSNtN`Vm?w%ncls=;kc^E`4?0LfFD z7^}AD`@vu8^ybl;%Oh%RyyG3v*KM#aqN!@SisW;4PfNpHd`S2mai4edL=kQCrY$Qq zGT+aopv+puJFSkuC(dVw2Ej7LEn4j$7(gr9GqhE{EE~WXTk&~&3USHwp*b=NGJ-~7 zOXqu+gvav8=0a4JJ2Gb1z6EbjB_H}yT7*exMaLeh0>Y*ex}Fk`$s@iXzR(rXEx`;8 z#8t*_Z=nIX0aMGt4qu~!^#(s4p|6<*5kR@aw+MoQxK2du%AzaiSy@qrWC#_D>6$XZ zTpdi&Ftp4?t4uEnBolc?k8mZXOSvY;^)!i0^N*44QwlD2YHgvk-CMl$+GLungLgb3 zNxbwL*o5xe555+q5qCoVZ9rhqzXAf=OX_K8s5`^l-MD1r6o`dpj(_3@2fFy&k&&1B zxL-hOa*sHC=^Gu&O%-8In7UDD7!z!UDpB`YVZstSWc-JSjKQ0OK*AV*VTrKlgD zM5KUP6N_8?C!P{l{Wa%DHesA^2O31Hr8T2I9fb`DJKCrbDDznUmPtO{uy_?3x2tmA z+DDT=Gkq=b7l&9cLtu^h0qOn>;@R|%7RPN%&Hin_J+`hP{ko`iFZogo8V8 z);F^uCOx)JEMa)r1ShS+f@O-7iLd?D_TmtGxG;AjQZ5HFg#ygER4HHNN%l_hEmwP% z-t&vQb7uNYx0+uEhFG~3_&lsv$wRz45BF?GHBz$6Q}q_-bJ#lCc1f<&J9zTYV#LGj0>b7 zsT~*qS62@Vw-=X|lI8kyk4suwLHgQSy)2(FQukX4+<=|NIQ`BjV=7O1lL5x#_|G@g{R(w}ZQSNkRgl z{?2~?D6;n49F||zhmHIK&;g50pb_{ENa}uWi->OFL;Rt&vB+7 zUGs7QV`+---R=76=MXZ%^W>4Ks^I-YP4|#$2gRXXmD{BWdaanIYXj?yx8GAbC<(SE zH)HaSo3;b522yXK4kFMm>l~Dllyk?)BoOA$xA%vrm~2vsIo5AsI)|sNZR^}ER=a*b z;aK8xwWXg>Pi5_Z#fu#XpyI>yHvyFmKS#)W(enOCL-ZugabzUpiPU6@JLN|? z-C6Rtk+k?>`R`L(=1pWq9CA@(TWX>nS|bFPmprRgdfNlm+??HVZPfMYA0}j4h{xvB}7`Z%NYJc;+^G(#7R9D99 zw+Dk0LylL!wO@{&L9Dcf-o;o1Q~t=7Hcff88}y8{={F3~14@p^+r0(ZOg3eV_wW!_ z`J`{WV@>ema;KewMC?4U3`ivpQWifscooj!>Ff6N@J#&v`-dmHyF&I19AzfMKTy-; z!^|xsyrBkY5mgAku}OktR!s^b=Q8h+^@kr>J~;h%kACQCGU9TE+(ou$gj6B8+qwMZ zTkFHj?LM7i5*xprf8j^}<^nkh!*b$9d8KX>I7j{(1W5S_AV&M^_y3(7 zp()nz0u3q6z~s#2*o!NPt){*Pz4^`aby5rc=wpY_QeY3uK=i;JKIU$@`a_a5KbliSgJInO84O=vO{NaT9# zB{y!o_Mx$KgOY!A5~jPEv-EN)$*g(F>4$*pE!r2cs|NM~B8GOiXQmb_Z=A2tS<7BM z9~ac}tgSy1G!9$JRi@}Zv=ouIGppmsTJHGb0QJ@}|0cZ`tQFM5>e2msMWyI4iPB%$ zEYp1Syt(q{au>b9+&!5H;h1>r%E|%a_d;;Q7E;qF6e1(4&Apv3Ehzqw{=j77GrwQQuVm#z#JKc3euQ^HIUd{;L*G)&SPR2OH=z}-1K8h4s=5Rpu ztfIb)yQt`X7XEpw&67{x*Y~>gnpARR)~xQVz!D@ppZ{q)*Dl$Pw=SX4YNkrf7Ul%4 z#a%oh+9kAOOFFft+9&JC_f1WIB+y#_1>#EGP6}=Rb>s?KWQ$N1Fx=Rq+uNhFF=LTn zh;2Le47iv2^~r9@K-FLwEz5YGX#?%~UQ^410`Z%<&UA4YXHF;;X(Ny0AdVDy{OL}R z#}a)_m`|E~gk@LYU)&FC*}lvKof%chJ(^vZd3?t&TEo)(pq->MX7)6Z;^cl@>=z4S zn9MitR%LWkHNEaYFN;6KeDW|}PF0xEHKSpn&uClzdQxbFP- z-NXh9HQRAjjccM-2Z_!$veo?u_r&7vo_JxvK4C(e!hye>&sQWLnBLDUwCbJ7PtwpE zepkTK%s9R)`-{R(G|VclaD7TLWt%2Mauu=;e73b|DmSfae{jUqN6LqO zL-p+jHDb!5bdSO|829j&Dofl@tg`j>EG}y3?F=~7ZLXHY!`#g@hPF!bgL0;$*!35N z&u2)@`HW}p=OyOw-WFGbjR~sY-#VO#SNow7-jW=3^4zrbG^k^0{kr1GDvBz${d@qu z^Oe62g!Ksyps=Cr={Xj|(*71YI_exoizCua*yIxU6mD74(3LFQ+p^p)tj>CVJ%6@? zEP&EV_iLu(7=()^`T1AwANk0$3}HLExlO4%hw)PH0+I}*zcV4qsOymj*pKM)fKd-N z%WRtAqlT0u*UG;tUb*v7$h3sB_S`e@KJWm z)7zgk={*$gD6_Hclh;{O^3mSQ`Fi2tfq%qVrMsb&F=Af9{Wj&}tnL>32bBm58Tz@# zf#s+IPodRo`(;nnHky)ecgA#&usH1+ewvDVLE@{c*d}kObLKll>+$r1(pP7>JMCNs z536&z$M0hv(0=bu{*v4k^xa+1@L@<>@AuC3*Y6|AMRPtYmtC)zT*&EpQ?Bhat$jC~b7F_7U_!mVjTY$;so=$>uIgCXma;w8>rhl6o6%*^p);uBzi46bEZ7e_Fu=Cl z=zBr8t@D({t$oB*?H=lfkJw_V$i#qdYkz;>?L`x_a%4(o`Y%EY>#;fN=+3u040mC# z65)-!>w3oyn&(I;{kyue^O}U;c&Cn$HQuE0bwKsxO zdw0lQ2!Mm=N{uF-8{a1)Un7an`kLXThRy3Q7MTNC1f(Ot~{99;0bw5mHv>Q#OMO~-_M_j?(Zn6*u*1U(6=);GOont?s) zUdu)juTy-4OZJa{-i5DevwTp_Wn?uxH~T)@af4;lxoTn~N(myGc6s&Qvb2q~bAn4?>J{F^$|jRmfwx106>HLx>js zj@?29)J+^lk#Yy)SRI&nzR!5I8tSjnkfI`izdQ7f25K@s+U7^y z0uDrk>|Bm-*^zxbI_=J7)_U=ch?4aC)pP%@ndJYonf?PML5lw~O8yHX{|S&6G zNfRPITKo$j-6cJU`1l|281yfE{P*xEEAtN&`zJhtWd6mDGV%&sK#<~p!;b$iH7XM8 z|36WqI*}OvfAXT#f6$`5%>P1*p#MsXe>5onD}%_&N&i>o_?O1yPlqRJOk8~B&8Um! z7?lsn4vufk5tC!cCwSd`{S)t|&!q73jPmTlQ^3>~BeM9By8HY16i{P!ab{C)rCaUQ zMopU!lg47fWiaIUEy^9}R=A-~KcQf0@av(ILe?46=XPKma4C#uAWa-nE4|AWrI8)D zN1t(jm!87^`$tYeWa!aI*rp{1>{F;g!Z*HXo)yj;DANe!UjiD{@$zT3-we_En5+YA z)Vd0c2Q6hle7)=VK&Fqj4&-EzMkP(mBPSU{ttyq<%irWRQ*E2dF&cTU^*kZNvsm2K z-&PDDYp|O3-J@966_a<)^X^8pVTz6(TOy%wWI4*tG@Q{{u`7FJ<81rY_%-*>We;yM zlgtL-(*Ip2|IN~+rsSNa{ zYH=jm_eh_xaGWv&%2^zGJ~1X->wo{rghp$tf=5K{=4bIC3h86k`SOBgP7h&M+gcA5 zqhp&+CL^D(vokdvAsnkJgsCvR3LkeHw=NP+Zp+31r27!w3qJB-fRS%ZX^=^MjFSXBdhYtYg3eh3nb>K40R40})vO#G+}OE$ zu(`?mmls^mP8#YNP^chk?eGFqJxSO-PmZul&t=ZY6V^6-2kXD(UuzZkTp{2z<(&4N zG?$X`?Wvu}WYB`2QDkW4Z0B&_xr4Onl&|?@ux7Uv@6H~b{6e*6OhUQA<)itoSe}y5 z9j|_dV(kKTYCcs`BWi%!tIRk>H8K9AZG}FOr?>c$xRAF5l1xA}-Wq+heSRa}=O?Y;9NZtxcv{)8_w_b?xy`tz9_xzA~Yd zLYnH7Yni=gU!#03L*+XnZ!|2 zN`&O{ZRga9J?~#L&wAH-)>`w<{=M%!zu&yqvK0dCjReY;>*4Hs)Uph_Rw(Ts``RjZDb8YUYiE&fTPBIm99qh;Z`EyG_c8tXkA)z>74JbjprPd6&gMlA6#Rb(0@yP*O5!GR~{J(3^g+wIMdS}LKy z`K6`E#{R5waliN895roev;Jk=6mS1h$*#f$i_eLRgZEfdDl|5|ch75fs^s3*G-&lGFzHCY zF|=QDdPEf}kCCaE;4f`gH<|W!o!nH|b9F3bq$RU$jfzv7pv(Y2Ub&PjbA~IfNoly7 zaduiE>`j?fdU`{k&hDa-12|L{){%V2Si?4Rfl2IIQ>T-YKKbF{e!mspk1M#!jgq*% zh-;pbo~vA*;e4_&v%@p~dUjs&Q=Nv!Yrg$@_7gtg?VqeOhEKDki=tOwDL?p;HMlcq z#q(GCj=@?=@&QBfA1}N2RvQn#i@(!UB`#=_UL@tmTf3eW0menWNL+m}PXCaOnDEjs z11h@~j6^^&{=*O+9c@Kc6%{P;xRJZXOo$7=v z2Zkf^aP*|F_Rj%DnQ$i8aoIiN2Sy$h=hmD)~69egbgERf!W)I%FYId{46UJ(k zC1Sdtc8Sr$H8)F_bIr0eQXhS~qO8^PCbrW-)hcIPElRR))p(G_X-;D48=G=HuGxLX z;$k=EsYT5W`}^9({KD#APdZJdT@f1`V0KSyL%Ro-HI+VHzIgBFoC)dW@8gLpRA>b? z&xeq;0<2!rhFNzct9=ji>7hIWLGWe0X6wBzk%1a@wqO>_aP2elHV>I0Fay zGKN;$qyyVs`gL!PjU;DcuGY*0W=MfS_@JC|=NEqI)TXF^C7*s=>3o56Mi)C~9tm?n zd>yMKNv*QSqIJg#yUR8;m0OM`I}88QcZa<<*wvk(d5BwPoIYmPHQ1RnxpiwzdvU~v z&&KPOZc!ZnJ1&h#NC`_b<%Xm+wV!OZ-1H>PaYqVIX)Gn+W)$1Dv zr#?R``(>C$c0=A>+1AzV1);Cc@gjMyHO}=NKH5@kzfwMQUOpZWI#2>{u$(F3Nqh;( zxFYseVswdHi1Mq;S3O4!#AO%6C@cg-_YFa8S2qeFWl(7U3@QS{beQT!Sp1LLG>nZ;D-Un=NJfmU|4#7@!%#vaoR0wjN{EXj z494apF>8Ymf>2~02!nIV_%;uR2@Pg;%(nJ#41q90{+frOAVla+Bn$&!G6#ymC^;UA zLx{*c97D()I0ys8S!o_G4k94g7Y9*@oC|?rWL0qH`oK6Rb?dyDA7)RzT zpjjJ)BQ%;weh5GlsRIE(QC}D$_XS6YWjdc1rqf{>DHj66Fj5a-Is`%@^H2aGalmwx zNRZ5d0U{U#(lAo5h=c+1IwdeVK;|GYh@2k;6I*=}FM`o&q;(48G)%;oz(jJv0T3s1 z;Di85=D>+ML^z2l)S^+&4)@PF#FQPL7$G963s?YQ;?1mg_4 z2qqwiftccS5P?i-IBvv%Obj8DZH~IuY}WuPQ3Hq*2V07tKa1`E*K-rbi8W8BY#?s> HHl_R@i@NEa literal 0 HcmV?d00001 diff --git a/doc/html/files.html b/doc/html/files.html new file mode 100644 index 0000000..f819a1a --- /dev/null +++ b/doc/html/files.html @@ -0,0 +1,126 @@ + + + + + + + +libfuse: File List + + + + + + +

+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
File List
+
+
+
Here is a list of all documented files with brief descriptions:
+
[detail level 123]
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  build-ubuntu
  meson-private
 sanitycheckc.c
 fuse_config.h
 libfuse_config.h
  example
 cuse.c
 cuse_client.c
 hello.c
 hello_ll.c
 hello_ll_uds.c
 invalidate_path.c
 ioctl.c
 ioctl.h
 ioctl_client.c
 notify_inval_entry.c
 notify_inval_inode.c
 notify_store_retrieve.c
 null.c
 passthrough.c
 passthrough_fh.c
 passthrough_helpers.h
 passthrough_ll.c
 poll.c
 poll_client.c
 printcap.c
  include
 cuse_lowlevel.h
 fuse.h
 fuse_common.h
 fuse_kernel.h
 fuse_log.h
 fuse_lowlevel.h
 fuse_mount_compat.h
 fuse_opt.h
  lib
  modules
 iconv.c
 subdir.c
 buffer.c
 compat.c
 cuse_lowlevel.c
 fuse.c
 fuse_i.h
 fuse_log.c
 fuse_loop.c
 fuse_loop_mt.c
 fuse_lowlevel.c
 fuse_misc.h
 fuse_opt.c
 fuse_signals.c
 helper.c
 mount.c
 mount_bsd.c
 mount_util.c
 mount_util.h
 util.c
 util.h
  test
 hello.c
 readdir_inode.c
 release_unlink_race.c
 stracedecode.c
 test_setattr.c
 test_syscalls.c
 test_want_conversion.c
 test_write_cache.c
 wrong_command.c
  util
 fusermount.c
 mount.fuse.c
+
+
+ +
+ + diff --git a/doc/html/folderclosed.png b/doc/html/folderclosed.png new file mode 100644 index 0000000000000000000000000000000000000000..bb8ab35edce8e97554e360005ee9fc5bffb36e66 GIT binary patch literal 616 zcmV-u0+;=XP)a9#ETzayK)T~Jw&MMH>OIr#&;dC}is*2Mqdf&akCc=O@`qC+4i z5Iu3w#1M@KqXCz8TIZd1wli&kkl2HVcAiZ8PUn5z_kG@-y;?yK06=cA0U%H0PH+kU zl6dp}OR(|r8-RG+YLu`zbI}5TlOU6ToR41{9=uz^?dGTNL;wIMf|V3`d1Wj3y!#6` zBLZ?xpKR~^2x}?~zA(_NUu3IaDB$tKma*XUdOZN~c=dLt_h_k!dbxm_*ibDM zlFX`g{k$X}yIe%$N)cn1LNu=q9_CS)*>A zsX_mM4L@`(cSNQKMFc$RtYbx{79#j-J7hk*>*+ZZhM4Hw?I?rsXCi#mRWJ=-0LGV5a-WR0Qgt<|Nqf)C-@80`5gIz45^_20000 + + + + + + + + + diff --git a/doc/html/folderclosedd.svg b/doc/html/folderclosedd.svg new file mode 100644 index 0000000..52f0166 --- /dev/null +++ b/doc/html/folderclosedd.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/doc/html/folderopen.png b/doc/html/folderopen.png new file mode 100644 index 0000000000000000000000000000000000000000..d6c7f676a3b3ef8c2c307d319dff3c6a604eb227 GIT binary patch literal 597 zcmV-b0;>IqP)X=#(TiCT&PiIIVc55T}TU}EUh*{q$|`3@{d>{Tc9Bo>e= zfmF3!f>fbI9#GoEHh0f`i5)wkLpva0ztf%HpZneK?w-7AK@b4Itw{y|Zd3k!fH?q2 zlhckHd_V2M_X7+)U&_Xcfvtw60l;--DgZmLSw-Y?S>)zIqMyJ1#FwLU*%bl38ok+! zh78H87n`ZTS;uhzAR$M`zZ`bVhq=+%u9^$5jDplgxd44}9;IRqUH1YHH|@6oFe%z( zo4)_>E$F&^P-f(#)>(TrnbE>Pefs9~@iN=|)Rz|V`sGfHNrJ)0gJb8xx+SBmRf@1l zvuzt=vGfI)<-F9!o&3l?>9~0QbUDT(wFdnQPv%xdD)m*g%!20>Bc9iYmGAp<9YAa( z0QgYgTWqf1qN++Gqp z8@AYPTB3E|6s=WLG?xw0tm|U!o=&zd+H0oRYE;Dbx+Na9s^STqX|Gnq%H8s(nGDGJ j8vwW|`Ts`)fSK|Kx=IK@RG@g200000NkvXXu0mjfauFEA literal 0 HcmV?d00001 diff --git a/doc/html/folderopen.svg b/doc/html/folderopen.svg new file mode 100644 index 0000000..f6896dd --- /dev/null +++ b/doc/html/folderopen.svg @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/doc/html/folderopend.svg b/doc/html/folderopend.svg new file mode 100644 index 0000000..2d1f06e --- /dev/null +++ b/doc/html/folderopend.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/doc/html/functions.html b/doc/html/functions.html new file mode 100644 index 0000000..eee096e --- /dev/null +++ b/doc/html/functions.html @@ -0,0 +1,256 @@ + + + + + + + +libfuse: Data Fields + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented struct and union fields with links to the struct/union documentation for each field:
+ +

- a -

+ + +

- b -

+ + +

- c -

+ + +

- d -

+ + +

- e -

+ + +

- f -

+ + +

- g -

+ + +

- h -

+ + +

- i -

+ + +

- k -

+ + +

- l -

+ + +

- m -

+ + +

- n -

+ + +

- o -

+ + +

- p -

+ + +

- r -

+ + +

- s -

+ + +

- t -

+ + +

- u -

+ + +

- v -

+ + +

- w -

+
+ + + + diff --git a/doc/html/functions_vars.html b/doc/html/functions_vars.html new file mode 100644 index 0000000..3915b1a --- /dev/null +++ b/doc/html/functions_vars.html @@ -0,0 +1,256 @@ + + + + + + + +libfuse: Data Fields - Variables + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented variables with links to the struct/union documentation for each field:
+ +

- a -

+ + +

- b -

+ + +

- c -

+ + +

- d -

+ + +

- e -

+ + +

- f -

+ + +

- g -

+ + +

- h -

+ + +

- i -

+ + +

- k -

+ + +

- l -

+ + +

- m -

+ + +

- n -

+ + +

- o -

+ + +

- p -

+ + +

- r -

+ + +

- s -

+ + +

- t -

+ + +

- u -

+ + +

- v -

+ + +

- w -

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2build_2fuse__config_8h_source.html b/doc/html/fuse-3_817_81-rc0_2build_2fuse__config_8h_source.html new file mode 100644 index 0000000..b04c8ed --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2build_2fuse__config_8h_source.html @@ -0,0 +1,103 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/build/fuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define HAVE_BACKTRACE
+
9
+
10#define HAVE_COPY_FILE_RANGE
+
11
+
12#define HAVE_FALLOCATE
+
13
+
14#define HAVE_FDATASYNC
+
15
+
16#define HAVE_FORK
+
17
+
18#define HAVE_FSTATAT
+
19
+
20#define HAVE_ICONV
+
21
+
22#define HAVE_OPENAT
+
23
+
24#define HAVE_PIPE2
+
25
+
26#define HAVE_POSIX_FALLOCATE
+
27
+
28#define HAVE_READLINKAT
+
29
+
30#define HAVE_SETXATTR
+
31
+
32#define HAVE_SPLICE
+
33
+
34#define HAVE_STRUCT_STAT_ST_ATIM
+
35
+
36#undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
37
+
38#define HAVE_UTIMENSAT
+
39
+
40#define HAVE_VMSPLICE
+
41
+
42#define PACKAGE_VERSION "3.17.1-rc0"
+
43
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2build_2libfuse__config_8h_source.html b/doc/html/fuse-3_817_81-rc0_2build_2libfuse__config_8h_source.html new file mode 100644 index 0000000..080cd65 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2build_2libfuse__config_8h_source.html @@ -0,0 +1,77 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/build/libfuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
libfuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define FUSE_HOTFIX_VERSION 1
+
9
+
10#define FUSE_MAJOR_VERSION 3
+
11
+
12#define FUSE_MINOR_VERSION 17
+
13
+
14#define FUSE_RC_VERSION rc0
+
15
+
16#define LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS 1
+
17
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2build_2meson-private_2sanitycheckc_8c_source.html b/doc/html/fuse-3_817_81-rc0_2build_2meson-private_2sanitycheckc_8c_source.html new file mode 100644 index 0000000..d31cef4 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2build_2meson-private_2sanitycheckc_8c_source.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/build/meson-private/sanitycheckc.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
sanitycheckc.c
+
+
+
1int main(void) { int class=0; return class; }
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2cuse_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2cuse_8c.html new file mode 100644 index 0000000..20f4357 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2cuse_8c.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2cuse_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2cuse_8c_source.html new file mode 100644 index 0000000..aaeb157 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2cuse_8c_source.html @@ -0,0 +1,395 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+ +
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c.html new file mode 100644 index 0000000..a7c5b70 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c_source.html new file mode 100644 index 0000000..7f2c71f --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2hello_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2hello_8c.html new file mode 100644 index 0000000..ae96cf2 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2hello_8c.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_ASYNC_READ
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2hello_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2hello_8c_source.html new file mode 100644 index 0000000..aaf22c0 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2hello_8c_source.html @@ -0,0 +1,246 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60 return NULL;
+
61}
+
62
+
63static int hello_getattr(const char *path, struct stat *stbuf,
+
64 struct fuse_file_info *fi)
+
65{
+
66 (void) fi;
+
67 int res = 0;
+
68
+
69 memset(stbuf, 0, sizeof(struct stat));
+
70 if (strcmp(path, "/") == 0) {
+
71 stbuf->st_mode = S_IFDIR | 0755;
+
72 stbuf->st_nlink = 2;
+
73 } else if (strcmp(path+1, options.filename) == 0) {
+
74 stbuf->st_mode = S_IFREG | 0444;
+
75 stbuf->st_nlink = 1;
+
76 stbuf->st_size = strlen(options.contents);
+
77 } else
+
78 res = -ENOENT;
+
79
+
80 return res;
+
81}
+
82
+
83static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
84 off_t offset, struct fuse_file_info *fi,
+
85 enum fuse_readdir_flags flags)
+
86{
+
87 (void) offset;
+
88 (void) fi;
+
89 (void) flags;
+
90
+
91 if (strcmp(path, "/") != 0)
+
92 return -ENOENT;
+
93
+
94 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
95 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
96 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
97
+
98 return 0;
+
99}
+
100
+
101static int hello_open(const char *path, struct fuse_file_info *fi)
+
102{
+
103 if (strcmp(path+1, options.filename) != 0)
+
104 return -ENOENT;
+
105
+
106 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
107 return -EACCES;
+
108
+
109 return 0;
+
110}
+
111
+
112static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
113 struct fuse_file_info *fi)
+
114{
+
115 size_t len;
+
116 (void) fi;
+
117 if(strcmp(path+1, options.filename) != 0)
+
118 return -ENOENT;
+
119
+
120 len = strlen(options.contents);
+
121 if (offset < len) {
+
122 if (offset + size > len)
+
123 size = len - offset;
+
124 memcpy(buf, options.contents + offset, size);
+
125 } else
+
126 size = 0;
+
127
+
128 return size;
+
129}
+
130
+
131static const struct fuse_operations hello_oper = {
+
132 .init = hello_init,
+
133 .getattr = hello_getattr,
+
134 .readdir = hello_readdir,
+
135 .open = hello_open,
+
136 .read = hello_read,
+
137};
+
138
+
139static void show_help(const char *progname)
+
140{
+
141 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
142 printf("File-system specific options:\n"
+
143 " --name=<s> Name of the \"hello\" file\n"
+
144 " (default: \"hello\")\n"
+
145 " --contents=<s> Contents \"hello\" file\n"
+
146 " (default \"Hello, World!\\n\")\n"
+
147 "\n");
+
148}
+
149
+
150int main(int argc, char *argv[])
+
151{
+
152 int ret;
+
153 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
154
+
155 /* Set defaults -- we have to use strdup so that
+
156 fuse_opt_parse can free the defaults if other
+
157 values are specified */
+
158 options.filename = strdup("hello");
+
159 options.contents = strdup("Hello World!\n");
+
160
+
161 /* Parse options */
+
162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
163 return 1;
+
164
+
165 /* When --help is specified, first print our own file-system
+
166 specific help text, then signal fuse_main to show
+
167 additional help (by adding `--help` to the options again)
+
168 without usage: line (by setting argv[0] to the empty
+
169 string) */
+
170 if (options.show_help) {
+
171 show_help(argv[0]);
+
172 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
173 args.argv[0][0] = '\0';
+
174 }
+
175
+
176 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
177 fuse_opt_free_args(&args);
+
178 return ret;
+
179}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c.html new file mode 100644 index 0000000..173ed97 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c.html @@ -0,0 +1,388 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_CAP_ASYNC_READ
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c_source.html new file mode 100644 index 0000000..9b845ed --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
21#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
22
+
23#include <fuse_lowlevel.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <unistd.h>
+
30#include <assert.h>
+
31
+
32static const char *hello_str = "Hello World!\n";
+
33static const char *hello_name = "hello";
+
34
+
35static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
36{
+
37 stbuf->st_ino = ino;
+
38 switch (ino) {
+
39 case 1:
+
40 stbuf->st_mode = S_IFDIR | 0755;
+
41 stbuf->st_nlink = 2;
+
42 break;
+
43
+
44 case 2:
+
45 stbuf->st_mode = S_IFREG | 0444;
+
46 stbuf->st_nlink = 1;
+
47 stbuf->st_size = strlen(hello_str);
+
48 break;
+
49
+
50 default:
+
51 return -1;
+
52 }
+
53 return 0;
+
54}
+
55
+
56static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
57{
+
58 (void)userdata;
+
59
+
60 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
61 conn->no_interrupt = 1;
+
62}
+
63
+
64static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
65 struct fuse_file_info *fi)
+
66{
+
67 struct stat stbuf;
+
68
+
69 (void) fi;
+
70
+
71 memset(&stbuf, 0, sizeof(stbuf));
+
72 if (hello_stat(ino, &stbuf) == -1)
+
73 fuse_reply_err(req, ENOENT);
+
74 else
+
75 fuse_reply_attr(req, &stbuf, 1.0);
+
76}
+
77
+
78static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
79{
+
80 struct fuse_entry_param e;
+
81
+
82 if (parent != 1 || strcmp(name, hello_name) != 0)
+
83 fuse_reply_err(req, ENOENT);
+
84 else {
+
85 memset(&e, 0, sizeof(e));
+
86 e.ino = 2;
+
87 e.attr_timeout = 1.0;
+
88 e.entry_timeout = 1.0;
+
89 hello_stat(e.ino, &e.attr);
+
90
+
91 fuse_reply_entry(req, &e);
+
92 }
+
93}
+
94
+
95struct dirbuf {
+
96 char *p;
+
97 size_t size;
+
98};
+
99
+
100static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
101 fuse_ino_t ino)
+
102{
+
103 struct stat stbuf;
+
104 size_t oldsize = b->size;
+
105 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
106 b->p = (char *) realloc(b->p, b->size);
+
107 memset(&stbuf, 0, sizeof(stbuf));
+
108 stbuf.st_ino = ino;
+
109 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
110 b->size);
+
111}
+
112
+
113#define min(x, y) ((x) < (y) ? (x) : (y))
+
114
+
115static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
116 off_t off, size_t maxsize)
+
117{
+
118 if (off < bufsize)
+
119 return fuse_reply_buf(req, buf + off,
+
120 min(bufsize - off, maxsize));
+
121 else
+
122 return fuse_reply_buf(req, NULL, 0);
+
123}
+
124
+
125static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
126 off_t off, struct fuse_file_info *fi)
+
127{
+
128 (void) fi;
+
129
+
130 if (ino != 1)
+
131 fuse_reply_err(req, ENOTDIR);
+
132 else {
+
133 struct dirbuf b;
+
134
+
135 memset(&b, 0, sizeof(b));
+
136 dirbuf_add(req, &b, ".", 1);
+
137 dirbuf_add(req, &b, "..", 1);
+
138 dirbuf_add(req, &b, hello_name, 2);
+
139 reply_buf_limited(req, b.p, b.size, off, size);
+
140 free(b.p);
+
141 }
+
142}
+
143
+
144static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
145 struct fuse_file_info *fi)
+
146{
+
147 if (ino != 2)
+
148 fuse_reply_err(req, EISDIR);
+
149 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
150 fuse_reply_err(req, EACCES);
+
151 else
+
152 fuse_reply_open(req, fi);
+
153}
+
154
+
155static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
156 off_t off, struct fuse_file_info *fi)
+
157{
+
158 (void) fi;
+
159
+
160 assert(ino == 2);
+
161 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
162}
+
163
+
164static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
165 size_t size)
+
166{
+
167 (void)size;
+
168 assert(ino == 1 || ino == 2);
+
169 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
170 {
+
171 const char *buf = "hello_ll_getxattr_value";
+
172 fuse_reply_buf(req, buf, strlen(buf));
+
173 }
+
174 else
+
175 {
+
176 fuse_reply_err(req, ENOTSUP);
+
177 }
+
178}
+
179
+
180static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
181 const char *value, size_t size, int flags)
+
182{
+
183 (void)flags;
+
184 (void)size;
+
185 assert(ino == 1 || ino == 2);
+
186 const char* exp_val = "hello_ll_setxattr_value";
+
187 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
188 strlen(exp_val) == size &&
+
189 strncmp(value, exp_val, size) == 0)
+
190 {
+
191 fuse_reply_err(req, 0);
+
192 }
+
193 else
+
194 {
+
195 fuse_reply_err(req, ENOTSUP);
+
196 }
+
197}
+
198
+
199static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
200{
+
201 assert(ino == 1 || ino == 2);
+
202 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
203 {
+
204 fuse_reply_err(req, 0);
+
205 }
+
206 else
+
207 {
+
208 fuse_reply_err(req, ENOTSUP);
+
209 }
+
210}
+
211
+
212static const struct fuse_lowlevel_ops hello_ll_oper = {
+
213 .init = hello_ll_init,
+
214 .lookup = hello_ll_lookup,
+
215 .getattr = hello_ll_getattr,
+
216 .readdir = hello_ll_readdir,
+
217 .open = hello_ll_open,
+
218 .read = hello_ll_read,
+
219 .setxattr = hello_ll_setxattr,
+
220 .getxattr = hello_ll_getxattr,
+
221 .removexattr = hello_ll_removexattr,
+
222};
+
223
+
224int main(int argc, char *argv[])
+
225{
+
226 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
227 struct fuse_session *se;
+
228 struct fuse_cmdline_opts opts;
+
229 struct fuse_loop_config *config;
+
230 int ret = -1;
+
231
+
232 if (fuse_parse_cmdline(&args, &opts) != 0)
+
233 return 1;
+
234 if (opts.show_help) {
+
235 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
238 ret = 0;
+
239 goto err_out1;
+
240 } else if (opts.show_version) {
+
241 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
243 ret = 0;
+
244 goto err_out1;
+
245 }
+
246
+
247 if(opts.mountpoint == NULL) {
+
248 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
249 printf(" %s --help\n", argv[0]);
+
250 ret = 1;
+
251 goto err_out1;
+
252 }
+
253
+
254 se = fuse_session_new(&args, &hello_ll_oper,
+
255 sizeof(hello_ll_oper), NULL);
+
256 if (se == NULL)
+
257 goto err_out1;
+
258
+
259 if (fuse_set_signal_handlers(se) != 0)
+
260 goto err_out2;
+
261
+
262 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
263 goto err_out3;
+
264
+
265 fuse_daemonize(opts.foreground);
+
266
+
267 /* Block until ctrl+c or fusermount -u */
+
268 if (opts.singlethread)
+
269 ret = fuse_session_loop(se);
+
270 else {
+
271 config = fuse_loop_cfg_create();
+
272 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
273 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
274 ret = fuse_session_loop_mt(se, config);
+
275 fuse_loop_cfg_destroy(config);
+
276 config = NULL;
+
277 }
+
278
+ +
280err_out3:
+ +
282err_out2:
+ +
284err_out1:
+
285 free(opts.mountpoint);
+
286 fuse_opt_free_args(&args);
+
287
+
288 return ret ? 1 : 0;
+
289}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c.html new file mode 100644 index 0000000..a768811 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c.html @@ -0,0 +1,391 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_CAP_ASYNC_READ
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c_source.html new file mode 100644 index 0000000..a86534c --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2hello__ll__uds_8c_source.html @@ -0,0 +1,442 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %lu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c.html new file mode 100644 index 0000000..e98b223 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c.html @@ -0,0 +1,390 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c_source.html new file mode 100644 index 0000000..38b1667 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2invalidate__path_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c.html new file mode 100644 index 0000000..3c6c414 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c.html @@ -0,0 +1,294 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c_source.html new file mode 100644 index 0000000..734a602 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h.html b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h.html new file mode 100644 index 0000000..d47ed71 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h_source.html b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h_source.html new file mode 100644 index 0000000..f372852 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c.html new file mode 100644 index 0000000..e38be8b --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c.html @@ -0,0 +1,138 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program tests the ioctl.c example file systsem.
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c_source.html new file mode 100644 index 0000000..69c003d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2ioctl__client_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program tests the ioctl.c example file systsem.
+
7
+
8 This program can be distributed under the terms of the GNU GPLv2.
+
9 See the file COPYING.
+
10*/
+
11
+
24#include <sys/types.h>
+
25#include <fcntl.h>
+
26#include <sys/stat.h>
+
27#include <sys/ioctl.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <ctype.h>
+
31#include <errno.h>
+
32#include <unistd.h>
+
33#include "ioctl.h"
+
34
+
35const char *usage =
+
36"Usage: fioclient FIOC_FILE [size]\n"
+
37"\n"
+
38"Get size if <size> is omitted, set size otherwise\n"
+
39"\n";
+
40
+
41int main(int argc, char **argv)
+
42{
+
43 size_t size;
+
44 int fd;
+
45 int ret = 0;
+
46
+
47 if (argc < 2) {
+
48 fprintf(stderr, "%s", usage);
+
49 return 1;
+
50 }
+
51
+
52 fd = open(argv[1], O_RDWR);
+
53 if (fd < 0) {
+
54 perror("open");
+
55 return 1;
+
56 }
+
57
+
58 if (argc == 2) {
+
59 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
60 perror("ioctl");
+
61 ret = 1;
+
62 goto out;
+
63 }
+
64 printf("%zu\n", size);
+
65 } else {
+
66 size = strtoul(argv[2], NULL, 0);
+
67 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
68 perror("ioctl");
+
69 ret = 1;
+
70 goto out;
+
71 }
+
72 }
+
73out:
+
74 close(fd);
+
75 return ret;
+
76}
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c.html new file mode 100644 index 0000000..ce23ba0 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c.html @@ -0,0 +1,474 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/notify_inval_entry.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_entry.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

+

It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

+

To see the effect, first start the file system with the --no-notify

$ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
+

Observe that ls always prints the correct directory contents (since readdir output is not cached)::

$ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+Time_is_15h_48m_33s  current_time
+Time_is_15h_48m_34s  current_time
+Time_is_15h_48m_35s  current_time
+

However, if you try to access a file by name the kernel will report that it still exists:

$ file=$(ls mnt/); echo $file
+Time_is_15h_50m_09s
+$ sleep 5; stat mnt/$file
+  File: ‘mnt/Time_is_15h_50m_09s’
+  Size: 32                Blocks: 0          IO Block: 4096   regular file
+Device: 2ah/42d     Inode: 3           Links: 1
+Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+

Only once the kernel cache timeout has been reached will the stat call fail:

$ sleep 30; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+

In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

$ notify_inval_entry --update-interval=1 --timeout=30 mnt/
+$ file=$(ls mnt/); stat mnt/$file
+  File: ‘mnt/Time_is_20h_42m_11s’
+  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+Device: 2ah/42d     Inode: 2           Links: 1
+Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+$ sleep 1; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+

To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

+

+Compilation

+
gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <signal.h>
+
#include <stddef.h>
+
#include <sys/stat.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
#define MAX_STR_LEN 128
+
static char file_name[MAX_STR_LEN];
+
static fuse_ino_t file_ino = 2;
+
static int lookup_cnt = 0;
+
static pthread_t main_thread;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
float timeout;
+
int update_interval;
+
int only_expire;
+
};
+
static struct options options = {
+
.timeout = 5,
+
.no_notify = 0,
+
.update_interval = 1,
+
.only_expire = 0,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+
OPTION("--timeout=%f", timeout),
+
OPTION("--only-expire", only_expire),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == file_ino) {
+
stbuf->st_mode = S_IFREG | 0000;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = 0;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, file_name) == 0) {
+
e.ino = file_ino;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = options.timeout;
+
e.entry_timeout = options.timeout;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == file_ino)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, options.timeout);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, file_name, file_ino);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
time_t t;
+
struct tm *now;
+
ssize_t ret;
+
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
ret = strftime(file_name, MAX_STR_LEN,
+
"Time_is_%Hh_%Mm_%Ss", now);
+
assert(ret != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
char *old_name;
+
+
+
while(!fuse_session_exited(se)) {
+
old_name = strdup(file_name);
+
update_fs();
+
+
if (!options.no_notify && lookup_cnt) {
+
if(options.only_expire) { // expire entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
+
// no kernel support
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
printf("Exiting...\n");
+
+ +
// Make sure to exit now, rather than on next request from userspace
+
pthread_kill(main_thread, SIGPIPE);
+
+
break;
+
}
+
// 1) ret == 0: successful expire of an existing entry
+
// 2) ret == -ENOENT: kernel has already expired the entry /
+
// entry does not exist anymore in the kernel
+
assert(ret == 0 || ret == -ENOENT);
+
} else { // invalidate entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
}
+
}
+
free(old_name);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --timeout=<secs> Timeout for kernel caches\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
" --only-expire Expire entries instead of invalidating them\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), &se);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
// Needed to ensure that the main thread continues/restarts processing as soon
+
// as the fuse session ends (immediately after calling fuse_session_exit() )
+
// and not only on the next request from userspace
+
main_thread = pthread_self();
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread) {
+
ret = fuse_session_loop(se);
+
} else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_entry.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c_source.html new file mode 100644 index 0000000..d0bbcbb --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__entry_8c_source.html @@ -0,0 +1,424 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/notify_inval_entry.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_entry.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
79#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
80
+
81#include <fuse_lowlevel.h>
+
82#include <stdio.h>
+
83#include <stdlib.h>
+
84#include <string.h>
+
85#include <errno.h>
+
86#include <fcntl.h>
+
87#include <assert.h>
+
88#include <signal.h>
+
89#include <stddef.h>
+
90#include <sys/stat.h>
+
91#include <unistd.h>
+
92#include <pthread.h>
+
93
+
94#define MAX_STR_LEN 128
+
95static char file_name[MAX_STR_LEN];
+
96static fuse_ino_t file_ino = 2;
+
97static int lookup_cnt = 0;
+
98static pthread_t main_thread;
+
99
+
100/* Command line parsing */
+
101struct options {
+
102 int no_notify;
+
103 float timeout;
+
104 int update_interval;
+
105 int only_expire;
+
106};
+
107static struct options options = {
+
108 .timeout = 5,
+
109 .no_notify = 0,
+
110 .update_interval = 1,
+
111 .only_expire = 0,
+
112};
+
113
+
114#define OPTION(t, p) \
+
115 { t, offsetof(struct options, p), 1 }
+
116static const struct fuse_opt option_spec[] = {
+
117 OPTION("--no-notify", no_notify),
+
118 OPTION("--update-interval=%d", update_interval),
+
119 OPTION("--timeout=%f", timeout),
+
120 OPTION("--only-expire", only_expire),
+ +
122};
+
123
+
124static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
125 stbuf->st_ino = ino;
+
126 if (ino == FUSE_ROOT_ID) {
+
127 stbuf->st_mode = S_IFDIR | 0755;
+
128 stbuf->st_nlink = 1;
+
129 }
+
130
+
131 else if (ino == file_ino) {
+
132 stbuf->st_mode = S_IFREG | 0000;
+
133 stbuf->st_nlink = 1;
+
134 stbuf->st_size = 0;
+
135 }
+
136
+
137 else
+
138 return -1;
+
139
+
140 return 0;
+
141}
+
142
+
143static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
144 (void)userdata;
+
145
+
146 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
147 conn->no_interrupt = 1;
+
148}
+
149
+
150static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
151 const char *name) {
+
152 struct fuse_entry_param e;
+
153 memset(&e, 0, sizeof(e));
+
154
+
155 if (parent != FUSE_ROOT_ID)
+
156 goto err_out;
+
157 else if (strcmp(name, file_name) == 0) {
+
158 e.ino = file_ino;
+
159 lookup_cnt++;
+
160 } else
+
161 goto err_out;
+
162
+
163 e.attr_timeout = options.timeout;
+
164 e.entry_timeout = options.timeout;
+
165 if (tfs_stat(e.ino, &e.attr) != 0)
+
166 goto err_out;
+
167 fuse_reply_entry(req, &e);
+
168 return;
+
169
+
170err_out:
+
171 fuse_reply_err(req, ENOENT);
+
172}
+
173
+
174static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
175 uint64_t nlookup) {
+
176 (void) req;
+
177 if(ino == file_ino)
+
178 lookup_cnt -= nlookup;
+
179 else
+
180 assert(ino == FUSE_ROOT_ID);
+
181 fuse_reply_none(req);
+
182}
+
183
+
184static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
185 struct fuse_file_info *fi) {
+
186 struct stat stbuf;
+
187
+
188 (void) fi;
+
189
+
190 memset(&stbuf, 0, sizeof(stbuf));
+
191 if (tfs_stat(ino, &stbuf) != 0)
+
192 fuse_reply_err(req, ENOENT);
+
193 else
+
194 fuse_reply_attr(req, &stbuf, options.timeout);
+
195}
+
196
+
197struct dirbuf {
+
198 char *p;
+
199 size_t size;
+
200};
+
201
+
202static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
203 fuse_ino_t ino) {
+
204 struct stat stbuf;
+
205 size_t oldsize = b->size;
+
206 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
207 b->p = (char *) realloc(b->p, b->size);
+
208 memset(&stbuf, 0, sizeof(stbuf));
+
209 stbuf.st_ino = ino;
+
210 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
211 b->size);
+
212}
+
213
+
214#define min(x, y) ((x) < (y) ? (x) : (y))
+
215
+
216static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
217 off_t off, size_t maxsize) {
+
218 if (off < bufsize)
+
219 return fuse_reply_buf(req, buf + off,
+
220 min(bufsize - off, maxsize));
+
221 else
+
222 return fuse_reply_buf(req, NULL, 0);
+
223}
+
224
+
225static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
226 off_t off, struct fuse_file_info *fi) {
+
227 (void) fi;
+
228
+
229 if (ino != FUSE_ROOT_ID)
+
230 fuse_reply_err(req, ENOTDIR);
+
231 else {
+
232 struct dirbuf b;
+
233
+
234 memset(&b, 0, sizeof(b));
+
235 dirbuf_add(req, &b, file_name, file_ino);
+
236 reply_buf_limited(req, b.p, b.size, off, size);
+
237 free(b.p);
+
238 }
+
239}
+
240
+
241static const struct fuse_lowlevel_ops tfs_oper = {
+
242 .init = tfs_init,
+
243 .lookup = tfs_lookup,
+
244 .getattr = tfs_getattr,
+
245 .readdir = tfs_readdir,
+
246 .forget = tfs_forget,
+
247};
+
248
+
249static void update_fs(void) {
+
250 time_t t;
+
251 struct tm *now;
+
252 ssize_t ret;
+
253
+
254 t = time(NULL);
+
255 now = localtime(&t);
+
256 assert(now != NULL);
+
257
+
258 ret = strftime(file_name, MAX_STR_LEN,
+
259 "Time_is_%Hh_%Mm_%Ss", now);
+
260 assert(ret != 0);
+
261}
+
262
+
263static void* update_fs_loop(void *data) {
+
264 struct fuse_session *se = (struct fuse_session*) data;
+
265 char *old_name;
+
266
+
267
+
268 while(!fuse_session_exited(se)) {
+
269 old_name = strdup(file_name);
+
270 update_fs();
+
271
+
272 if (!options.no_notify && lookup_cnt) {
+
273 if(options.only_expire) { // expire entry
+ +
275 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
276
+
277 // no kernel support
+
278 if (ret == -ENOSYS) {
+
279 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
280 printf("Exiting...\n");
+
281
+ +
283 // Make sure to exit now, rather than on next request from userspace
+
284 pthread_kill(main_thread, SIGPIPE);
+
285
+
286 break;
+
287 }
+
288 // 1) ret == 0: successful expire of an existing entry
+
289 // 2) ret == -ENOENT: kernel has already expired the entry /
+
290 // entry does not exist anymore in the kernel
+
291 assert(ret == 0 || ret == -ENOENT);
+
292 } else { // invalidate entry
+ +
294 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
295 }
+
296 }
+
297 free(old_name);
+
298 sleep(options.update_interval);
+
299 }
+
300 return NULL;
+
301}
+
302
+
303static void show_help(const char *progname)
+
304{
+
305 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
306 printf("File-system specific options:\n"
+
307 " --timeout=<secs> Timeout for kernel caches\n"
+
308 " --update-interval=<secs> Update-rate of file system contents\n"
+
309 " --no-notify Disable kernel notifications\n"
+
310 " --only-expire Expire entries instead of invalidating them\n"
+
311 "\n");
+
312}
+
313
+
314int main(int argc, char *argv[]) {
+
315 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
316 struct fuse_session *se;
+
317 struct fuse_cmdline_opts opts;
+
318 struct fuse_loop_config *config;
+
319 pthread_t updater;
+
320 int ret = -1;
+
321
+
322 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
323 return 1;
+
324
+
325 if (fuse_parse_cmdline(&args, &opts) != 0)
+
326 return 1;
+
327 if (opts.show_help) {
+
328 show_help(argv[0]);
+ + +
331 ret = 0;
+
332 goto err_out1;
+
333 } else if (opts.show_version) {
+
334 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
336 ret = 0;
+
337 goto err_out1;
+
338 }
+
339
+
340 /* Initial contents */
+
341 update_fs();
+
342
+
343 se = fuse_session_new(&args, &tfs_oper,
+
344 sizeof(tfs_oper), &se);
+
345 if (se == NULL)
+
346 goto err_out1;
+
347
+
348 if (fuse_set_signal_handlers(se) != 0)
+
349 goto err_out2;
+
350
+
351 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
352 goto err_out3;
+
353
+
354 fuse_daemonize(opts.foreground);
+
355
+
356 // Needed to ensure that the main thread continues/restarts processing as soon
+
357 // as the fuse session ends (immediately after calling fuse_session_exit() )
+
358 // and not only on the next request from userspace
+
359 main_thread = pthread_self();
+
360
+
361 /* Start thread to update file contents */
+
362 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
363 if (ret != 0) {
+
364 fprintf(stderr, "pthread_create failed with %s\n",
+
365 strerror(ret));
+
366 goto err_out3;
+
367 }
+
368
+
369 /* Block until ctrl+c or fusermount -u */
+
370 if (opts.singlethread) {
+
371 ret = fuse_session_loop(se);
+
372 } else {
+
373 config = fuse_loop_cfg_create();
+
374 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
375 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
376 ret = fuse_session_loop_mt(se, config);
+
377 fuse_loop_cfg_destroy(config);
+
378 config = NULL;
+
379 }
+
380
+ +
382err_out3:
+ +
384err_out2:
+ +
386err_out1:
+
387 free(opts.mountpoint);
+
388 fuse_opt_free_args(&args);
+
389
+
390 return ret ? 1 : 0;
+
391}
+
392
+
393
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c.html new file mode 100644 index 0000000..f9d5969 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c.html @@ -0,0 +1,483 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/notify_inval_inode.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_inode.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

+

To see the effect, first start the file system with the --no-notify option:

+

$ notify_inval_inode –update-interval=1 –no-notify mnt/

+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

 $ notify_inval_inode --update-interval=1 mnt/
+ $ for i in 1 2 3 4 5; do
+ >     cat mnt/current_time
+ >     sleep 1
+ > done
+ The current time is 15:58:40
+ The current time is 15:58:41
+ The current time is 15:58:42
+ The current time is 15:58:43
+ The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
#include <stdatomic.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static size_t file_size;
+
static _Atomic bool is_stop = false;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_destroy(void *userarg)
+
{
+
(void)userarg;
+
+
is_stop = true;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO)
+
fuse_reply_open(req, fi);
+
else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.destroy = tfs_destroy,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
+
while(!is_stop) {
+
update_fs();
+
if (!options.no_notify && lookup_cnt) {
+
/* Only send notification if the kernel is aware of the inode */
+
+
/* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
int ret =
+
fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
if ((ret != 0 && !is_stop) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0) {
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+ +
free(opts.mountpoint);
+
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_inode.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c_source.html new file mode 100644 index 0000000..e0ba327 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2notify__inval__inode_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/notify_inval_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_inode.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
63
+
64#include <fuse_lowlevel.h>
+
65#include <stdio.h>
+
66#include <stdlib.h>
+
67#include <string.h>
+
68#include <errno.h>
+
69#include <fcntl.h>
+
70#include <assert.h>
+
71#include <stddef.h>
+
72#include <unistd.h>
+
73#include <pthread.h>
+
74#include <stdbool.h>
+
75#include <stdatomic.h>
+
76
+
77/* We can't actually tell the kernel that there is no
+
78 timeout, so we just send a big value */
+
79#define NO_TIMEOUT 500000
+
80
+
81#define MAX_STR_LEN 128
+
82#define FILE_INO 2
+
83#define FILE_NAME "current_time"
+
84static char file_contents[MAX_STR_LEN];
+
85static int lookup_cnt = 0;
+
86static size_t file_size;
+
87static _Atomic bool is_stop = false;
+
88
+
89/* Command line parsing */
+
90struct options {
+
91 int no_notify;
+
92 int update_interval;
+
93};
+
94static struct options options = {
+
95 .no_notify = 0,
+
96 .update_interval = 1,
+
97};
+
98
+
99#define OPTION(t, p) \
+
100 { t, offsetof(struct options, p), 1 }
+
101static const struct fuse_opt option_spec[] = {
+
102 OPTION("--no-notify", no_notify),
+
103 OPTION("--update-interval=%d", update_interval),
+ +
105};
+
106
+
107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
108 stbuf->st_ino = ino;
+
109 if (ino == FUSE_ROOT_ID) {
+
110 stbuf->st_mode = S_IFDIR | 0755;
+
111 stbuf->st_nlink = 1;
+
112 }
+
113
+
114 else if (ino == FILE_INO) {
+
115 stbuf->st_mode = S_IFREG | 0444;
+
116 stbuf->st_nlink = 1;
+
117 stbuf->st_size = file_size;
+
118 }
+
119
+
120 else
+
121 return -1;
+
122
+
123 return 0;
+
124}
+
125
+
126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
127 (void)userdata;
+
128
+
129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
130 conn->no_interrupt = 1;
+
131}
+
132
+
133static void tfs_destroy(void *userarg)
+
134{
+
135 (void)userarg;
+
136
+
137 is_stop = true;
+
138}
+
139
+
140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
141 const char *name) {
+
142 struct fuse_entry_param e;
+
143 memset(&e, 0, sizeof(e));
+
144
+
145 if (parent != FUSE_ROOT_ID)
+
146 goto err_out;
+
147 else if (strcmp(name, FILE_NAME) == 0) {
+
148 e.ino = FILE_INO;
+
149 lookup_cnt++;
+
150 } else
+
151 goto err_out;
+
152
+
153 e.attr_timeout = NO_TIMEOUT;
+
154 e.entry_timeout = NO_TIMEOUT;
+
155 if (tfs_stat(e.ino, &e.attr) != 0)
+
156 goto err_out;
+
157 fuse_reply_entry(req, &e);
+
158 return;
+
159
+
160err_out:
+
161 fuse_reply_err(req, ENOENT);
+
162}
+
163
+
164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
165 uint64_t nlookup) {
+
166 (void) req;
+
167 if(ino == FILE_INO)
+
168 lookup_cnt -= nlookup;
+
169 else
+
170 assert(ino == FUSE_ROOT_ID);
+
171 fuse_reply_none(req);
+
172}
+
173
+
174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
175 struct fuse_file_info *fi) {
+
176 struct stat stbuf;
+
177
+
178 (void) fi;
+
179
+
180 memset(&stbuf, 0, sizeof(stbuf));
+
181 if (tfs_stat(ino, &stbuf) != 0)
+
182 fuse_reply_err(req, ENOENT);
+
183 else
+
184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
185}
+
186
+
187struct dirbuf {
+
188 char *p;
+
189 size_t size;
+
190};
+
191
+
192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
193 fuse_ino_t ino) {
+
194 struct stat stbuf;
+
195 size_t oldsize = b->size;
+
196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
197 b->p = (char *) realloc(b->p, b->size);
+
198 memset(&stbuf, 0, sizeof(stbuf));
+
199 stbuf.st_ino = ino;
+
200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
201 b->size);
+
202}
+
203
+
204#define min(x, y) ((x) < (y) ? (x) : (y))
+
205
+
206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
207 off_t off, size_t maxsize) {
+
208 if (off < bufsize)
+
209 return fuse_reply_buf(req, buf + off,
+
210 min(bufsize - off, maxsize));
+
211 else
+
212 return fuse_reply_buf(req, NULL, 0);
+
213}
+
214
+
215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
216 off_t off, struct fuse_file_info *fi) {
+
217 (void) fi;
+
218
+
219 if (ino != FUSE_ROOT_ID)
+
220 fuse_reply_err(req, ENOTDIR);
+
221 else {
+
222 struct dirbuf b;
+
223
+
224 memset(&b, 0, sizeof(b));
+
225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
226 reply_buf_limited(req, b.p, b.size, off, size);
+
227 free(b.p);
+
228 }
+
229}
+
230
+
231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
232 struct fuse_file_info *fi) {
+
233
+
234 /* Make cache persistent even if file is closed,
+
235 this makes it easier to see the effects */
+
236 fi->keep_cache = 1;
+
237
+
238 if (ino == FUSE_ROOT_ID)
+
239 fuse_reply_err(req, EISDIR);
+
240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
241 fuse_reply_err(req, EACCES);
+
242 else if (ino == FILE_INO)
+
243 fuse_reply_open(req, fi);
+
244 else {
+
245 // This should not happen
+
246 fprintf(stderr, "Got open for non-existing inode!\n");
+
247 fuse_reply_err(req, ENOENT);
+
248 }
+
249}
+
250
+
251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
252 off_t off, struct fuse_file_info *fi) {
+
253 (void) fi;
+
254
+
255 assert(ino == FILE_INO);
+
256 reply_buf_limited(req, file_contents, file_size, off, size);
+
257}
+
258
+
259static const struct fuse_lowlevel_ops tfs_oper = {
+
260 .init = tfs_init,
+
261 .destroy = tfs_destroy,
+
262 .lookup = tfs_lookup,
+
263 .getattr = tfs_getattr,
+
264 .readdir = tfs_readdir,
+
265 .open = tfs_open,
+
266 .read = tfs_read,
+
267 .forget = tfs_forget,
+
268};
+
269
+
270static void update_fs(void) {
+
271 struct tm *now;
+
272 time_t t;
+
273 t = time(NULL);
+
274 now = localtime(&t);
+
275 assert(now != NULL);
+
276
+
277 file_size = strftime(file_contents, MAX_STR_LEN,
+
278 "The current time is %H:%M:%S\n", now);
+
279 assert(file_size != 0);
+
280}
+
281
+
282static void* update_fs_loop(void *data) {
+
283 struct fuse_session *se = (struct fuse_session*) data;
+
284
+
285 while(!is_stop) {
+
286 update_fs();
+
287 if (!options.no_notify && lookup_cnt) {
+
288 /* Only send notification if the kernel is aware of the inode */
+
289
+
290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
291 * might come up during umount, when kernel side already releases
+
292 * all inodes, but does not send FUSE_DESTROY yet.
+
293 */
+
294 int ret =
+
295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
296 if ((ret != 0 && !is_stop) &&
+
297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
298 fprintf(stderr,
+
299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
300 strerror(-ret), -ret);
+
301 abort();
+
302 }
+
303 }
+
304 sleep(options.update_interval);
+
305 }
+
306 return NULL;
+
307}
+
308
+
309static void show_help(const char *progname)
+
310{
+
311 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
312 printf("File-system specific options:\n"
+
313 " --update-interval=<secs> Update-rate of file system contents\n"
+
314 " --no-notify Disable kernel notifications\n"
+
315 "\n");
+
316}
+
317
+
318int main(int argc, char *argv[]) {
+
319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
320 struct fuse_session *se;
+
321 struct fuse_cmdline_opts opts;
+
322 struct fuse_loop_config *config;
+
323 pthread_t updater;
+
324 int ret = -1;
+
325
+
326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
327 return 1;
+
328
+
329 if (fuse_parse_cmdline(&args, &opts) != 0) {
+
330 ret = 1;
+
331 goto err_out1;
+
332 }
+
333
+
334 if (opts.show_help) {
+
335 show_help(argv[0]);
+ + +
338 ret = 0;
+
339 goto err_out1;
+
340 } else if (opts.show_version) {
+
341 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
343 ret = 0;
+
344 goto err_out1;
+
345 }
+
346
+
347 /* Initial contents */
+
348 update_fs();
+
349
+
350 se = fuse_session_new(&args, &tfs_oper,
+
351 sizeof(tfs_oper), NULL);
+
352 if (se == NULL)
+
353 goto err_out1;
+
354
+
355 if (fuse_set_signal_handlers(se) != 0)
+
356 goto err_out2;
+
357
+
358 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
359 goto err_out3;
+
360
+
361 fuse_daemonize(opts.foreground);
+
362
+
363 /* Start thread to update file contents */
+
364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
365 if (ret != 0) {
+
366 fprintf(stderr, "pthread_create failed with %s\n",
+
367 strerror(ret));
+
368 goto err_out3;
+
369 }
+
370
+
371 /* Block until ctrl+c or fusermount -u */
+
372 if (opts.singlethread)
+
373 ret = fuse_session_loop(se);
+
374 else {
+
375 config = fuse_loop_cfg_create();
+
376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
378 ret = fuse_session_loop_mt(se, config);
+
379 fuse_loop_cfg_destroy(config);
+
380 config = NULL;
+
381 }
+
382
+ +
384err_out3:
+ +
386err_out2:
+ +
388err_out1:
+
389 fuse_opt_free_args(&args);
+
390 free(opts.mountpoint);
+
391
+
392 return ret ? 1 : 0;
+
393}
+
394
+
395
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..44fba67 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..81bdf06 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2null_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2null_8c.html new file mode 100644 index 0000000..1d090ae --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2null_8c.html @@ -0,0 +1,763 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2null_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2null_8c_source.html new file mode 100644 index 0000000..19f4125 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c.html new file mode 100644 index 0000000..e582659 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c.html @@ -0,0 +1,663 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#ifdef __FreeBSD__
+
#include <sys/socket.h>
+
#include <sys/un.h>
+
#endif
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = -posix_fallocate(fd, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c_source.html new file mode 100644 index 0000000..f3bf200 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2passthrough_8c_source.html @@ -0,0 +1,649 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#ifdef __FreeBSD__
+
44#include <sys/socket.h>
+
45#include <sys/un.h>
+
46#endif
+
47#include <sys/time.h>
+
48#ifdef HAVE_SETXATTR
+
49#include <sys/xattr.h>
+
50#endif
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54static int fill_dir_plus = 0;
+
55
+
56static void *xmp_init(struct fuse_conn_info *conn,
+
57 struct fuse_config *cfg)
+
58{
+
59 (void) conn;
+
60 cfg->use_ino = 1;
+
61
+
62 /* parallel_direct_writes feature depends on direct_io features.
+
63 To make parallel_direct_writes valid, need either set cfg->direct_io
+
64 in current function (recommended in high level API) or set fi->direct_io
+
65 in xmp_create() or xmp_open(). */
+
66 // cfg->direct_io = 1;
+ +
68
+
69 /* Pick up changes from lower filesystem right away. This is
+
70 also necessary for better hardlink support. When the kernel
+
71 calls the unlink() handler, it does not know the inode of
+
72 the to-be-removed entry and can therefore not invalidate
+
73 the cache of the associated inode - resulting in an
+
74 incorrect st_nlink value being reported for any remaining
+
75 hardlinks to this inode. */
+
76 if (!cfg->auto_cache) {
+
77 cfg->entry_timeout = 0;
+
78 cfg->attr_timeout = 0;
+
79 cfg->negative_timeout = 0;
+
80 }
+
81
+
82 return NULL;
+
83}
+
84
+
85static int xmp_getattr(const char *path, struct stat *stbuf,
+
86 struct fuse_file_info *fi)
+
87{
+
88 (void) fi;
+
89 int res;
+
90
+
91 res = lstat(path, stbuf);
+
92 if (res == -1)
+
93 return -errno;
+
94
+
95 return 0;
+
96}
+
97
+
98static int xmp_access(const char *path, int mask)
+
99{
+
100 int res;
+
101
+
102 res = access(path, mask);
+
103 if (res == -1)
+
104 return -errno;
+
105
+
106 return 0;
+
107}
+
108
+
109static int xmp_readlink(const char *path, char *buf, size_t size)
+
110{
+
111 int res;
+
112
+
113 res = readlink(path, buf, size - 1);
+
114 if (res == -1)
+
115 return -errno;
+
116
+
117 buf[res] = '\0';
+
118 return 0;
+
119}
+
120
+
121
+
122static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
123 off_t offset, struct fuse_file_info *fi,
+
124 enum fuse_readdir_flags flags)
+
125{
+
126 DIR *dp;
+
127 struct dirent *de;
+
128
+
129 (void) offset;
+
130 (void) fi;
+
131 (void) flags;
+
132
+
133 dp = opendir(path);
+
134 if (dp == NULL)
+
135 return -errno;
+
136
+
137 while ((de = readdir(dp)) != NULL) {
+
138 struct stat st;
+
139 if (fill_dir_plus) {
+
140 fstatat(dirfd(dp), de->d_name, &st,
+
141 AT_SYMLINK_NOFOLLOW);
+
142 } else {
+
143 memset(&st, 0, sizeof(st));
+
144 st.st_ino = de->d_ino;
+
145 st.st_mode = de->d_type << 12;
+
146 }
+
147 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
148 break;
+
149 }
+
150
+
151 closedir(dp);
+
152 return 0;
+
153}
+
154
+
155static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
156{
+
157 int res;
+
158
+
159 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
160 if (res == -1)
+
161 return -errno;
+
162
+
163 return 0;
+
164}
+
165
+
166static int xmp_mkdir(const char *path, mode_t mode)
+
167{
+
168 int res;
+
169
+
170 res = mkdir(path, mode);
+
171 if (res == -1)
+
172 return -errno;
+
173
+
174 return 0;
+
175}
+
176
+
177static int xmp_unlink(const char *path)
+
178{
+
179 int res;
+
180
+
181 res = unlink(path);
+
182 if (res == -1)
+
183 return -errno;
+
184
+
185 return 0;
+
186}
+
187
+
188static int xmp_rmdir(const char *path)
+
189{
+
190 int res;
+
191
+
192 res = rmdir(path);
+
193 if (res == -1)
+
194 return -errno;
+
195
+
196 return 0;
+
197}
+
198
+
199static int xmp_symlink(const char *from, const char *to)
+
200{
+
201 int res;
+
202
+
203 res = symlink(from, to);
+
204 if (res == -1)
+
205 return -errno;
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
211{
+
212 int res;
+
213
+
214 if (flags)
+
215 return -EINVAL;
+
216
+
217 res = rename(from, to);
+
218 if (res == -1)
+
219 return -errno;
+
220
+
221 return 0;
+
222}
+
223
+
224static int xmp_link(const char *from, const char *to)
+
225{
+
226 int res;
+
227
+
228 res = link(from, to);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_chmod(const char *path, mode_t mode,
+
236 struct fuse_file_info *fi)
+
237{
+
238 (void) fi;
+
239 int res;
+
240
+
241 res = chmod(path, mode);
+
242 if (res == -1)
+
243 return -errno;
+
244
+
245 return 0;
+
246}
+
247
+
248static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
249 struct fuse_file_info *fi)
+
250{
+
251 (void) fi;
+
252 int res;
+
253
+
254 res = lchown(path, uid, gid);
+
255 if (res == -1)
+
256 return -errno;
+
257
+
258 return 0;
+
259}
+
260
+
261static int xmp_truncate(const char *path, off_t size,
+
262 struct fuse_file_info *fi)
+
263{
+
264 int res;
+
265
+
266 if (fi != NULL)
+
267 res = ftruncate(fi->fh, size);
+
268 else
+
269 res = truncate(path, size);
+
270 if (res == -1)
+
271 return -errno;
+
272
+
273 return 0;
+
274}
+
275
+
276#ifdef HAVE_UTIMENSAT
+
277static int xmp_utimens(const char *path, const struct timespec ts[2],
+
278 struct fuse_file_info *fi)
+
279{
+
280 (void) fi;
+
281 int res;
+
282
+
283 /* don't use utime/utimes since they follow symlinks */
+
284 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
285 if (res == -1)
+
286 return -errno;
+
287
+
288 return 0;
+
289}
+
290#endif
+
291
+
292static int xmp_create(const char *path, mode_t mode,
+
293 struct fuse_file_info *fi)
+
294{
+
295 int res;
+
296
+
297 res = open(path, fi->flags, mode);
+
298 if (res == -1)
+
299 return -errno;
+
300
+
301 fi->fh = res;
+
302 return 0;
+
303}
+
304
+
305static int xmp_open(const char *path, struct fuse_file_info *fi)
+
306{
+
307 int res;
+
308
+
309 res = open(path, fi->flags);
+
310 if (res == -1)
+
311 return -errno;
+
312
+
313 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
314 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
315 for writes to the same file). */
+
316 if (fi->flags & O_DIRECT) {
+
317 fi->direct_io = 1;
+ +
319 }
+
320
+
321 fi->fh = res;
+
322 return 0;
+
323}
+
324
+
325static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
326 struct fuse_file_info *fi)
+
327{
+
328 int fd;
+
329 int res;
+
330
+
331 if(fi == NULL)
+
332 fd = open(path, O_RDONLY);
+
333 else
+
334 fd = fi->fh;
+
335
+
336 if (fd == -1)
+
337 return -errno;
+
338
+
339 res = pread(fd, buf, size, offset);
+
340 if (res == -1)
+
341 res = -errno;
+
342
+
343 if(fi == NULL)
+
344 close(fd);
+
345 return res;
+
346}
+
347
+
348static int xmp_write(const char *path, const char *buf, size_t size,
+
349 off_t offset, struct fuse_file_info *fi)
+
350{
+
351 int fd;
+
352 int res;
+
353
+
354 (void) fi;
+
355 if(fi == NULL)
+
356 fd = open(path, O_WRONLY);
+
357 else
+
358 fd = fi->fh;
+
359
+
360 if (fd == -1)
+
361 return -errno;
+
362
+
363 res = pwrite(fd, buf, size, offset);
+
364 if (res == -1)
+
365 res = -errno;
+
366
+
367 if(fi == NULL)
+
368 close(fd);
+
369 return res;
+
370}
+
371
+
372static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
373{
+
374 int res;
+
375
+
376 res = statvfs(path, stbuf);
+
377 if (res == -1)
+
378 return -errno;
+
379
+
380 return 0;
+
381}
+
382
+
383static int xmp_release(const char *path, struct fuse_file_info *fi)
+
384{
+
385 (void) path;
+
386 close(fi->fh);
+
387 return 0;
+
388}
+
389
+
390static int xmp_fsync(const char *path, int isdatasync,
+
391 struct fuse_file_info *fi)
+
392{
+
393 /* Just a stub. This method is optional and can safely be left
+
394 unimplemented */
+
395
+
396 (void) path;
+
397 (void) isdatasync;
+
398 (void) fi;
+
399 return 0;
+
400}
+
401
+
402#ifdef HAVE_POSIX_FALLOCATE
+
403static int xmp_fallocate(const char *path, int mode,
+
404 off_t offset, off_t length, struct fuse_file_info *fi)
+
405{
+
406 int fd;
+
407 int res;
+
408
+
409 (void) fi;
+
410
+
411 if (mode)
+
412 return -EOPNOTSUPP;
+
413
+
414 if(fi == NULL)
+
415 fd = open(path, O_WRONLY);
+
416 else
+
417 fd = fi->fh;
+
418
+
419 if (fd == -1)
+
420 return -errno;
+
421
+
422 res = -posix_fallocate(fd, offset, length);
+
423
+
424 if(fi == NULL)
+
425 close(fd);
+
426 return res;
+
427}
+
428#endif
+
429
+
430#ifdef HAVE_SETXATTR
+
431/* xattr operations are optional and can safely be left unimplemented */
+
432static int xmp_setxattr(const char *path, const char *name, const char *value,
+
433 size_t size, int flags)
+
434{
+
435 int res = lsetxattr(path, name, value, size, flags);
+
436 if (res == -1)
+
437 return -errno;
+
438 return 0;
+
439}
+
440
+
441static int xmp_getxattr(const char *path, const char *name, char *value,
+
442 size_t size)
+
443{
+
444 int res = lgetxattr(path, name, value, size);
+
445 if (res == -1)
+
446 return -errno;
+
447 return res;
+
448}
+
449
+
450static int xmp_listxattr(const char *path, char *list, size_t size)
+
451{
+
452 int res = llistxattr(path, list, size);
+
453 if (res == -1)
+
454 return -errno;
+
455 return res;
+
456}
+
457
+
458static int xmp_removexattr(const char *path, const char *name)
+
459{
+
460 int res = lremovexattr(path, name);
+
461 if (res == -1)
+
462 return -errno;
+
463 return 0;
+
464}
+
465#endif /* HAVE_SETXATTR */
+
466
+
467#ifdef HAVE_COPY_FILE_RANGE
+
468static ssize_t xmp_copy_file_range(const char *path_in,
+
469 struct fuse_file_info *fi_in,
+
470 off_t offset_in, const char *path_out,
+
471 struct fuse_file_info *fi_out,
+
472 off_t offset_out, size_t len, int flags)
+
473{
+
474 int fd_in, fd_out;
+
475 ssize_t res;
+
476
+
477 if(fi_in == NULL)
+
478 fd_in = open(path_in, O_RDONLY);
+
479 else
+
480 fd_in = fi_in->fh;
+
481
+
482 if (fd_in == -1)
+
483 return -errno;
+
484
+
485 if(fi_out == NULL)
+
486 fd_out = open(path_out, O_WRONLY);
+
487 else
+
488 fd_out = fi_out->fh;
+
489
+
490 if (fd_out == -1) {
+
491 close(fd_in);
+
492 return -errno;
+
493 }
+
494
+
495 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
496 flags);
+
497 if (res == -1)
+
498 res = -errno;
+
499
+
500 if (fi_out == NULL)
+
501 close(fd_out);
+
502 if (fi_in == NULL)
+
503 close(fd_in);
+
504
+
505 return res;
+
506}
+
507#endif
+
508
+
509static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
510{
+
511 int fd;
+
512 off_t res;
+
513
+
514 if (fi == NULL)
+
515 fd = open(path, O_RDONLY);
+
516 else
+
517 fd = fi->fh;
+
518
+
519 if (fd == -1)
+
520 return -errno;
+
521
+
522 res = lseek(fd, off, whence);
+
523 if (res == -1)
+
524 res = -errno;
+
525
+
526 if (fi == NULL)
+
527 close(fd);
+
528 return res;
+
529}
+
530
+
531static const struct fuse_operations xmp_oper = {
+
532 .init = xmp_init,
+
533 .getattr = xmp_getattr,
+
534 .access = xmp_access,
+
535 .readlink = xmp_readlink,
+
536 .readdir = xmp_readdir,
+
537 .mknod = xmp_mknod,
+
538 .mkdir = xmp_mkdir,
+
539 .symlink = xmp_symlink,
+
540 .unlink = xmp_unlink,
+
541 .rmdir = xmp_rmdir,
+
542 .rename = xmp_rename,
+
543 .link = xmp_link,
+
544 .chmod = xmp_chmod,
+
545 .chown = xmp_chown,
+
546 .truncate = xmp_truncate,
+
547#ifdef HAVE_UTIMENSAT
+
548 .utimens = xmp_utimens,
+
549#endif
+
550 .open = xmp_open,
+
551 .create = xmp_create,
+
552 .read = xmp_read,
+
553 .write = xmp_write,
+
554 .statfs = xmp_statfs,
+
555 .release = xmp_release,
+
556 .fsync = xmp_fsync,
+
557#ifdef HAVE_POSIX_FALLOCATE
+
558 .fallocate = xmp_fallocate,
+
559#endif
+
560#ifdef HAVE_SETXATTR
+
561 .setxattr = xmp_setxattr,
+
562 .getxattr = xmp_getxattr,
+
563 .listxattr = xmp_listxattr,
+
564 .removexattr = xmp_removexattr,
+
565#endif
+
566#ifdef HAVE_COPY_FILE_RANGE
+
567 .copy_file_range = xmp_copy_file_range,
+
568#endif
+
569 .lseek = xmp_lseek,
+
570};
+
571
+
572int main(int argc, char *argv[])
+
573{
+
574 enum { MAX_ARGS = 10 };
+
575 int i,new_argc;
+
576 char *new_argv[MAX_ARGS];
+
577
+
578 umask(0);
+
579 /* Process the "--plus" option apart */
+
580 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
581 if (!strcmp(argv[i], "--plus")) {
+
582 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
583 } else {
+
584 new_argv[new_argc++] = argv[i];
+
585 }
+
586 }
+
587 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
588}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c.html new file mode 100644 index 0000000..d659183 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c.html @@ -0,0 +1,766 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..a718584 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__fh_8c_source.html @@ -0,0 +1,751 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50static void *xmp_init(struct fuse_conn_info *conn,
+
51 struct fuse_config *cfg)
+
52{
+
53 (void) conn;
+
54 cfg->use_ino = 1;
+
55 cfg->nullpath_ok = 1;
+
56
+
57 /* parallel_direct_writes feature depends on direct_io features.
+
58 To make parallel_direct_writes valid, need either set cfg->direct_io
+
59 in current function (recommended in high level API) or set fi->direct_io
+
60 in xmp_create() or xmp_open(). */
+
61 // cfg->direct_io = 1;
+ +
63
+
64 /* Pick up changes from lower filesystem right away. This is
+
65 also necessary for better hardlink support. When the kernel
+
66 calls the unlink() handler, it does not know the inode of
+
67 the to-be-removed entry and can therefore not invalidate
+
68 the cache of the associated inode - resulting in an
+
69 incorrect st_nlink value being reported for any remaining
+
70 hardlinks to this inode. */
+
71 cfg->entry_timeout = 0;
+
72 cfg->attr_timeout = 0;
+
73 cfg->negative_timeout = 0;
+
74
+
75 return NULL;
+
76}
+
77
+
78static int xmp_getattr(const char *path, struct stat *stbuf,
+
79 struct fuse_file_info *fi)
+
80{
+
81 int res;
+
82
+
83 (void) path;
+
84
+
85 if(fi)
+
86 res = fstat(fi->fh, stbuf);
+
87 else
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118struct xmp_dirp {
+
119 DIR *dp;
+
120 struct dirent *entry;
+
121 off_t offset;
+
122};
+
123
+
124static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
125{
+
126 int res;
+
127 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
128 if (d == NULL)
+
129 return -ENOMEM;
+
130
+
131 d->dp = opendir(path);
+
132 if (d->dp == NULL) {
+
133 res = -errno;
+
134 free(d);
+
135 return res;
+
136 }
+
137 d->offset = 0;
+
138 d->entry = NULL;
+
139
+
140 fi->fh = (unsigned long) d;
+
141 return 0;
+
142}
+
143
+
144static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
145{
+
146 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
147}
+
148
+
149static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
150 off_t offset, struct fuse_file_info *fi,
+
151 enum fuse_readdir_flags flags)
+
152{
+
153 struct xmp_dirp *d = get_dirp(fi);
+
154
+
155 (void) path;
+
156 if (offset != d->offset) {
+
157#ifndef __FreeBSD__
+
158 seekdir(d->dp, offset);
+
159#else
+
160 /* Subtract the one that we add when calling
+
161 telldir() below */
+
162 seekdir(d->dp, offset-1);
+
163#endif
+
164 d->entry = NULL;
+
165 d->offset = offset;
+
166 }
+
167 while (1) {
+
168 struct stat st;
+
169 off_t nextoff;
+ +
171
+
172 if (!d->entry) {
+
173 d->entry = readdir(d->dp);
+
174 if (!d->entry)
+
175 break;
+
176 }
+
177#ifdef HAVE_FSTATAT
+
178 if (flags & FUSE_READDIR_PLUS) {
+
179 int res;
+
180
+
181 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
182 AT_SYMLINK_NOFOLLOW);
+
183 if (res != -1)
+
184 fill_flags |= FUSE_FILL_DIR_PLUS;
+
185 }
+
186#endif
+
187 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
188 memset(&st, 0, sizeof(st));
+
189 st.st_ino = d->entry->d_ino;
+
190 st.st_mode = d->entry->d_type << 12;
+
191 }
+
192 nextoff = telldir(d->dp);
+
193#ifdef __FreeBSD__
+
194 /* Under FreeBSD, telldir() may return 0 the first time
+
195 it is called. But for libfuse, an offset of zero
+
196 means that offsets are not supported, so we shift
+
197 everything by one. */
+
198 nextoff++;
+
199#endif
+
200 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
201 break;
+
202
+
203 d->entry = NULL;
+
204 d->offset = nextoff;
+
205 }
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
211{
+
212 struct xmp_dirp *d = get_dirp(fi);
+
213 (void) path;
+
214 closedir(d->dp);
+
215 free(d);
+
216 return 0;
+
217}
+
218
+
219static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
220{
+
221 int res;
+
222
+
223 if (S_ISFIFO(mode))
+
224 res = mkfifo(path, mode);
+
225 else
+
226 res = mknod(path, mode, rdev);
+
227 if (res == -1)
+
228 return -errno;
+
229
+
230 return 0;
+
231}
+
232
+
233static int xmp_mkdir(const char *path, mode_t mode)
+
234{
+
235 int res;
+
236
+
237 res = mkdir(path, mode);
+
238 if (res == -1)
+
239 return -errno;
+
240
+
241 return 0;
+
242}
+
243
+
244static int xmp_unlink(const char *path)
+
245{
+
246 int res;
+
247
+
248 res = unlink(path);
+
249 if (res == -1)
+
250 return -errno;
+
251
+
252 return 0;
+
253}
+
254
+
255static int xmp_rmdir(const char *path)
+
256{
+
257 int res;
+
258
+
259 res = rmdir(path);
+
260 if (res == -1)
+
261 return -errno;
+
262
+
263 return 0;
+
264}
+
265
+
266static int xmp_symlink(const char *from, const char *to)
+
267{
+
268 int res;
+
269
+
270 res = symlink(from, to);
+
271 if (res == -1)
+
272 return -errno;
+
273
+
274 return 0;
+
275}
+
276
+
277static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
278{
+
279 int res;
+
280
+
281 /* When we have renameat2() in libc, then we can implement flags */
+
282 if (flags)
+
283 return -EINVAL;
+
284
+
285 res = rename(from, to);
+
286 if (res == -1)
+
287 return -errno;
+
288
+
289 return 0;
+
290}
+
291
+
292static int xmp_link(const char *from, const char *to)
+
293{
+
294 int res;
+
295
+
296 res = link(from, to);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 return 0;
+
301}
+
302
+
303static int xmp_chmod(const char *path, mode_t mode,
+
304 struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 if(fi)
+
309 res = fchmod(fi->fh, mode);
+
310 else
+
311 res = chmod(path, mode);
+
312 if (res == -1)
+
313 return -errno;
+
314
+
315 return 0;
+
316}
+
317
+
318static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 int res;
+
322
+
323 if (fi)
+
324 res = fchown(fi->fh, uid, gid);
+
325 else
+
326 res = lchown(path, uid, gid);
+
327 if (res == -1)
+
328 return -errno;
+
329
+
330 return 0;
+
331}
+
332
+
333static int xmp_truncate(const char *path, off_t size,
+
334 struct fuse_file_info *fi)
+
335{
+
336 int res;
+
337
+
338 if(fi)
+
339 res = ftruncate(fi->fh, size);
+
340 else
+
341 res = truncate(path, size);
+
342
+
343 if (res == -1)
+
344 return -errno;
+
345
+
346 return 0;
+
347}
+
348
+
349#ifdef HAVE_UTIMENSAT
+
350static int xmp_utimens(const char *path, const struct timespec ts[2],
+
351 struct fuse_file_info *fi)
+
352{
+
353 int res;
+
354
+
355 /* don't use utime/utimes since they follow symlinks */
+
356 if (fi)
+
357 res = futimens(fi->fh, ts);
+
358 else
+
359 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
360 if (res == -1)
+
361 return -errno;
+
362
+
363 return 0;
+
364}
+
365#endif
+
366
+
367static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
368{
+
369 int fd;
+
370
+
371 fd = open(path, fi->flags, mode);
+
372 if (fd == -1)
+
373 return -errno;
+
374
+
375 fi->fh = fd;
+
376 return 0;
+
377}
+
378
+
379static int xmp_open(const char *path, struct fuse_file_info *fi)
+
380{
+
381 int fd;
+
382
+
383 fd = open(path, fi->flags);
+
384 if (fd == -1)
+
385 return -errno;
+
386
+
387 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
388 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
389 for writes to the same file). */
+
390 if (fi->flags & O_DIRECT) {
+
391 fi->direct_io = 1;
+ +
393 }
+
394
+
395 fi->fh = fd;
+
396 return 0;
+
397}
+
398
+
399static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
400 struct fuse_file_info *fi)
+
401{
+
402 int res;
+
403
+
404 (void) path;
+
405 res = pread(fi->fh, buf, size, offset);
+
406 if (res == -1)
+
407 res = -errno;
+
408
+
409 return res;
+
410}
+
411
+
412static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
413 size_t size, off_t offset, struct fuse_file_info *fi)
+
414{
+
415 struct fuse_bufvec *src;
+
416
+
417 (void) path;
+
418
+
419 src = malloc(sizeof(struct fuse_bufvec));
+
420 if (src == NULL)
+
421 return -ENOMEM;
+
422
+
423 *src = FUSE_BUFVEC_INIT(size);
+
424
+ +
426 src->buf[0].fd = fi->fh;
+
427 src->buf[0].pos = offset;
+
428
+
429 *bufp = src;
+
430
+
431 return 0;
+
432}
+
433
+
434static int xmp_write(const char *path, const char *buf, size_t size,
+
435 off_t offset, struct fuse_file_info *fi)
+
436{
+
437 int res;
+
438
+
439 (void) path;
+
440 res = pwrite(fi->fh, buf, size, offset);
+
441 if (res == -1)
+
442 res = -errno;
+
443
+
444 return res;
+
445}
+
446
+
447static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
448 off_t offset, struct fuse_file_info *fi)
+
449{
+
450 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
451
+
452 (void) path;
+
453
+ +
455 dst.buf[0].fd = fi->fh;
+
456 dst.buf[0].pos = offset;
+
457
+ +
459}
+
460
+
461static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
462{
+
463 int res;
+
464
+
465 res = statvfs(path, stbuf);
+
466 if (res == -1)
+
467 return -errno;
+
468
+
469 return 0;
+
470}
+
471
+
472static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
473{
+
474 int res;
+
475
+
476 (void) path;
+
477 /* This is called from every close on an open file, so call the
+
478 close on the underlying filesystem. But since flush may be
+
479 called multiple times for an open file, this must not really
+
480 close the file. This is important if used on a network
+
481 filesystem like NFS which flush the data/metadata on close() */
+
482 res = close(dup(fi->fh));
+
483 if (res == -1)
+
484 return -errno;
+
485
+
486 return 0;
+
487}
+
488
+
489static int xmp_release(const char *path, struct fuse_file_info *fi)
+
490{
+
491 (void) path;
+
492 close(fi->fh);
+
493
+
494 return 0;
+
495}
+
496
+
497static int xmp_fsync(const char *path, int isdatasync,
+
498 struct fuse_file_info *fi)
+
499{
+
500 int res;
+
501 (void) path;
+
502
+
503#ifndef HAVE_FDATASYNC
+
504 (void) isdatasync;
+
505#else
+
506 if (isdatasync)
+
507 res = fdatasync(fi->fh);
+
508 else
+
509#endif
+
510 res = fsync(fi->fh);
+
511 if (res == -1)
+
512 return -errno;
+
513
+
514 return 0;
+
515}
+
516
+
517#ifdef HAVE_POSIX_FALLOCATE
+
518static int xmp_fallocate(const char *path, int mode,
+
519 off_t offset, off_t length, struct fuse_file_info *fi)
+
520{
+
521 (void) path;
+
522
+
523 if (mode)
+
524 return -EOPNOTSUPP;
+
525
+
526 return -posix_fallocate(fi->fh, offset, length);
+
527}
+
528#endif
+
529
+
530#ifdef HAVE_SETXATTR
+
531/* xattr operations are optional and can safely be left unimplemented */
+
532static int xmp_setxattr(const char *path, const char *name, const char *value,
+
533 size_t size, int flags)
+
534{
+
535 int res = lsetxattr(path, name, value, size, flags);
+
536 if (res == -1)
+
537 return -errno;
+
538 return 0;
+
539}
+
540
+
541static int xmp_getxattr(const char *path, const char *name, char *value,
+
542 size_t size)
+
543{
+
544 int res = lgetxattr(path, name, value, size);
+
545 if (res == -1)
+
546 return -errno;
+
547 return res;
+
548}
+
549
+
550static int xmp_listxattr(const char *path, char *list, size_t size)
+
551{
+
552 int res = llistxattr(path, list, size);
+
553 if (res == -1)
+
554 return -errno;
+
555 return res;
+
556}
+
557
+
558static int xmp_removexattr(const char *path, const char *name)
+
559{
+
560 int res = lremovexattr(path, name);
+
561 if (res == -1)
+
562 return -errno;
+
563 return 0;
+
564}
+
565#endif /* HAVE_SETXATTR */
+
566
+
567#ifdef HAVE_LIBULOCKMGR
+
568static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
569 struct flock *lock)
+
570{
+
571 (void) path;
+
572
+
573 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
574 sizeof(fi->lock_owner));
+
575}
+
576#endif
+
577
+
578static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
579{
+
580 int res;
+
581 (void) path;
+
582
+
583 res = flock(fi->fh, op);
+
584 if (res == -1)
+
585 return -errno;
+
586
+
587 return 0;
+
588}
+
589
+
590#ifdef HAVE_COPY_FILE_RANGE
+
591static ssize_t xmp_copy_file_range(const char *path_in,
+
592 struct fuse_file_info *fi_in,
+
593 off_t off_in, const char *path_out,
+
594 struct fuse_file_info *fi_out,
+
595 off_t off_out, size_t len, int flags)
+
596{
+
597 ssize_t res;
+
598 (void) path_in;
+
599 (void) path_out;
+
600
+
601 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
602 flags);
+
603 if (res == -1)
+
604 return -errno;
+
605
+
606 return res;
+
607}
+
608#endif
+
609
+
610static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
611{
+
612 off_t res;
+
613 (void) path;
+
614
+
615 res = lseek(fi->fh, off, whence);
+
616 if (res == -1)
+
617 return -errno;
+
618
+
619 return res;
+
620}
+
621
+
622static const struct fuse_operations xmp_oper = {
+
623 .init = xmp_init,
+
624 .getattr = xmp_getattr,
+
625 .access = xmp_access,
+
626 .readlink = xmp_readlink,
+
627 .opendir = xmp_opendir,
+
628 .readdir = xmp_readdir,
+
629 .releasedir = xmp_releasedir,
+
630 .mknod = xmp_mknod,
+
631 .mkdir = xmp_mkdir,
+
632 .symlink = xmp_symlink,
+
633 .unlink = xmp_unlink,
+
634 .rmdir = xmp_rmdir,
+
635 .rename = xmp_rename,
+
636 .link = xmp_link,
+
637 .chmod = xmp_chmod,
+
638 .chown = xmp_chown,
+
639 .truncate = xmp_truncate,
+
640#ifdef HAVE_UTIMENSAT
+
641 .utimens = xmp_utimens,
+
642#endif
+
643 .create = xmp_create,
+
644 .open = xmp_open,
+
645 .read = xmp_read,
+
646 .read_buf = xmp_read_buf,
+
647 .write = xmp_write,
+
648 .write_buf = xmp_write_buf,
+
649 .statfs = xmp_statfs,
+
650 .flush = xmp_flush,
+
651 .release = xmp_release,
+
652 .fsync = xmp_fsync,
+
653#ifdef HAVE_POSIX_FALLOCATE
+
654 .fallocate = xmp_fallocate,
+
655#endif
+
656#ifdef HAVE_SETXATTR
+
657 .setxattr = xmp_setxattr,
+
658 .getxattr = xmp_getxattr,
+
659 .listxattr = xmp_listxattr,
+
660 .removexattr = xmp_removexattr,
+
661#endif
+
662#ifdef HAVE_LIBULOCKMGR
+
663 .lock = xmp_lock,
+
664#endif
+
665 .flock = xmp_flock,
+
666#ifdef HAVE_COPY_FILE_RANGE
+
667 .copy_file_range = xmp_copy_file_range,
+
668#endif
+
669 .lseek = xmp_lseek,
+
670};
+
671
+
672int main(int argc, char *argv[])
+
673{
+
674 umask(0);
+
675 return fuse_main(argc, argv, &xmp_oper, NULL);
+
676}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2passthrough__helpers_8h_source.html b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..3d21ffb --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__helpers_8h_source.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26/*
+
27 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
28 * operation
+
29 */
+
30static int mknod_wrapper(int dirfd, const char *path, const char *link,
+
31 int mode, dev_t rdev)
+
32{
+
33 int res;
+
34
+
35 if (S_ISREG(mode)) {
+
36 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
37 if (res >= 0)
+
38 res = close(res);
+
39 } else if (S_ISDIR(mode)) {
+
40 res = mkdirat(dirfd, path, mode);
+
41 } else if (S_ISLNK(mode) && link != NULL) {
+
42 res = symlinkat(link, dirfd, path);
+
43 } else if (S_ISFIFO(mode)) {
+
44 res = mkfifoat(dirfd, path, mode);
+
45#ifdef __FreeBSD__
+
46 } else if (S_ISSOCK(mode)) {
+
47 struct sockaddr_un su;
+
48 int fd;
+
49
+
50 if (strlen(path) >= sizeof(su.sun_path)) {
+
51 errno = ENAMETOOLONG;
+
52 return -1;
+
53 }
+
54 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
55 if (fd >= 0) {
+
56 /*
+
57 * We must bind the socket to the underlying file
+
58 * system to create the socket file, even though
+
59 * we'll never listen on this socket.
+
60 */
+
61 su.sun_family = AF_UNIX;
+
62 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
63 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
64 sizeof(su));
+
65 if (res == 0)
+
66 close(fd);
+
67 } else {
+
68 res = -1;
+
69 }
+
70#endif
+
71 } else {
+
72 res = mknodat(dirfd, path, mode, rdev);
+
73 }
+
74
+
75 return res;
+
76}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c.html new file mode 100644 index 0000000..eabfa9b --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c.html @@ -0,0 +1,1529 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three, but many operations are not implemented. In particular, it is not possible to remove files (or directories) because the code necessary to defer actual removal until the file is not opened anymore would make the example much more complicated.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
ino, out_buf.buf[0].size, (unsigned long) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err = EOPNOTSUPP;
+
(void) ino;
+
+
#ifdef HAVE_FALLOCATE
+
err = fallocate(fi->fh, mode, offset, length);
+
if (err < 0)
+
err = errno;
+
+
#elif defined(HAVE_POSIX_FALLOCATE)
+
if (mode) {
+
fuse_reply_err(req, EOPNOTSUPP);
+
return;
+
}
+
+
err = posix_fallocate(fi->fh, offset, length);
+
#endif
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, size=%zd, flags=0x%x)\n",
+
ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_FLOCK_LOCKS
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..db6d254 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2passthrough__ll_8c_source.html @@ -0,0 +1,1508 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
37#define _GNU_SOURCE
+
38#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
39
+
40#include <fuse_lowlevel.h>
+
41#include <unistd.h>
+
42#include <stdlib.h>
+
43#include <stdio.h>
+
44#include <stddef.h>
+
45#include <stdbool.h>
+
46#include <string.h>
+
47#include <limits.h>
+
48#include <dirent.h>
+
49#include <assert.h>
+
50#include <errno.h>
+
51#include <inttypes.h>
+
52#include <pthread.h>
+
53#include <sys/file.h>
+
54#include <sys/xattr.h>
+
55
+
56#include "passthrough_helpers.h"
+
57
+
58/* We are re-using pointers to our `struct lo_inode` and `struct
+
59 lo_dirp` elements as inodes. This means that we must be able to
+
60 store uintptr_t values in a fuse_ino_t variable. The following
+
61 incantation checks this condition at compile time. */
+
62#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
63_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
64 "fuse_ino_t too small to hold uintptr_t values!");
+
65#else
+
66struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
67 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
68 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
69#endif
+
70
+
71struct lo_inode {
+
72 struct lo_inode *next; /* protected by lo->mutex */
+
73 struct lo_inode *prev; /* protected by lo->mutex */
+
74 int fd;
+
75 ino_t ino;
+
76 dev_t dev;
+
77 uint64_t refcount; /* protected by lo->mutex */
+
78};
+
79
+
80enum {
+
81 CACHE_NEVER,
+
82 CACHE_NORMAL,
+
83 CACHE_ALWAYS,
+
84};
+
85
+
86struct lo_data {
+
87 pthread_mutex_t mutex;
+
88 int debug;
+
89 int writeback;
+
90 int flock;
+
91 int xattr;
+
92 char *source;
+
93 double timeout;
+
94 int cache;
+
95 int timeout_set;
+
96 struct lo_inode root; /* protected by lo->mutex */
+
97};
+
98
+
99static const struct fuse_opt lo_opts[] = {
+
100 { "writeback",
+
101 offsetof(struct lo_data, writeback), 1 },
+
102 { "no_writeback",
+
103 offsetof(struct lo_data, writeback), 0 },
+
104 { "source=%s",
+
105 offsetof(struct lo_data, source), 0 },
+
106 { "flock",
+
107 offsetof(struct lo_data, flock), 1 },
+
108 { "no_flock",
+
109 offsetof(struct lo_data, flock), 0 },
+
110 { "xattr",
+
111 offsetof(struct lo_data, xattr), 1 },
+
112 { "no_xattr",
+
113 offsetof(struct lo_data, xattr), 0 },
+
114 { "timeout=%lf",
+
115 offsetof(struct lo_data, timeout), 0 },
+
116 { "timeout=",
+
117 offsetof(struct lo_data, timeout_set), 1 },
+
118 { "cache=never",
+
119 offsetof(struct lo_data, cache), CACHE_NEVER },
+
120 { "cache=auto",
+
121 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
122 { "cache=always",
+
123 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
124
+ +
126};
+
127
+
128static void passthrough_ll_help(void)
+
129{
+
130 printf(
+
131" -o writeback Enable writeback\n"
+
132" -o no_writeback Disable write back\n"
+
133" -o source=/home/dir Source directory to be mounted\n"
+
134" -o flock Enable flock\n"
+
135" -o no_flock Disable flock\n"
+
136" -o xattr Enable xattr\n"
+
137" -o no_xattr Disable xattr\n"
+
138" -o timeout=1.0 Caching timeout\n"
+
139" -o timeout=0/1 Timeout is set\n"
+
140" -o cache=never Disable cache\n"
+
141" -o cache=auto Auto enable cache\n"
+
142" -o cache=always Cache always\n");
+
143}
+
144
+
145static struct lo_data *lo_data(fuse_req_t req)
+
146{
+
147 return (struct lo_data *) fuse_req_userdata(req);
+
148}
+
149
+
150static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
151{
+
152 if (ino == FUSE_ROOT_ID)
+
153 return &lo_data(req)->root;
+
154 else
+
155 return (struct lo_inode *) (uintptr_t) ino;
+
156}
+
157
+
158static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
159{
+
160 return lo_inode(req, ino)->fd;
+
161}
+
162
+
163static bool lo_debug(fuse_req_t req)
+
164{
+
165 return lo_data(req)->debug != 0;
+
166}
+
167
+
168static void lo_init(void *userdata,
+
169 struct fuse_conn_info *conn)
+
170{
+
171 struct lo_data *lo = (struct lo_data *)userdata;
+
172 bool has_flag;
+
173
+
174 if (lo->writeback) {
+
175 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
176 if (lo->debug && has_flag)
+
177 fuse_log(FUSE_LOG_DEBUG,
+
178 "lo_init: activating writeback\n");
+
179 }
+
180 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
181 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
182 if (lo->debug && has_flag)
+
183 fuse_log(FUSE_LOG_DEBUG,
+
184 "lo_init: activating flock locks\n");
+
185 }
+
186
+
187 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
188 conn->no_interrupt = 1;
+
189}
+
190
+
191static void lo_destroy(void *userdata)
+
192{
+
193 struct lo_data *lo = (struct lo_data*) userdata;
+
194
+
195 while (lo->root.next != &lo->root) {
+
196 struct lo_inode* next = lo->root.next;
+
197 lo->root.next = next->next;
+
198 close(next->fd);
+
199 free(next);
+
200 }
+
201}
+
202
+
203static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
204 struct fuse_file_info *fi)
+
205{
+
206 int res;
+
207 struct stat buf;
+
208 struct lo_data *lo = lo_data(req);
+
209 int fd = fi ? fi->fh : lo_fd(req, ino);
+
210
+
211 (void) fi;
+
212
+
213 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
214 if (res == -1)
+
215 return (void) fuse_reply_err(req, errno);
+
216
+
217 fuse_reply_attr(req, &buf, lo->timeout);
+
218}
+
219
+
220static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
221 int valid, struct fuse_file_info *fi)
+
222{
+
223 int saverr;
+
224 char procname[64];
+
225 struct lo_inode *inode = lo_inode(req, ino);
+
226 int ifd = inode->fd;
+
227 int res;
+
228
+
229 if (valid & FUSE_SET_ATTR_MODE) {
+
230 if (fi) {
+
231 res = fchmod(fi->fh, attr->st_mode);
+
232 } else {
+
233 sprintf(procname, "/proc/self/fd/%i", ifd);
+
234 res = chmod(procname, attr->st_mode);
+
235 }
+
236 if (res == -1)
+
237 goto out_err;
+
238 }
+
239 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
240 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
241 attr->st_uid : (uid_t) -1;
+
242 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
243 attr->st_gid : (gid_t) -1;
+
244
+
245 res = fchownat(ifd, "", uid, gid,
+
246 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
247 if (res == -1)
+
248 goto out_err;
+
249 }
+
250 if (valid & FUSE_SET_ATTR_SIZE) {
+
251 if (fi) {
+
252 res = ftruncate(fi->fh, attr->st_size);
+
253 } else {
+
254 sprintf(procname, "/proc/self/fd/%i", ifd);
+
255 res = truncate(procname, attr->st_size);
+
256 }
+
257 if (res == -1)
+
258 goto out_err;
+
259 }
+
260 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
261 struct timespec tv[2];
+
262
+
263 tv[0].tv_sec = 0;
+
264 tv[1].tv_sec = 0;
+
265 tv[0].tv_nsec = UTIME_OMIT;
+
266 tv[1].tv_nsec = UTIME_OMIT;
+
267
+
268 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
269 tv[0].tv_nsec = UTIME_NOW;
+
270 else if (valid & FUSE_SET_ATTR_ATIME)
+
271 tv[0] = attr->st_atim;
+
272
+
273 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
274 tv[1].tv_nsec = UTIME_NOW;
+
275 else if (valid & FUSE_SET_ATTR_MTIME)
+
276 tv[1] = attr->st_mtim;
+
277
+
278 if (fi)
+
279 res = futimens(fi->fh, tv);
+
280 else {
+
281 sprintf(procname, "/proc/self/fd/%i", ifd);
+
282 res = utimensat(AT_FDCWD, procname, tv, 0);
+
283 }
+
284 if (res == -1)
+
285 goto out_err;
+
286 }
+
287
+
288 return lo_getattr(req, ino, fi);
+
289
+
290out_err:
+
291 saverr = errno;
+
292 fuse_reply_err(req, saverr);
+
293}
+
294
+
295static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
296{
+
297 struct lo_inode *p;
+
298 struct lo_inode *ret = NULL;
+
299
+
300 pthread_mutex_lock(&lo->mutex);
+
301 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
302 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
303 assert(p->refcount > 0);
+
304 ret = p;
+
305 ret->refcount++;
+
306 break;
+
307 }
+
308 }
+
309 pthread_mutex_unlock(&lo->mutex);
+
310 return ret;
+
311}
+
312
+
313
+
314static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
315{
+
316 struct lo_inode *inode = NULL;
+
317 struct lo_inode *prev, *next;
+
318
+
319 inode = calloc(1, sizeof(struct lo_inode));
+
320 if (!inode)
+
321 return NULL;
+
322
+
323 inode->refcount = 1;
+
324 inode->fd = fd;
+
325 inode->ino = e->attr.st_ino;
+
326 inode->dev = e->attr.st_dev;
+
327
+
328 pthread_mutex_lock(&lo->mutex);
+
329 prev = &lo->root;
+
330 next = prev->next;
+
331 next->prev = inode;
+
332 inode->next = next;
+
333 inode->prev = prev;
+
334 prev->next = inode;
+
335 pthread_mutex_unlock(&lo->mutex);
+
336 return inode;
+
337}
+
338
+
339static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
340{
+
341 int res;
+
342 struct lo_data *lo = lo_data(req);
+
343
+
344 memset(e, 0, sizeof(*e));
+
345 e->attr_timeout = lo->timeout;
+
346 e->entry_timeout = lo->timeout;
+
347
+
348 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
349 if (res == -1)
+
350 return errno;
+
351
+
352 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
353
+
354 if (lo_debug(req))
+
355 fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
356 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
357
+
358 return 0;
+
359
+
360}
+
361
+
362static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
363 struct fuse_entry_param *e)
+
364{
+
365 int newfd;
+
366 int res;
+
367 int saverr;
+
368 struct lo_data *lo = lo_data(req);
+
369 struct lo_inode *inode;
+
370
+
371 memset(e, 0, sizeof(*e));
+
372 e->attr_timeout = lo->timeout;
+
373 e->entry_timeout = lo->timeout;
+
374
+
375 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
376 if (newfd == -1)
+
377 goto out_err;
+
378
+
379 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
380 if (res == -1)
+
381 goto out_err;
+
382
+
383 inode = lo_find(lo_data(req), &e->attr);
+
384 if (inode) {
+
385 close(newfd);
+
386 newfd = -1;
+
387 } else {
+
388 inode = create_new_inode(newfd, e, lo);
+
389 if (!inode)
+
390 goto out_err;
+
391 }
+
392 e->ino = (uintptr_t) inode;
+
393
+
394 if (lo_debug(req))
+
395 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
396 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
397
+
398 return 0;
+
399
+
400out_err:
+
401 saverr = errno;
+
402 if (newfd != -1)
+
403 close(newfd);
+
404 return saverr;
+
405}
+
406
+
407static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
408{
+
409 struct fuse_entry_param e;
+
410 int err;
+
411
+
412 if (lo_debug(req))
+
413 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
414 parent, name);
+
415
+
416 err = lo_do_lookup(req, parent, name, &e);
+
417 if (err)
+
418 fuse_reply_err(req, err);
+
419 else
+
420 fuse_reply_entry(req, &e);
+
421}
+
422
+
423static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
424 const char *name, mode_t mode, dev_t rdev,
+
425 const char *link)
+
426{
+
427 int res;
+
428 int saverr;
+
429 struct lo_inode *dir = lo_inode(req, parent);
+
430 struct fuse_entry_param e;
+
431
+
432 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
433
+
434 saverr = errno;
+
435 if (res == -1)
+
436 goto out;
+
437
+
438 saverr = lo_do_lookup(req, parent, name, &e);
+
439 if (saverr)
+
440 goto out;
+
441
+
442 if (lo_debug(req))
+
443 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
444 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
445
+
446 fuse_reply_entry(req, &e);
+
447 return;
+
448
+
449out:
+
450 fuse_reply_err(req, saverr);
+
451}
+
452
+
453static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
454 const char *name, mode_t mode, dev_t rdev)
+
455{
+
456 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
457}
+
458
+
459static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
460 mode_t mode)
+
461{
+
462 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
463}
+
464
+
465static void lo_symlink(fuse_req_t req, const char *link,
+
466 fuse_ino_t parent, const char *name)
+
467{
+
468 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
469}
+
470
+
471static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
472 const char *name)
+
473{
+
474 int res;
+
475 struct lo_data *lo = lo_data(req);
+
476 struct lo_inode *inode = lo_inode(req, ino);
+
477 struct fuse_entry_param e;
+
478 char procname[64];
+
479 int saverr;
+
480
+
481 memset(&e, 0, sizeof(struct fuse_entry_param));
+
482 e.attr_timeout = lo->timeout;
+
483 e.entry_timeout = lo->timeout;
+
484
+
485 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
486 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
487 AT_SYMLINK_FOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
492 if (res == -1)
+
493 goto out_err;
+
494
+
495 pthread_mutex_lock(&lo->mutex);
+
496 inode->refcount++;
+
497 pthread_mutex_unlock(&lo->mutex);
+
498 e.ino = (uintptr_t) inode;
+
499
+
500 if (lo_debug(req))
+
501 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
502 (unsigned long long) parent, name,
+
503 (unsigned long long) e.ino);
+
504
+
505 fuse_reply_entry(req, &e);
+
506 return;
+
507
+
508out_err:
+
509 saverr = errno;
+
510 fuse_reply_err(req, saverr);
+
511}
+
512
+
513static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
514{
+
515 int res;
+
516
+
517 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
518
+
519 fuse_reply_err(req, res == -1 ? errno : 0);
+
520}
+
521
+
522static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
523 fuse_ino_t newparent, const char *newname,
+
524 unsigned int flags)
+
525{
+
526 int res;
+
527
+
528 if (flags) {
+
529 fuse_reply_err(req, EINVAL);
+
530 return;
+
531 }
+
532
+
533 res = renameat(lo_fd(req, parent), name,
+
534 lo_fd(req, newparent), newname);
+
535
+
536 fuse_reply_err(req, res == -1 ? errno : 0);
+
537}
+
538
+
539static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
540{
+
541 int res;
+
542
+
543 res = unlinkat(lo_fd(req, parent), name, 0);
+
544
+
545 fuse_reply_err(req, res == -1 ? errno : 0);
+
546}
+
547
+
548static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
549{
+
550 if (!inode)
+
551 return;
+
552
+
553 pthread_mutex_lock(&lo->mutex);
+
554 assert(inode->refcount >= n);
+
555 inode->refcount -= n;
+
556 if (!inode->refcount) {
+
557 struct lo_inode *prev, *next;
+
558
+
559 prev = inode->prev;
+
560 next = inode->next;
+
561 next->prev = prev;
+
562 prev->next = next;
+
563
+
564 pthread_mutex_unlock(&lo->mutex);
+
565 close(inode->fd);
+
566 free(inode);
+
567
+
568 } else {
+
569 pthread_mutex_unlock(&lo->mutex);
+
570 }
+
571}
+
572
+
573static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
574{
+
575 struct lo_data *lo = lo_data(req);
+
576 struct lo_inode *inode = lo_inode(req, ino);
+
577
+
578 if (lo_debug(req)) {
+
579 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
580 (unsigned long long) ino,
+
581 (unsigned long long) inode->refcount,
+
582 (unsigned long long) nlookup);
+
583 }
+
584
+
585 unref_inode(lo, inode, nlookup);
+
586}
+
587
+
588static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
589{
+
590 lo_forget_one(req, ino, nlookup);
+
591 fuse_reply_none(req);
+
592}
+
593
+
594static void lo_forget_multi(fuse_req_t req, size_t count,
+
595 struct fuse_forget_data *forgets)
+
596{
+
597 int i;
+
598
+
599 for (i = 0; i < count; i++)
+
600 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
601 fuse_reply_none(req);
+
602}
+
603
+
604static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
605{
+
606 char buf[PATH_MAX + 1];
+
607 int res;
+
608
+
609 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
610 if (res == -1)
+
611 return (void) fuse_reply_err(req, errno);
+
612
+
613 if (res == sizeof(buf))
+
614 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
615
+
616 buf[res] = '\0';
+
617
+
618 fuse_reply_readlink(req, buf);
+
619}
+
620
+
621struct lo_dirp {
+
622 DIR *dp;
+
623 struct dirent *entry;
+
624 off_t offset;
+
625};
+
626
+
627static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
628{
+
629 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
630}
+
631
+
632static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
633{
+
634 int error = ENOMEM;
+
635 struct lo_data *lo = lo_data(req);
+
636 struct lo_dirp *d;
+
637 int fd = -1;
+
638
+
639 d = calloc(1, sizeof(struct lo_dirp));
+
640 if (d == NULL)
+
641 goto out_err;
+
642
+
643 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
644 if (fd == -1)
+
645 goto out_errno;
+
646
+
647 d->dp = fdopendir(fd);
+
648 if (d->dp == NULL)
+
649 goto out_errno;
+
650
+
651 d->offset = 0;
+
652 d->entry = NULL;
+
653
+
654 fi->fh = (uintptr_t) d;
+
655 if (lo->cache != CACHE_NEVER)
+
656 fi->cache_readdir = 1;
+
657 if (lo->cache == CACHE_ALWAYS)
+
658 fi->keep_cache = 1;
+
659 fuse_reply_open(req, fi);
+
660 return;
+
661
+
662out_errno:
+
663 error = errno;
+
664out_err:
+
665 if (d) {
+
666 if (fd != -1)
+
667 close(fd);
+
668 free(d);
+
669 }
+
670 fuse_reply_err(req, error);
+
671}
+
672
+
673static int is_dot_or_dotdot(const char *name)
+
674{
+
675 return name[0] == '.' && (name[1] == '\0' ||
+
676 (name[1] == '.' && name[2] == '\0'));
+
677}
+
678
+
679static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
680 off_t offset, struct fuse_file_info *fi, int plus)
+
681{
+
682 struct lo_dirp *d = lo_dirp(fi);
+
683 char *buf;
+
684 char *p;
+
685 size_t rem = size;
+
686 int err;
+
687
+
688 (void) ino;
+
689
+
690 buf = calloc(1, size);
+
691 if (!buf) {
+
692 err = ENOMEM;
+
693 goto error;
+
694 }
+
695 p = buf;
+
696
+
697 if (offset != d->offset) {
+
698 seekdir(d->dp, offset);
+
699 d->entry = NULL;
+
700 d->offset = offset;
+
701 }
+
702 while (1) {
+
703 size_t entsize;
+
704 off_t nextoff;
+
705 const char *name;
+
706
+
707 if (!d->entry) {
+
708 errno = 0;
+
709 d->entry = readdir(d->dp);
+
710 if (!d->entry) {
+
711 if (errno) { // Error
+
712 err = errno;
+
713 goto error;
+
714 } else { // End of stream
+
715 break;
+
716 }
+
717 }
+
718 }
+
719 nextoff = d->entry->d_off;
+
720 name = d->entry->d_name;
+
721 fuse_ino_t entry_ino = 0;
+
722 if (plus) {
+
723 struct fuse_entry_param e;
+
724 if (is_dot_or_dotdot(name)) {
+
725 e = (struct fuse_entry_param) {
+
726 .attr.st_ino = d->entry->d_ino,
+
727 .attr.st_mode = d->entry->d_type << 12,
+
728 };
+
729 } else {
+
730 err = lo_do_lookup(req, ino, name, &e);
+
731 if (err)
+
732 goto error;
+
733 entry_ino = e.ino;
+
734 }
+
735
+
736 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
737 &e, nextoff);
+
738 } else {
+
739 struct stat st = {
+
740 .st_ino = d->entry->d_ino,
+
741 .st_mode = d->entry->d_type << 12,
+
742 };
+
743 entsize = fuse_add_direntry(req, p, rem, name,
+
744 &st, nextoff);
+
745 }
+
746 if (entsize > rem) {
+
747 if (entry_ino != 0)
+
748 lo_forget_one(req, entry_ino, 1);
+
749 break;
+
750 }
+
751
+
752 p += entsize;
+
753 rem -= entsize;
+
754
+
755 d->entry = NULL;
+
756 d->offset = nextoff;
+
757 }
+
758
+
759 err = 0;
+
760error:
+
761 // If there's an error, we can only signal it if we haven't stored
+
762 // any entries yet - otherwise we'd end up with wrong lookup
+
763 // counts for the entries that are already in the buffer. So we
+
764 // return what we've collected until that point.
+
765 if (err && rem == size)
+
766 fuse_reply_err(req, err);
+
767 else
+
768 fuse_reply_buf(req, buf, size - rem);
+
769 free(buf);
+
770}
+
771
+
772static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
773 off_t offset, struct fuse_file_info *fi)
+
774{
+
775 lo_do_readdir(req, ino, size, offset, fi, 0);
+
776}
+
777
+
778static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
779 off_t offset, struct fuse_file_info *fi)
+
780{
+
781 lo_do_readdir(req, ino, size, offset, fi, 1);
+
782}
+
783
+
784static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
785{
+
786 struct lo_dirp *d = lo_dirp(fi);
+
787 (void) ino;
+
788 closedir(d->dp);
+
789 free(d);
+
790 fuse_reply_err(req, 0);
+
791}
+
792
+
793static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
794 mode_t mode, struct fuse_file_info *fi)
+
795{
+
796 int fd;
+
797 struct lo_data *lo = lo_data(req);
+
798 struct fuse_entry_param e;
+
799 int err;
+
800
+
801 if (lo_debug(req))
+
802 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
803 parent);
+
804
+
805 fd = openat(lo_fd(req, parent), ".",
+
806 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
807 if (fd == -1)
+
808 return (void) fuse_reply_err(req, errno);
+
809
+
810 fi->fh = fd;
+
811 if (lo->cache == CACHE_NEVER)
+
812 fi->direct_io = 1;
+
813 else if (lo->cache == CACHE_ALWAYS)
+
814 fi->keep_cache = 1;
+
815
+
816 /* parallel_direct_writes feature depends on direct_io features.
+
817 To make parallel_direct_writes valid, need set fi->direct_io
+
818 in current function. */
+
819 fi->parallel_direct_writes = 1;
+
820
+
821 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
822 if (err)
+
823 fuse_reply_err(req, err);
+
824 else
+
825 fuse_reply_create(req, &e, fi);
+
826}
+
827
+
828static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
829 mode_t mode, struct fuse_file_info *fi)
+
830{
+
831 int fd;
+
832 struct lo_data *lo = lo_data(req);
+
833 struct fuse_entry_param e;
+
834 int err;
+
835
+
836 if (lo_debug(req))
+
837 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
838 parent, name);
+
839
+
840 fd = openat(lo_fd(req, parent), name,
+
841 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
842 if (fd == -1)
+
843 return (void) fuse_reply_err(req, errno);
+
844
+
845 fi->fh = fd;
+
846 if (lo->cache == CACHE_NEVER)
+
847 fi->direct_io = 1;
+
848 else if (lo->cache == CACHE_ALWAYS)
+
849 fi->keep_cache = 1;
+
850
+
851 /* parallel_direct_writes feature depends on direct_io features.
+
852 To make parallel_direct_writes valid, need set fi->direct_io
+
853 in current function. */
+ +
855
+
856 err = lo_do_lookup(req, parent, name, &e);
+
857 if (err)
+
858 fuse_reply_err(req, err);
+
859 else
+
860 fuse_reply_create(req, &e, fi);
+
861}
+
862
+
863static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
864 struct fuse_file_info *fi)
+
865{
+
866 int res;
+
867 int fd = dirfd(lo_dirp(fi)->dp);
+
868 (void) ino;
+
869 if (datasync)
+
870 res = fdatasync(fd);
+
871 else
+
872 res = fsync(fd);
+
873 fuse_reply_err(req, res == -1 ? errno : 0);
+
874}
+
875
+
876static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
877{
+
878 int fd;
+
879 char buf[64];
+
880 struct lo_data *lo = lo_data(req);
+
881
+
882 if (lo_debug(req))
+
883 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
884 ino, fi->flags);
+
885
+
886 /* With writeback cache, kernel may send read requests even
+
887 when userspace opened write-only */
+
888 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
889 fi->flags &= ~O_ACCMODE;
+
890 fi->flags |= O_RDWR;
+
891 }
+
892
+
893 /* With writeback cache, O_APPEND is handled by the kernel.
+
894 This breaks atomicity (since the file may change in the
+
895 underlying filesystem, so that the kernel's idea of the
+
896 end of the file isn't accurate anymore). In this example,
+
897 we just accept that. A more rigorous filesystem may want
+
898 to return an error here */
+
899 if (lo->writeback && (fi->flags & O_APPEND))
+
900 fi->flags &= ~O_APPEND;
+
901
+
902 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
903 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
904 if (fd == -1)
+
905 return (void) fuse_reply_err(req, errno);
+
906
+
907 fi->fh = fd;
+
908 if (lo->cache == CACHE_NEVER)
+
909 fi->direct_io = 1;
+
910 else if (lo->cache == CACHE_ALWAYS)
+
911 fi->keep_cache = 1;
+
912
+
913 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
914 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
915 for writes to the same file in the kernel). */
+
916 if (fi->flags & O_DIRECT)
+
917 fi->direct_io = 1;
+
918
+
919 /* parallel_direct_writes feature depends on direct_io features.
+
920 To make parallel_direct_writes valid, need set fi->direct_io
+
921 in current function. */
+ +
923
+
924 fuse_reply_open(req, fi);
+
925}
+
926
+
927static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
928{
+
929 (void) ino;
+
930
+
931 close(fi->fh);
+
932 fuse_reply_err(req, 0);
+
933}
+
934
+
935static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
936{
+
937 int res;
+
938 (void) ino;
+
939 res = close(dup(fi->fh));
+
940 fuse_reply_err(req, res == -1 ? errno : 0);
+
941}
+
942
+
943static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
944 struct fuse_file_info *fi)
+
945{
+
946 int res;
+
947 (void) ino;
+
948 if (datasync)
+
949 res = fdatasync(fi->fh);
+
950 else
+
951 res = fsync(fi->fh);
+
952 fuse_reply_err(req, res == -1 ? errno : 0);
+
953}
+
954
+
955static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
956 off_t offset, struct fuse_file_info *fi)
+
957{
+
958 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
959
+
960 if (lo_debug(req))
+
961 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
962 "off=%lu)\n", ino, size, (unsigned long) offset);
+
963
+ +
965 buf.buf[0].fd = fi->fh;
+
966 buf.buf[0].pos = offset;
+
967
+ +
969}
+
970
+
971static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
972 struct fuse_bufvec *in_buf, off_t off,
+
973 struct fuse_file_info *fi)
+
974{
+
975 (void) ino;
+
976 ssize_t res;
+
977 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
978
+ +
980 out_buf.buf[0].fd = fi->fh;
+
981 out_buf.buf[0].pos = off;
+
982
+
983 if (lo_debug(req))
+
984 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
985 ino, out_buf.buf[0].size, (unsigned long) off);
+
986
+
987 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
988 if(res < 0)
+
989 fuse_reply_err(req, -res);
+
990 else
+
991 fuse_reply_write(req, (size_t) res);
+
992}
+
993
+
994static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
995{
+
996 int res;
+
997 struct statvfs stbuf;
+
998
+
999 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
1000 if (res == -1)
+
1001 fuse_reply_err(req, errno);
+
1002 else
+
1003 fuse_reply_statfs(req, &stbuf);
+
1004}
+
1005
+
1006static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1007 off_t offset, off_t length, struct fuse_file_info *fi)
+
1008{
+
1009 int err = EOPNOTSUPP;
+
1010 (void) ino;
+
1011
+
1012#ifdef HAVE_FALLOCATE
+
1013 err = fallocate(fi->fh, mode, offset, length);
+
1014 if (err < 0)
+
1015 err = errno;
+
1016
+
1017#elif defined(HAVE_POSIX_FALLOCATE)
+
1018 if (mode) {
+
1019 fuse_reply_err(req, EOPNOTSUPP);
+
1020 return;
+
1021 }
+
1022
+
1023 err = posix_fallocate(fi->fh, offset, length);
+
1024#endif
+
1025
+
1026 fuse_reply_err(req, err);
+
1027}
+
1028
+
1029static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1030 int op)
+
1031{
+
1032 int res;
+
1033 (void) ino;
+
1034
+
1035 res = flock(fi->fh, op);
+
1036
+
1037 fuse_reply_err(req, res == -1 ? errno : 0);
+
1038}
+
1039
+
1040static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1041 size_t size)
+
1042{
+
1043 char *value = NULL;
+
1044 char procname[64];
+
1045 struct lo_inode *inode = lo_inode(req, ino);
+
1046 ssize_t ret;
+
1047 int saverr;
+
1048
+
1049 saverr = ENOSYS;
+
1050 if (!lo_data(req)->xattr)
+
1051 goto out;
+
1052
+
1053 if (lo_debug(req)) {
+
1054 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1055 ino, name, size);
+
1056 }
+
1057
+
1058 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1059
+
1060 if (size) {
+
1061 value = malloc(size);
+
1062 if (!value)
+
1063 goto out_err;
+
1064
+
1065 ret = getxattr(procname, name, value, size);
+
1066 if (ret == -1)
+
1067 goto out_err;
+
1068 saverr = 0;
+
1069 if (ret == 0)
+
1070 goto out;
+
1071
+
1072 fuse_reply_buf(req, value, ret);
+
1073 } else {
+
1074 ret = getxattr(procname, name, NULL, 0);
+
1075 if (ret == -1)
+
1076 goto out_err;
+
1077
+
1078 fuse_reply_xattr(req, ret);
+
1079 }
+
1080out_free:
+
1081 free(value);
+
1082 return;
+
1083
+
1084out_err:
+
1085 saverr = errno;
+
1086out:
+
1087 fuse_reply_err(req, saverr);
+
1088 goto out_free;
+
1089}
+
1090
+
1091static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1092{
+
1093 char *value = NULL;
+
1094 char procname[64];
+
1095 struct lo_inode *inode = lo_inode(req, ino);
+
1096 ssize_t ret;
+
1097 int saverr;
+
1098
+
1099 saverr = ENOSYS;
+
1100 if (!lo_data(req)->xattr)
+
1101 goto out;
+
1102
+
1103 if (lo_debug(req)) {
+
1104 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1105 ino, size);
+
1106 }
+
1107
+
1108 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1109
+
1110 if (size) {
+
1111 value = malloc(size);
+
1112 if (!value)
+
1113 goto out_err;
+
1114
+
1115 ret = listxattr(procname, value, size);
+
1116 if (ret == -1)
+
1117 goto out_err;
+
1118 saverr = 0;
+
1119 if (ret == 0)
+
1120 goto out;
+
1121
+
1122 fuse_reply_buf(req, value, ret);
+
1123 } else {
+
1124 ret = listxattr(procname, NULL, 0);
+
1125 if (ret == -1)
+
1126 goto out_err;
+
1127
+
1128 fuse_reply_xattr(req, ret);
+
1129 }
+
1130out_free:
+
1131 free(value);
+
1132 return;
+
1133
+
1134out_err:
+
1135 saverr = errno;
+
1136out:
+
1137 fuse_reply_err(req, saverr);
+
1138 goto out_free;
+
1139}
+
1140
+
1141static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1142 const char *value, size_t size, int flags)
+
1143{
+
1144 char procname[64];
+
1145 struct lo_inode *inode = lo_inode(req, ino);
+
1146 ssize_t ret;
+
1147 int saverr;
+
1148
+
1149 saverr = ENOSYS;
+
1150 if (!lo_data(req)->xattr)
+
1151 goto out;
+
1152
+
1153 if (lo_debug(req)) {
+
1154 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1155 ino, name, value, size);
+
1156 }
+
1157
+
1158 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1159
+
1160 ret = setxattr(procname, name, value, size, flags);
+
1161 saverr = ret == -1 ? errno : 0;
+
1162
+
1163out:
+
1164 fuse_reply_err(req, saverr);
+
1165}
+
1166
+
1167static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1168{
+
1169 char procname[64];
+
1170 struct lo_inode *inode = lo_inode(req, ino);
+
1171 ssize_t ret;
+
1172 int saverr;
+
1173
+
1174 saverr = ENOSYS;
+
1175 if (!lo_data(req)->xattr)
+
1176 goto out;
+
1177
+
1178 if (lo_debug(req)) {
+
1179 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1180 ino, name);
+
1181 }
+
1182
+
1183 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1184
+
1185 ret = removexattr(procname, name);
+
1186 saverr = ret == -1 ? errno : 0;
+
1187
+
1188out:
+
1189 fuse_reply_err(req, saverr);
+
1190}
+
1191
+
1192#ifdef HAVE_COPY_FILE_RANGE
+
1193static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1194 struct fuse_file_info *fi_in,
+
1195 fuse_ino_t ino_out, off_t off_out,
+
1196 struct fuse_file_info *fi_out, size_t len,
+
1197 int flags)
+
1198{
+
1199 ssize_t res;
+
1200
+
1201 if (lo_debug(req))
+
1202 fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
1203 "off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
1204 "off=%lu, size=%zd, flags=0x%x)\n",
+
1205 ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
1206 len, flags);
+
1207
+
1208 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1209 flags);
+
1210 if (res < 0)
+
1211 fuse_reply_err(req, errno);
+
1212 else
+
1213 fuse_reply_write(req, res);
+
1214}
+
1215#endif
+
1216
+
1217static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 off_t res;
+
1221
+
1222 (void)ino;
+
1223 res = lseek(fi->fh, off, whence);
+
1224 if (res != -1)
+
1225 fuse_reply_lseek(req, res);
+
1226 else
+
1227 fuse_reply_err(req, errno);
+
1228}
+
1229
+
1230static const struct fuse_lowlevel_ops lo_oper = {
+
1231 .init = lo_init,
+
1232 .destroy = lo_destroy,
+
1233 .lookup = lo_lookup,
+
1234 .mkdir = lo_mkdir,
+
1235 .mknod = lo_mknod,
+
1236 .symlink = lo_symlink,
+
1237 .link = lo_link,
+
1238 .unlink = lo_unlink,
+
1239 .rmdir = lo_rmdir,
+
1240 .rename = lo_rename,
+
1241 .forget = lo_forget,
+
1242 .forget_multi = lo_forget_multi,
+
1243 .getattr = lo_getattr,
+
1244 .setattr = lo_setattr,
+
1245 .readlink = lo_readlink,
+
1246 .opendir = lo_opendir,
+
1247 .readdir = lo_readdir,
+
1248 .readdirplus = lo_readdirplus,
+
1249 .releasedir = lo_releasedir,
+
1250 .fsyncdir = lo_fsyncdir,
+
1251 .create = lo_create,
+
1252 .tmpfile = lo_tmpfile,
+
1253 .open = lo_open,
+
1254 .release = lo_release,
+
1255 .flush = lo_flush,
+
1256 .fsync = lo_fsync,
+
1257 .read = lo_read,
+
1258 .write_buf = lo_write_buf,
+
1259 .statfs = lo_statfs,
+
1260 .fallocate = lo_fallocate,
+
1261 .flock = lo_flock,
+
1262 .getxattr = lo_getxattr,
+
1263 .listxattr = lo_listxattr,
+
1264 .setxattr = lo_setxattr,
+
1265 .removexattr = lo_removexattr,
+
1266#ifdef HAVE_COPY_FILE_RANGE
+
1267 .copy_file_range = lo_copy_file_range,
+
1268#endif
+
1269 .lseek = lo_lseek,
+
1270};
+
1271
+
1272int main(int argc, char *argv[])
+
1273{
+
1274 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1275 struct fuse_session *se;
+
1276 struct fuse_cmdline_opts opts;
+
1277 struct fuse_loop_config *config;
+
1278 struct lo_data lo = { .debug = 0,
+
1279 .writeback = 0 };
+
1280 int ret = -1;
+
1281
+
1282 /* Don't mask creation mode, kernel already did that */
+
1283 umask(0);
+
1284
+
1285 pthread_mutex_init(&lo.mutex, NULL);
+
1286 lo.root.next = lo.root.prev = &lo.root;
+
1287 lo.root.fd = -1;
+
1288 lo.cache = CACHE_NORMAL;
+
1289
+
1290 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1291 return 1;
+
1292 if (opts.show_help) {
+
1293 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1296 passthrough_ll_help();
+
1297 ret = 0;
+
1298 goto err_out1;
+
1299 } else if (opts.show_version) {
+
1300 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1302 ret = 0;
+
1303 goto err_out1;
+
1304 }
+
1305
+
1306 if(opts.mountpoint == NULL) {
+
1307 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1308 printf(" %s --help\n", argv[0]);
+
1309 ret = 1;
+
1310 goto err_out1;
+
1311 }
+
1312
+
1313 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1314 return 1;
+
1315
+
1316 lo.debug = opts.debug;
+
1317 lo.root.refcount = 2;
+
1318 if (lo.source) {
+
1319 struct stat stat;
+
1320 int res;
+
1321
+
1322 res = lstat(lo.source, &stat);
+
1323 if (res == -1) {
+
1324 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1325 lo.source);
+
1326 exit(1);
+
1327 }
+
1328 if (!S_ISDIR(stat.st_mode)) {
+
1329 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1330 exit(1);
+
1331 }
+
1332
+
1333 } else {
+
1334 lo.source = strdup("/");
+
1335 if(!lo.source) {
+
1336 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1337 exit(1);
+
1338 }
+
1339 }
+
1340 if (!lo.timeout_set) {
+
1341 switch (lo.cache) {
+
1342 case CACHE_NEVER:
+
1343 lo.timeout = 0.0;
+
1344 break;
+
1345
+
1346 case CACHE_NORMAL:
+
1347 lo.timeout = 1.0;
+
1348 break;
+
1349
+
1350 case CACHE_ALWAYS:
+
1351 lo.timeout = 86400.0;
+
1352 break;
+
1353 }
+
1354 } else if (lo.timeout < 0) {
+
1355 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1356 lo.timeout);
+
1357 exit(1);
+
1358 }
+
1359
+
1360 lo.root.fd = open(lo.source, O_PATH);
+
1361 if (lo.root.fd == -1) {
+
1362 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1363 lo.source);
+
1364 exit(1);
+
1365 }
+
1366
+
1367 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1368 if (se == NULL)
+
1369 goto err_out1;
+
1370
+
1371 if (fuse_set_signal_handlers(se) != 0)
+
1372 goto err_out2;
+
1373
+
1374 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1375 goto err_out3;
+
1376
+
1377 fuse_daemonize(opts.foreground);
+
1378
+
1379 /* Block until ctrl+c or fusermount -u */
+
1380 if (opts.singlethread)
+
1381 ret = fuse_session_loop(se);
+
1382 else {
+
1383 config = fuse_loop_cfg_create();
+
1384 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1385 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1386 ret = fuse_session_loop_mt(se, config);
+
1387 fuse_loop_cfg_destroy(config);
+
1388 config = NULL;
+
1389 }
+
1390
+ +
1392err_out3:
+ +
1394err_out2:
+ +
1396err_out1:
+
1397 free(opts.mountpoint);
+
1398 fuse_opt_free_args(&args);
+
1399
+
1400 if (lo.root.fd >= 0)
+
1401 close(lo.root.fd);
+
1402
+
1403 free(lo.source);
+
1404 return ret ? 1 : 0;
+
1405}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_FLOCK_LOCKS
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2poll_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2poll_8c.html new file mode 100644 index 0000000..866baad --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2poll_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2poll_8c_source.html new file mode 100644 index 0000000..ba5fc54 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c.html new file mode 100644 index 0000000..e539f2d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c_source.html new file mode 100644 index 0000000..7fd5fa5 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2printcap_8c.html b/doc/html/fuse-3_817_81-rc0_2example_2printcap_8c.html new file mode 100644 index 0000000..4d16032 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2printcap_8c.html @@ -0,0 +1,239 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2example_2printcap_8c_source.html b/doc/html/fuse-3_817_81-rc0_2example_2printcap_8c_source.html new file mode 100644 index 0000000..ba5228e --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2example_2printcap_8c_source.html @@ -0,0 +1,230 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2cuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..b312489 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse_8h.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse_8h.html new file mode 100644 index 0000000..221502d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse_8h.html @@ -0,0 +1,780 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1415 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 87 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1386 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: all file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+
+ +

Definition at line 58 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 42 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4433 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5153 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4639 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4854 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4644 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4520 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4654 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4663 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4673 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4744 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4577 of file fuse.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5204 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 479 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4904 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4912 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5209 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse_8h_source.html new file mode 100644 index 0000000..7e91ebd --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse_8h_source.html @@ -0,0 +1,666 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
37struct fuse;
+
38
+
+ + +
52 FUSE_READDIR_PLUS = (1 << 0)
+
53};
+
+
54
+
+ + +
69 FUSE_FILL_DIR_PLUS = (1 << 1)
+
70};
+
+
71
+
87typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
88 const struct stat *stbuf, off_t off,
+
89 enum fuse_fill_dir_flags flags);
+
+ +
106 int32_t set_gid;
+
107 uint32_t gid;
+
108
+
113 int32_t set_uid;
+
114 uint32_t uid;
+
115
+
120 int32_t set_mode;
+
121 uint32_t umask;
+
122
+ +
128
+ +
138
+ +
144
+
148 int32_t intr;
+
149
+
155 int32_t intr_signal;
+
156
+
167 int32_t remember;
+
168
+
185 int32_t hard_remove;
+
186
+
198 int32_t use_ino;
+
199
+
207 int32_t readdir_ino;
+
208
+
226 int32_t direct_io;
+
227
+ +
246
+
253 int32_t auto_cache;
+
254
+
255 /*
+
256 * The timeout in seconds for which file attributes are cached
+
257 * for the purpose of checking if auto_cache should flush the
+
258 * file data on open.
+
259 */
+
260 int32_t ac_attr_timeout_set;
+
261 double ac_attr_timeout;
+
262
+
273 int32_t nullpath_ok;
+
274
+
279 int32_t show_help;
+
280 char *modules;
+
281 int32_t debug;
+
282
+
288 uint32_t fmask;
+
289 uint32_t dmask;
+
290
+ +
298
+ +
313
+
314
+
318 uint32_t flags;
+
319
+
323 uint64_t reserved[48];
+
324};
+
+
325
+
326
+
+ +
361 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
362
+
371 int (*readlink) (const char *, char *, size_t);
+
372
+
379 int (*mknod) (const char *, mode_t, dev_t);
+
380
+
387 int (*mkdir) (const char *, mode_t);
+
388
+
390 int (*unlink) (const char *);
+
391
+
393 int (*rmdir) (const char *);
+
394
+
396 int (*symlink) (const char *, const char *);
+
397
+
407 int (*rename) (const char *, const char *, unsigned int flags);
+
408
+
410 int (*link) (const char *, const char *);
+
411
+
417 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
418
+
427 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
428
+
437 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
438
+
486 int (*open) (const char *, struct fuse_file_info *);
+
487
+
497 int (*read) (const char *, char *, size_t, off_t,
+
498 struct fuse_file_info *);
+
499
+
509 int (*write) (const char *, const char *, size_t, off_t,
+
510 struct fuse_file_info *);
+
511
+
516 int (*statfs) (const char *, struct statvfs *);
+
517
+
546 int (*flush) (const char *, struct fuse_file_info *);
+
547
+
560 int (*release) (const char *, struct fuse_file_info *);
+
561
+
567 int (*fsync) (const char *, int, struct fuse_file_info *);
+
568
+
570 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
571
+
573 int (*getxattr) (const char *, const char *, char *, size_t);
+
574
+
576 int (*listxattr) (const char *, char *, size_t);
+
577
+
579 int (*removexattr) (const char *, const char *);
+
580
+
589 int (*opendir) (const char *, struct fuse_file_info *);
+
590
+
613 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
614 struct fuse_file_info *, enum fuse_readdir_flags);
+
615
+
621 int (*releasedir) (const char *, struct fuse_file_info *);
+
622
+
631 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
632
+
641 void *(*init) (struct fuse_conn_info *conn,
+
642 struct fuse_config *cfg);
+
643
+
649 void (*destroy) (void *private_data);
+
650
+
660 int (*access) (const char *, int);
+
661
+
672 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
673
+
704 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
705 struct flock *);
+
706
+
719 int (*utimens) (const char *, const struct timespec tv[2],
+
720 struct fuse_file_info *fi);
+
721
+
728 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
729
+
730#if FUSE_USE_VERSION < 35
+
731 int (*ioctl) (const char *, int cmd, void *arg,
+
732 struct fuse_file_info *, unsigned int flags, void *data);
+
733#else
+
750 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
751 struct fuse_file_info *, unsigned int flags, void *data);
+
752#endif
+
753
+
769 int (*poll) (const char *, struct fuse_file_info *,
+
770 struct fuse_pollhandle *ph, unsigned *reventsp);
+
771
+
781 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
782 struct fuse_file_info *);
+
783
+
798 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
799 size_t size, off_t off, struct fuse_file_info *);
+
818 int (*flock) (const char *, struct fuse_file_info *, int op);
+
819
+
828 int (*fallocate) (const char *, int, off_t, off_t,
+
829 struct fuse_file_info *);
+
830
+
843 ssize_t (*copy_file_range) (const char *path_in,
+
844 struct fuse_file_info *fi_in,
+
845 off_t offset_in, const char *path_out,
+
846 struct fuse_file_info *fi_out,
+
847 off_t offset_out, size_t size, int flags);
+
848
+
852 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
853};
+
+
854
+
+ +
862 struct fuse *fuse;
+
863
+
865 uid_t uid;
+
866
+
868 gid_t gid;
+
869
+
871 pid_t pid;
+
872
+ +
875
+
877 mode_t umask;
+
878};
+
+
879
+
885static inline int fuse_main_real(int argc, char *argv[],
+
886 const struct fuse_operations *op,
+
887 size_t op_size, void *user_data)
+
888{
+
889 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
890 .minor = FUSE_MINOR_VERSION,
+
891 .hotfix = FUSE_HOTFIX_VERSION,
+
892 .padding = 0 };
+
893
+
894 fuse_log(FUSE_LOG_ERR,
+
895 "%s is a libfuse internal function, please use fuse_main()\n",
+
896 __func__);
+
897
+
898 /* not declared globally, to restrict usage of this function */
+
899 int fuse_main_real_versioned(int argc, char *argv[],
+
900 const struct fuse_operations *op,
+
901 size_t op_size,
+
902 struct libfuse_version *version,
+
903 void *user_data);
+
904
+
905 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
906 user_data);
+
907}
+
908
+
963static inline int fuse_main_fn(int argc, char *argv[],
+
964 const struct fuse_operations *op,
+
965 void *user_data)
+
966{
+
967 struct libfuse_version version = {
+
968 .major = FUSE_MAJOR_VERSION,
+
969 .minor = FUSE_MINOR_VERSION,
+
970 .hotfix = FUSE_HOTFIX_VERSION,
+
971 .padding = 0
+
972 };
+
973
+
974 /* not declared globally, to restrict usage of this function */
+
975 int fuse_main_real_versioned(int argc, char *argv[],
+
976 const struct fuse_operations *op,
+
977 size_t op_size,
+
978 struct libfuse_version *version,
+
979 void *user_data);
+
980 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
981 user_data);
+
982}
+
983#define fuse_main(argc, argv, op, user_data) \
+
984 fuse_main_fn(argc, argv, op, user_data)
+
985
+
986/* ----------------------------------------------------------- *
+
987 * More detailed API *
+
988 * ----------------------------------------------------------- */
+
989
+
1001void fuse_lib_help(struct fuse_args *args);
+
1002
+
1030#if FUSE_USE_VERSION == 30
+
1031struct fuse *_fuse_new_30(struct fuse_args *args,
+
1032 const struct fuse_operations *op,
+
1033 size_t op_size,
+
1034 struct libfuse_version *version,
+
1035 void *user_data);
+
1036static inline struct fuse *
+
1037fuse_new_fn(struct fuse_args *args,
+
1038 const struct fuse_operations *op, size_t op_size,
+
1039 void *user_data)
+
1040{
+
1041 /* not declared globally, to restrict usage of this function */
+
1042 struct fuse *_fuse_new_30(struct fuse_args *args,
+
1043 const struct fuse_operations *op, size_t op_size,
+
1044 struct libfuse_version *version,
+
1045 void *user_data);
+
1046
+
1047 struct libfuse_version version = {
+
1048 .major = FUSE_MAJOR_VERSION,
+
1049 .minor = FUSE_MINOR_VERSION,
+
1050 .hotfix = FUSE_HOTFIX_VERSION,
+
1051 .padding = 0
+
1052 };
+
1053
+
1054 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1055}
+
1056#else /* FUSE_USE_VERSION */
+
1057static inline struct fuse *
+
1058fuse_new_fn(struct fuse_args *args,
+
1059 const struct fuse_operations *op, size_t op_size,
+
1060 void *user_data)
+
1061{
+
1062 struct libfuse_version version = {
+
1063 .major = FUSE_MAJOR_VERSION,
+
1064 .minor = FUSE_MINOR_VERSION,
+
1065 .hotfix = FUSE_HOTFIX_VERSION,
+
1066 .padding = 0
+
1067 };
+
1068
+
1069 /* not declared globally, to restrict usage of this function */
+
1070 struct fuse *_fuse_new_31(struct fuse_args *args,
+
1071 const struct fuse_operations *op,
+
1072 size_t op_size, struct libfuse_version *version,
+
1073 void *user_data);
+
1074 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1075}
+
1076#endif
+
1077#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1078
+
1087int fuse_mount(struct fuse *f, const char *mountpoint);
+
1088
+
1096void fuse_unmount(struct fuse *f);
+
1097
+
1106void fuse_destroy(struct fuse *f);
+
1107
+
1123int fuse_loop(struct fuse *f);
+
1124
+
1133void fuse_exit(struct fuse *f);
+
1134
+
1135#if FUSE_USE_VERSION < 32
+
1136int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1137#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1138#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1139int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1140#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1141#else
+
1173#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1174int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1175#else
+
1176#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1177#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1178#endif
+
1179
+
1180
+
1189struct fuse_context *fuse_get_context(void);
+
1190
+
1209int fuse_getgroups(int size, gid_t list[]);
+
1210
+
1216int fuse_interrupted(void);
+
1217
+
1229int fuse_invalidate_path(struct fuse *f, const char *path);
+
1230
+ +
1239
+
1246void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1247
+
1257int fuse_clean_cache(struct fuse *fuse);
+
1258
+
1259/*
+
1260 * Stacking API
+
1261 */
+
1262
+
1268struct fuse_fs;
+
1269
+
1270/*
+
1271 * These functions call the relevant filesystem operation, and return
+
1272 * the result.
+
1273 *
+
1274 * If the operation is not defined, they return -ENOSYS, with the
+
1275 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1276 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1277 */
+
1278
+
1279int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1280 struct fuse_file_info *fi);
+
1281int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1282 const char *newpath, unsigned int flags);
+
1283int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1284int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1285int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1286 const char *path);
+
1287int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1288int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1289 struct fuse_file_info *fi);
+
1290int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1291 struct fuse_file_info *fi);
+
1292int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1293 off_t off, struct fuse_file_info *fi);
+
1294int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1295 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1296 struct fuse_file_info *fi);
+
1297int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1298 size_t size, off_t off, struct fuse_file_info *fi);
+
1299int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1300 struct fuse_bufvec *buf, off_t off,
+
1301 struct fuse_file_info *fi);
+
1302int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1303 struct fuse_file_info *fi);
+
1304int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1305 struct fuse_file_info *fi);
+
1306int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1307int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1308 struct fuse_file_info *fi);
+
1309int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1310 fuse_fill_dir_t filler, off_t off,
+
1311 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1312int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1313 struct fuse_file_info *fi);
+
1314int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1315 struct fuse_file_info *fi);
+
1316int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1317 struct fuse_file_info *fi);
+
1318int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1319 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1320int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1321 struct fuse_file_info *fi, int op);
+
1322int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1323 struct fuse_file_info *fi);
+
1324int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1325 struct fuse_file_info *fi);
+
1326int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1327 struct fuse_file_info *fi);
+
1328int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1329 const struct timespec tv[2], struct fuse_file_info *fi);
+
1330int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1331int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1332 size_t len);
+
1333int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1334 dev_t rdev);
+
1335int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1336int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1337 const char *value, size_t size, int flags);
+
1338int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1339 char *value, size_t size);
+
1340int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1341 size_t size);
+
1342int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1343 const char *name);
+
1344int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1345 uint64_t *idx);
+
1346#if FUSE_USE_VERSION < 35
+
1347int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1348 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1349 void *data);
+
1350#else
+
1351int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1352 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1353 void *data);
+
1354#endif
+
1355int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1356 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1357 unsigned *reventsp);
+
1358int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1359 off_t offset, off_t length, struct fuse_file_info *fi);
+
1360ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1361 struct fuse_file_info *fi_in, off_t off_in,
+
1362 const char *path_out,
+
1363 struct fuse_file_info *fi_out, off_t off_out,
+
1364 size_t len, int flags);
+
1365off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1366 struct fuse_file_info *fi);
+
1367void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1368 struct fuse_config *cfg);
+
1369void fuse_fs_destroy(struct fuse_fs *fs);
+
1370
+
1371int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1372
+
1386struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1387 void *private_data);
+
1388
+
1403typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1404 struct fuse_fs *fs[]);
+
+
1415#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1416 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1417
+
1419struct fuse_session *fuse_get_session(struct fuse *f);
+
1420
+
1429int fuse_open_channel(const char *mountpoint, const char *options);
+
1430
+
1431#ifdef __cplusplus
+
1432}
+
1433#endif
+
1434
+
1435#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4904
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:479
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4912
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h.html new file mode 100644 index 0000000..e73aa5c --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h.html @@ -0,0 +1,723 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include <stdbool.h>
+#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + +

+Macros

#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + + + +

+Enumerations

enum  fuse_capability {
+  FUSE_CAP_ASYNC_READ = (1 << 0) +, FUSE_CAP_POSIX_LOCKS = (1 << 1) +, FUSE_CAP_ATOMIC_O_TRUNC = (1 << 3) +, FUSE_CAP_EXPORT_SUPPORT = (1 << 4) +,
+  FUSE_CAP_DONT_MASK = (1 << 6) +, FUSE_CAP_SPLICE_WRITE = (1 << 7) +, FUSE_CAP_SPLICE_MOVE = (1 << 8) +, FUSE_CAP_SPLICE_READ = (1 << 9) +,
+  FUSE_CAP_FLOCK_LOCKS = (1 << 10) +, FUSE_CAP_IOCTL_DIR = (1 << 11) +, FUSE_CAP_AUTO_INVAL_DATA = (1 << 12) +, FUSE_CAP_READDIRPLUS = (1 << 13) +,
+  FUSE_CAP_READDIRPLUS_AUTO = (1 << 14) +, FUSE_CAP_ASYNC_DIO = (1 << 15) +, FUSE_CAP_WRITEBACK_CACHE = (1 << 16) +, FUSE_CAP_NO_OPEN_SUPPORT = (1 << 17) +,
+  FUSE_CAP_PARALLEL_DIROPS = (1 << 18) +, FUSE_CAP_POSIX_ACL = (1 << 19) +, FUSE_CAP_HANDLE_KILLPRIV = (1 << 20) +, FUSE_CAP_HANDLE_KILLPRIV_V2 = (1 << 21) +,
+  FUSE_CAP_CACHE_SYMLINKS = (1 << 23) +, FUSE_CAP_NO_OPENDIR_SUPPORT = (1 << 24) +, FUSE_CAP_EXPLICIT_INVAL_DATA = (1 << 25) +, FUSE_CAP_EXPIRE_ONLY = (1 << 26) +,
+  FUSE_CAP_SETXATTR_EXT = (1 << 27) +, FUSE_CAP_DIRECT_IO_ALLOW_MMAP = (1 << 28) +, FUSE_CAP_PASSTHROUGH = (1 << 29) +, FUSE_CAP_NO_EXPORT_SUPPORT = (1 << 30) +,
+  FUSE_CAP_CURRENT_MAX +
+ }
 
enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 682 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 537 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 848 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 817 of file fuse_common.h.

+ +
+
+ +

◆ fuse_capability

+ +
+
+ + + + +
enum fuse_capability
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Enumerator
FUSE_CAP_ASYNC_READ 

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_POSIX_LOCKS 

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+
FUSE_CAP_ATOMIC_O_TRUNC 

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_EXPORT_SUPPORT 

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+
FUSE_CAP_DONT_MASK 

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+
FUSE_CAP_SPLICE_WRITE 

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+
FUSE_CAP_SPLICE_MOVE 

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+
FUSE_CAP_SPLICE_READ 

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+
FUSE_CAP_FLOCK_LOCKS 

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+
FUSE_CAP_IOCTL_DIR 

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_AUTO_INVAL_DATA 

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_READDIRPLUS 

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+
FUSE_CAP_READDIRPLUS_AUTO 

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+
FUSE_CAP_ASYNC_DIO 

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_WRITEBACK_CACHE 

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+
FUSE_CAP_NO_OPEN_SUPPORT 

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+
FUSE_CAP_PARALLEL_DIROPS 

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+
FUSE_CAP_POSIX_ACL 

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+
FUSE_CAP_HANDLE_KILLPRIV 

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+
FUSE_CAP_HANDLE_KILLPRIV_V2 

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+
FUSE_CAP_CACHE_SYMLINKS 

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+
FUSE_CAP_NO_OPENDIR_SUPPORT 

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+
FUSE_CAP_EXPLICIT_INVAL_DATA 

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+
FUSE_CAP_EXPIRE_ONLY 

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+
FUSE_CAP_SETXATTR_EXT 

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+
FUSE_CAP_DIRECT_IO_ALLOW_MMAP 

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+
FUSE_CAP_PASSTHROUGH 

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+
FUSE_CAP_NO_EXPORT_SUPPORT 

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+
FUSE_CAP_CURRENT_MAX 

Current maximum capability value.

+
+ +

Definition at line 177 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 416 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 463 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5218 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1906 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 180 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 158 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 138 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5213 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h_source.html new file mode 100644 index 0000000..b689cba --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__common_8h_source.html @@ -0,0 +1,554 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file COPYING.LIB.
+
6*/
+
7
+
10#include <stdbool.h>
+
11#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
12#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
13#endif
+
14
+
15#ifndef FUSE_COMMON_H_
+
16#define FUSE_COMMON_H_
+
17
+
18#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
19#include "fuse_config.h"
+
20#endif
+
21
+
22#include "libfuse_config.h"
+
23
+
24#include "fuse_opt.h"
+
25#include "fuse_log.h"
+
26#include <stdint.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#if (defined(__cplusplus) && __cplusplus >= 201103L) || \
+
34 (!defined(__cplusplus) && defined(__STDC_VERSION__) && \
+
35 __STDC_VERSION__ >= 201112L)
+
36#define fuse_static_assert(condition, message) static_assert(condition, message)
+
37#else
+
38#define fuse_static_assert(condition, message)
+
39#endif
+
40
+
41#ifdef __cplusplus
+
42extern "C" {
+
43#endif
+
44
+
+ +
60 int32_t flags;
+
61
+
68 uint32_t writepage : 1;
+
69
+
71 uint32_t direct_io : 1;
+
72
+
77 uint32_t keep_cache : 1;
+
78
+
82 uint32_t flush : 1;
+
83
+
86 uint32_t nonseekable : 1;
+
87
+
88 /* Indicates that flock locks for this file should be
+
89 released. If set, lock_owner shall contain a valid value.
+
90 May only be set in ->release(). */
+
91 uint32_t flock_release : 1;
+
92
+
97 uint32_t cache_readdir : 1;
+
98
+
101 uint32_t noflush : 1;
+
102
+ +
106
+
108 uint32_t padding : 23;
+
109 uint32_t padding2 : 32;
+
110 uint32_t padding3 : 32;
+
111
+
115 uint64_t fh;
+
116
+
118 uint64_t lock_owner;
+
119
+
122 uint32_t poll_events;
+
123
+
127 int32_t backing_id;
+
128
+
130 uint64_t compat_flags;
+
131
+
132 uint64_t reserved[2];
+
133};
+
+
134fuse_static_assert(sizeof(struct fuse_file_info) == 64,
+
135 "fuse_file_info size mismatch");
+
136
+
147#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
148struct fuse_loop_config_v1; /* forward declaration */
+
+ +
150#else
+
151struct fuse_loop_config_v1 {
+
152#endif
+ +
158
+
169 unsigned int max_idle_threads;
+
170};
+
+
171
+
172
+
173/**************************************************************************
+
174 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
175 **************************************************************************/
+
176
+
+ + +
189
+ +
197
+ +
206
+ +
218
+ +
226
+ +
234
+ +
242
+ +
251
+ +
264
+ +
271
+ +
293
+ +
301
+ +
329
+ +
340
+ +
349
+ +
364
+ +
372
+ +
391
+ +
400
+ +
417
+ +
430
+ +
445
+ +
468
+ +
484
+ +
491
+ +
500
+ +
512
+ +
520
+ + +
+
526
+
537#define FUSE_IOCTL_COMPAT (1 << 0)
+
538#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
539#define FUSE_IOCTL_RETRY (1 << 2)
+
540#define FUSE_IOCTL_DIR (1 << 4)
+
541
+
542#define FUSE_IOCTL_MAX_IOV 256
+
543
+
+ +
559 uint32_t proto_major;
+
560
+
564 uint32_t proto_minor;
+
565
+
569 uint32_t max_write;
+
570
+
583 uint32_t max_read;
+
584
+ +
589
+
595 uint32_t capable;
+
596
+
607 uint32_t want;
+
608
+ +
638
+ +
648
+
664 uint32_t time_gran;
+
665
+
682#define FUSE_BACKING_STACKED_UNDER (0)
+
683#define FUSE_BACKING_STACKED_OVER (1)
+
684 uint32_t max_backing_stack_depth;
+
685
+
694 uint32_t no_interrupt : 1;
+
695
+
696 /* reserved bits for future use */
+
697 uint32_t padding : 31;
+
698
+
703 uint64_t capable_ext;
+
704
+
713 uint64_t want_ext;
+
714
+
718 uint32_t reserved[16];
+
719};
+
+
720fuse_static_assert(sizeof(struct fuse_conn_info) == 128,
+
721 "Size of struct fuse_conn_info must be 128 bytes");
+
722
+
723struct fuse_session;
+
724struct fuse_pollhandle;
+
725struct fuse_conn_info_opts;
+
726
+
769struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
770
+
778void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
779 struct fuse_conn_info *conn);
+
780
+
787int fuse_daemonize(int foreground);
+
788
+
794int fuse_version(void);
+
795
+
801const char *fuse_pkgversion(void);
+
802
+
808void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
809
+
810/* ----------------------------------------------------------- *
+
811 * Data buffer *
+
812 * ----------------------------------------------------------- */
+
813
+
+ +
824 FUSE_BUF_IS_FD = (1 << 1),
+
825
+ +
834
+
842 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
844
+
+ + +
859
+ +
867
+ +
876
+ + +
+
886
+
+
893struct fuse_buf {
+
897 size_t size;
+
898
+ +
903
+
909 void *mem;
+
910
+
916 int fd;
+
917
+
923 off_t pos;
+
924
+
931 size_t mem_size;
+
932};
+
+
933
+
+ +
946 size_t count;
+
947
+
951 size_t idx;
+
952
+
956 size_t off;
+
957
+
961 struct fuse_buf buf[1];
+
962};
+
+
963
+
+ +
969{
+
970 uint32_t major;
+
971 uint32_t minor;
+
972 uint32_t hotfix;
+
973 uint32_t padding;
+
974};
+
+
975
+
976/* Initialize bufvec with a single buffer of given size */
+
977#define FUSE_BUFVEC_INIT(size__) \
+
978 ((struct fuse_bufvec) { \
+
979 /* .count= */ 1, \
+
980 /* .idx = */ 0, \
+
981 /* .off = */ 0, \
+
982 /* .buf = */ { /* [0] = */ { \
+
983 /* .size = */ (size__), \
+
984 /* .flags = */ (enum fuse_buf_flags) 0, \
+
985 /* .mem = */ NULL, \
+
986 /* .fd = */ -1, \
+
987 /* .pos = */ 0, \
+
988 /* .mem_size = */ 0, \
+
989 } } \
+
990 } )
+
991
+
998size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
999
+
1008ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1009 enum fuse_buf_copy_flags flags);
+
1010
+
1011/* ----------------------------------------------------------- *
+
1012 * Signal handling *
+
1013 * ----------------------------------------------------------- */
+
1014
+
1030int fuse_set_signal_handlers(struct fuse_session *se);
+
1031
+
1047int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1048
+
1060void fuse_remove_signal_handlers(struct fuse_session *se);
+
1061
+
1067#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1073struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1074
+
1078void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1079
+
1083void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1084 unsigned int value);
+
1085
+
1089void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1090 unsigned int value);
+
1091
+
1095void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1096 unsigned int value);
+
1097
+
1104void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1105 struct fuse_loop_config_v1 *v1_conf);
+
1106#endif
+
1107
+
1108
+
1109static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
1110 uint64_t flag)
+
1111{
+
1112 if (conn->capable_ext & flag) {
+
1113 conn->want_ext |= flag;
+
1114 return true;
+
1115 }
+
1116 return false;
+
1117}
+
1118
+
1119static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
1120 uint64_t flag)
+
1121{
+
1122 conn->want_ext &= ~flag;
+
1123}
+
1124
+
1125static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
1126 uint64_t flag)
+
1127{
+
1128 return conn->capable_ext & flag ? true : false;
+
1129}
+
1130
+
1131/* ----------------------------------------------------------- *
+
1132 * Compatibility stuff *
+
1133 * ----------------------------------------------------------- */
+
1134
+
1135#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1136# error only API version 30 or greater is supported
+
1137#endif
+
1138
+
1139#ifdef __cplusplus
+
1140}
+
1141#endif
+
1142
+
1143
+
1144/*
+
1145 * This interface uses 64 bit off_t.
+
1146 *
+
1147 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1148 */
+
1149
+
1150#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1151_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1152#else
+
1153struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1154 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1155#endif
+
1156
+
1157#endif /* FUSE_COMMON_H_ */
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
fuse_capability
+
@ FUSE_CAP_CURRENT_MAX
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:416
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:463
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5213
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
fuse_buf_flags
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+ + + + +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
off_t pos
+
void * mem
+
size_t size
+ + + + + +
uint32_t time_gran
+
uint32_t proto_major
+ +
uint32_t congestion_threshold
+
uint32_t proto_minor
+
uint32_t max_write
+
uint64_t capable_ext
+
uint32_t max_readahead
+
uint32_t no_interrupt
+
uint32_t max_read
+
uint32_t max_background
+
uint32_t capable
+
uint64_t want_ext
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t padding
+
uint32_t noflush
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
unsigned int max_idle_threads
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__kernel_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..718aa4c --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__kernel_8h_source.html @@ -0,0 +1,1097 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 */
+
221
+
222#ifndef _LINUX_FUSE_H
+
223#define _LINUX_FUSE_H
+
224
+
225#ifdef __KERNEL__
+
226#include <linux/types.h>
+
227#else
+
228#include <stdint.h>
+
229#endif
+
230
+
231/*
+
232 * Version negotiation:
+
233 *
+
234 * Both the kernel and userspace send the version they support in the
+
235 * INIT request and reply respectively.
+
236 *
+
237 * If the major versions match then both shall use the smallest
+
238 * of the two minor versions for communication.
+
239 *
+
240 * If the kernel supports a larger major version, then userspace shall
+
241 * reply with the major version it supports, ignore the rest of the
+
242 * INIT message and expect a new INIT message from the kernel with a
+
243 * matching major version.
+
244 *
+
245 * If the library supports a larger major version, then it shall fall
+
246 * back to the major protocol version sent by the kernel for
+
247 * communication and reply with that major version (and an arbitrary
+
248 * supported minor version).
+
249 */
+
250
+
252#define FUSE_KERNEL_VERSION 7
+
253
+
255#define FUSE_KERNEL_MINOR_VERSION 40
+
256
+
258#define FUSE_ROOT_ID 1
+
259
+
260/* Make sure all structures are padded to 64bit boundary, so 32bit
+
261 userspace works under 64bit kernels */
+
262
+
263struct fuse_attr {
+
264 uint64_t ino;
+
265 uint64_t size;
+
266 uint64_t blocks;
+
267 uint64_t atime;
+
268 uint64_t mtime;
+
269 uint64_t ctime;
+
270 uint32_t atimensec;
+
271 uint32_t mtimensec;
+
272 uint32_t ctimensec;
+
273 uint32_t mode;
+
274 uint32_t nlink;
+
275 uint32_t uid;
+
276 uint32_t gid;
+
277 uint32_t rdev;
+
278 uint32_t blksize;
+
279 uint32_t flags;
+
280};
+
281
+
282/*
+
283 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
284 * Linux.
+
285 */
+
286struct fuse_sx_time {
+
287 int64_t tv_sec;
+
288 uint32_t tv_nsec;
+
289 int32_t __reserved;
+
290};
+
291
+
292struct fuse_statx {
+
293 uint32_t mask;
+
294 uint32_t blksize;
+
295 uint64_t attributes;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint16_t mode;
+
300 uint16_t __spare0[1];
+
301 uint64_t ino;
+
302 uint64_t size;
+
303 uint64_t blocks;
+
304 uint64_t attributes_mask;
+
305 struct fuse_sx_time atime;
+
306 struct fuse_sx_time btime;
+
307 struct fuse_sx_time ctime;
+
308 struct fuse_sx_time mtime;
+
309 uint32_t rdev_major;
+
310 uint32_t rdev_minor;
+
311 uint32_t dev_major;
+
312 uint32_t dev_minor;
+
313 uint64_t __spare2[14];
+
314};
+
315
+
316struct fuse_kstatfs {
+
317 uint64_t blocks;
+
318 uint64_t bfree;
+
319 uint64_t bavail;
+
320 uint64_t files;
+
321 uint64_t ffree;
+
322 uint32_t bsize;
+
323 uint32_t namelen;
+
324 uint32_t frsize;
+
325 uint32_t padding;
+
326 uint32_t spare[6];
+
327};
+
328
+
329struct fuse_file_lock {
+
330 uint64_t start;
+
331 uint64_t end;
+
332 uint32_t type;
+
333 uint32_t pid; /* tgid */
+
334};
+
335
+
339#define FATTR_MODE (1 << 0)
+
340#define FATTR_UID (1 << 1)
+
341#define FATTR_GID (1 << 2)
+
342#define FATTR_SIZE (1 << 3)
+
343#define FATTR_ATIME (1 << 4)
+
344#define FATTR_MTIME (1 << 5)
+
345#define FATTR_FH (1 << 6)
+
346#define FATTR_ATIME_NOW (1 << 7)
+
347#define FATTR_MTIME_NOW (1 << 8)
+
348#define FATTR_LOCKOWNER (1 << 9)
+
349#define FATTR_CTIME (1 << 10)
+
350#define FATTR_KILL_SUIDGID (1 << 11)
+
351
+
364#define FOPEN_DIRECT_IO (1 << 0)
+
365#define FOPEN_KEEP_CACHE (1 << 1)
+
366#define FOPEN_NONSEEKABLE (1 << 2)
+
367#define FOPEN_CACHE_DIR (1 << 3)
+
368#define FOPEN_STREAM (1 << 4)
+
369#define FOPEN_NOFLUSH (1 << 5)
+
370#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
371#define FOPEN_PASSTHROUGH (1 << 7)
+
372
+
425#define FUSE_ASYNC_READ (1 << 0)
+
426#define FUSE_POSIX_LOCKS (1 << 1)
+
427#define FUSE_FILE_OPS (1 << 2)
+
428#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
429#define FUSE_EXPORT_SUPPORT (1 << 4)
+
430#define FUSE_BIG_WRITES (1 << 5)
+
431#define FUSE_DONT_MASK (1 << 6)
+
432#define FUSE_SPLICE_WRITE (1 << 7)
+
433#define FUSE_SPLICE_MOVE (1 << 8)
+
434#define FUSE_SPLICE_READ (1 << 9)
+
435#define FUSE_FLOCK_LOCKS (1 << 10)
+
436#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
437#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
438#define FUSE_DO_READDIRPLUS (1 << 13)
+
439#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
440#define FUSE_ASYNC_DIO (1 << 15)
+
441#define FUSE_WRITEBACK_CACHE (1 << 16)
+
442#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
443#define FUSE_PARALLEL_DIROPS (1 << 18)
+
444#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
445#define FUSE_POSIX_ACL (1 << 20)
+
446#define FUSE_ABORT_ERROR (1 << 21)
+
447#define FUSE_MAX_PAGES (1 << 22)
+
448#define FUSE_CACHE_SYMLINKS (1 << 23)
+
449#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
450#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
451#define FUSE_MAP_ALIGNMENT (1 << 26)
+
452#define FUSE_SUBMOUNTS (1 << 27)
+
453#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
454#define FUSE_SETXATTR_EXT (1 << 29)
+
455#define FUSE_INIT_EXT (1 << 30)
+
456#define FUSE_INIT_RESERVED (1 << 31)
+
457/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
458#define FUSE_SECURITY_CTX (1ULL << 32)
+
459#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
460#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
461#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
462#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
463#define FUSE_PASSTHROUGH (1ULL << 37)
+
464#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
465#define FUSE_HAS_RESEND (1ULL << 39)
+
466
+
467/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
468#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
469
+
475#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
476
+
480#define FUSE_RELEASE_FLUSH (1 << 0)
+
481#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
482
+
486#define FUSE_GETATTR_FH (1 << 0)
+
487
+
491#define FUSE_LK_FLOCK (1 << 0)
+
492
+
500#define FUSE_WRITE_CACHE (1 << 0)
+
501#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
502#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
503
+
504/* Obsolete alias; this flag implies killing suid/sgid only. */
+
505#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
506
+
510#define FUSE_READ_LOCKOWNER (1 << 1)
+
511
+
524#define FUSE_IOCTL_COMPAT (1 << 0)
+
525#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
526#define FUSE_IOCTL_RETRY (1 << 2)
+
527#define FUSE_IOCTL_32BIT (1 << 3)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
530
+
531#define FUSE_IOCTL_MAX_IOV 256
+
532
+
538#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
539
+
545#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
546
+
553#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
554#define FUSE_ATTR_DAX (1 << 1)
+
555
+
560#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
561
+
566#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
567
+
572#define FUSE_EXPIRE_ONLY (1 << 0)
+
573
+
579enum fuse_ext_type {
+
580 /* Types 0..31 are reserved for fuse_secctx_header */
+
581 FUSE_MAX_NR_SECCTX = 31,
+
582 FUSE_EXT_GROUPS = 32,
+
583};
+
584
+
585enum fuse_opcode {
+
586 FUSE_LOOKUP = 1,
+
587 FUSE_FORGET = 2, /* no reply */
+
588 FUSE_GETATTR = 3,
+
589 FUSE_SETATTR = 4,
+
590 FUSE_READLINK = 5,
+
591 FUSE_SYMLINK = 6,
+
592 FUSE_MKNOD = 8,
+
593 FUSE_MKDIR = 9,
+
594 FUSE_UNLINK = 10,
+
595 FUSE_RMDIR = 11,
+
596 FUSE_RENAME = 12,
+
597 FUSE_LINK = 13,
+
598 FUSE_OPEN = 14,
+
599 FUSE_READ = 15,
+
600 FUSE_WRITE = 16,
+
601 FUSE_STATFS = 17,
+
602 FUSE_RELEASE = 18,
+
603 FUSE_FSYNC = 20,
+
604 FUSE_SETXATTR = 21,
+
605 FUSE_GETXATTR = 22,
+
606 FUSE_LISTXATTR = 23,
+
607 FUSE_REMOVEXATTR = 24,
+
608 FUSE_FLUSH = 25,
+
609 FUSE_INIT = 26,
+
610 FUSE_OPENDIR = 27,
+
611 FUSE_READDIR = 28,
+
612 FUSE_RELEASEDIR = 29,
+
613 FUSE_FSYNCDIR = 30,
+
614 FUSE_GETLK = 31,
+
615 FUSE_SETLK = 32,
+
616 FUSE_SETLKW = 33,
+
617 FUSE_ACCESS = 34,
+
618 FUSE_CREATE = 35,
+
619 FUSE_INTERRUPT = 36,
+
620 FUSE_BMAP = 37,
+
621 FUSE_DESTROY = 38,
+
622 FUSE_IOCTL = 39,
+
623 FUSE_POLL = 40,
+
624 FUSE_NOTIFY_REPLY = 41,
+
625 FUSE_BATCH_FORGET = 42,
+
626 FUSE_FALLOCATE = 43,
+
627 FUSE_READDIRPLUS = 44,
+
628 FUSE_RENAME2 = 45,
+
629 FUSE_LSEEK = 46,
+
630 FUSE_COPY_FILE_RANGE = 47,
+
631 FUSE_SETUPMAPPING = 48,
+
632 FUSE_REMOVEMAPPING = 49,
+
633 FUSE_SYNCFS = 50,
+
634 FUSE_TMPFILE = 51,
+
635 FUSE_STATX = 52,
+
636
+
637 /* CUSE specific operations */
+
638 CUSE_INIT = 4096,
+
639
+
640 /* Reserved opcodes: helpful to detect structure endian-ness */
+
641 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
642 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
643};
+
644
+
645enum fuse_notify_code {
+
646 FUSE_NOTIFY_POLL = 1,
+
647 FUSE_NOTIFY_INVAL_INODE = 2,
+
648 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
649 FUSE_NOTIFY_STORE = 4,
+
650 FUSE_NOTIFY_RETRIEVE = 5,
+
651 FUSE_NOTIFY_DELETE = 6,
+
652 FUSE_NOTIFY_RESEND = 7,
+
653 FUSE_NOTIFY_CODE_MAX,
+
654};
+
655
+
656/* The read buffer is required to be at least 8k, but may be much larger */
+
657#define FUSE_MIN_READ_BUFFER 8192
+
658
+
659#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
660
+
661struct fuse_entry_out {
+
662 uint64_t nodeid; /* Inode ID */
+
663 uint64_t generation; /* Inode generation: nodeid:gen must
+
664 be unique for the fs's lifetime */
+
665 uint64_t entry_valid; /* Cache timeout for the name */
+
666 uint64_t attr_valid; /* Cache timeout for the attributes */
+
667 uint32_t entry_valid_nsec;
+
668 uint32_t attr_valid_nsec;
+
669 struct fuse_attr attr;
+
670};
+
671
+
672struct fuse_forget_in {
+
673 uint64_t nlookup;
+
674};
+
675
+
676struct fuse_forget_one {
+
677 uint64_t nodeid;
+
678 uint64_t nlookup;
+
679};
+
680
+
681struct fuse_batch_forget_in {
+
682 uint32_t count;
+
683 uint32_t dummy;
+
684};
+
685
+
686struct fuse_getattr_in {
+
687 uint32_t getattr_flags;
+
688 uint32_t dummy;
+
689 uint64_t fh;
+
690};
+
691
+
692#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
693
+
694struct fuse_attr_out {
+
695 uint64_t attr_valid; /* Cache timeout for the attributes */
+
696 uint32_t attr_valid_nsec;
+
697 uint32_t dummy;
+
698 struct fuse_attr attr;
+
699};
+
700
+
701struct fuse_statx_in {
+
702 uint32_t getattr_flags;
+
703 uint32_t reserved;
+
704 uint64_t fh;
+
705 uint32_t sx_flags;
+
706 uint32_t sx_mask;
+
707};
+
708
+
709struct fuse_statx_out {
+
710 uint64_t attr_valid; /* Cache timeout for the attributes */
+
711 uint32_t attr_valid_nsec;
+
712 uint32_t flags;
+
713 uint64_t spare[2];
+
714 struct fuse_statx stat;
+
715};
+
716
+
717#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
718
+
719struct fuse_mknod_in {
+
720 uint32_t mode;
+
721 uint32_t rdev;
+
722 uint32_t umask;
+
723 uint32_t padding;
+
724};
+
725
+
726struct fuse_mkdir_in {
+
727 uint32_t mode;
+
728 uint32_t umask;
+
729};
+
730
+
731struct fuse_rename_in {
+
732 uint64_t newdir;
+
733};
+
734
+
735struct fuse_rename2_in {
+
736 uint64_t newdir;
+
737 uint32_t flags;
+
738 uint32_t padding;
+
739};
+
740
+
741struct fuse_link_in {
+
742 uint64_t oldnodeid;
+
743};
+
744
+
745struct fuse_setattr_in {
+
746 uint32_t valid;
+
747 uint32_t padding;
+
748 uint64_t fh;
+
749 uint64_t size;
+
750 uint64_t lock_owner;
+
751 uint64_t atime;
+
752 uint64_t mtime;
+
753 uint64_t ctime;
+
754 uint32_t atimensec;
+
755 uint32_t mtimensec;
+
756 uint32_t ctimensec;
+
757 uint32_t mode;
+
758 uint32_t unused4;
+
759 uint32_t uid;
+
760 uint32_t gid;
+
761 uint32_t unused5;
+
762};
+
763
+
764struct fuse_open_in {
+
765 uint32_t flags;
+
766 uint32_t open_flags; /* FUSE_OPEN_... */
+
767};
+
768
+
769struct fuse_create_in {
+
770 uint32_t flags;
+
771 uint32_t mode;
+
772 uint32_t umask;
+
773 uint32_t open_flags; /* FUSE_OPEN_... */
+
774};
+
775
+
776struct fuse_open_out {
+
777 uint64_t fh;
+
778 uint32_t open_flags;
+
779 int32_t backing_id;
+
780};
+
781
+
782struct fuse_release_in {
+
783 uint64_t fh;
+
784 uint32_t flags;
+
785 uint32_t release_flags;
+
786 uint64_t lock_owner;
+
787};
+
788
+
789struct fuse_flush_in {
+
790 uint64_t fh;
+
791 uint32_t unused;
+
792 uint32_t padding;
+
793 uint64_t lock_owner;
+
794};
+
795
+
796struct fuse_read_in {
+
797 uint64_t fh;
+
798 uint64_t offset;
+
799 uint32_t size;
+
800 uint32_t read_flags;
+
801 uint64_t lock_owner;
+
802 uint32_t flags;
+
803 uint32_t padding;
+
804};
+
805
+
806#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
807
+
808struct fuse_write_in {
+
809 uint64_t fh;
+
810 uint64_t offset;
+
811 uint32_t size;
+
812 uint32_t write_flags;
+
813 uint64_t lock_owner;
+
814 uint32_t flags;
+
815 uint32_t padding;
+
816};
+
817
+
818struct fuse_write_out {
+
819 uint32_t size;
+
820 uint32_t padding;
+
821};
+
822
+
823#define FUSE_COMPAT_STATFS_SIZE 48
+
824
+
825struct fuse_statfs_out {
+
826 struct fuse_kstatfs st;
+
827};
+
828
+
829struct fuse_fsync_in {
+
830 uint64_t fh;
+
831 uint32_t fsync_flags;
+
832 uint32_t padding;
+
833};
+
834
+
835#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
836
+
837struct fuse_setxattr_in {
+
838 uint32_t size;
+
839 uint32_t flags;
+
840 uint32_t setxattr_flags;
+
841 uint32_t padding;
+
842};
+
843
+
844struct fuse_getxattr_in {
+
845 uint32_t size;
+
846 uint32_t padding;
+
847};
+
848
+
849struct fuse_getxattr_out {
+
850 uint32_t size;
+
851 uint32_t padding;
+
852};
+
853
+
854struct fuse_lk_in {
+
855 uint64_t fh;
+
856 uint64_t owner;
+
857 struct fuse_file_lock lk;
+
858 uint32_t lk_flags;
+
859 uint32_t padding;
+
860};
+
861
+
862struct fuse_lk_out {
+
863 struct fuse_file_lock lk;
+
864};
+
865
+
866struct fuse_access_in {
+
867 uint32_t mask;
+
868 uint32_t padding;
+
869};
+
870
+
871struct fuse_init_in {
+
872 uint32_t major;
+
873 uint32_t minor;
+
874 uint32_t max_readahead;
+
875 uint32_t flags;
+
876 uint32_t flags2;
+
877 uint32_t unused[11];
+
878};
+
879
+
880#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
881#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
882
+
883struct fuse_init_out {
+
884 uint32_t major;
+
885 uint32_t minor;
+
886 uint32_t max_readahead;
+
887 uint32_t flags;
+
888 uint16_t max_background;
+
889 uint16_t congestion_threshold;
+
890 uint32_t max_write;
+
891 uint32_t time_gran;
+
892 uint16_t max_pages;
+
893 uint16_t map_alignment;
+
894 uint32_t flags2;
+
895 uint32_t max_stack_depth;
+
896 uint32_t unused[6];
+
897};
+
898
+
899#define CUSE_INIT_INFO_MAX 4096
+
900
+
901struct cuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t unused;
+
905 uint32_t flags;
+
906};
+
907
+
908struct cuse_init_out {
+
909 uint32_t major;
+
910 uint32_t minor;
+
911 uint32_t unused;
+
912 uint32_t flags;
+
913 uint32_t max_read;
+
914 uint32_t max_write;
+
915 uint32_t dev_major; /* chardev major */
+
916 uint32_t dev_minor; /* chardev minor */
+
917 uint32_t spare[10];
+
918};
+
919
+
920struct fuse_interrupt_in {
+
921 uint64_t unique;
+
922};
+
923
+
924struct fuse_bmap_in {
+
925 uint64_t block;
+
926 uint32_t blocksize;
+
927 uint32_t padding;
+
928};
+
929
+
930struct fuse_bmap_out {
+
931 uint64_t block;
+
932};
+
933
+
934struct fuse_ioctl_in {
+
935 uint64_t fh;
+
936 uint32_t flags;
+
937 uint32_t cmd;
+
938 uint64_t arg;
+
939 uint32_t in_size;
+
940 uint32_t out_size;
+
941};
+
942
+
943struct fuse_ioctl_iovec {
+
944 uint64_t base;
+
945 uint64_t len;
+
946};
+
947
+
948struct fuse_ioctl_out {
+
949 int32_t result;
+
950 uint32_t flags;
+
951 uint32_t in_iovs;
+
952 uint32_t out_iovs;
+
953};
+
954
+
955struct fuse_poll_in {
+
956 uint64_t fh;
+
957 uint64_t kh;
+
958 uint32_t flags;
+
959 uint32_t events;
+
960};
+
961
+
962struct fuse_poll_out {
+
963 uint32_t revents;
+
964 uint32_t padding;
+
965};
+
966
+
967struct fuse_notify_poll_wakeup_out {
+
968 uint64_t kh;
+
969};
+
970
+
971struct fuse_fallocate_in {
+
972 uint64_t fh;
+
973 uint64_t offset;
+
974 uint64_t length;
+
975 uint32_t mode;
+
976 uint32_t padding;
+
977};
+
978
+
985#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
986
+
987struct fuse_in_header {
+
988 uint32_t len;
+
989 uint32_t opcode;
+
990 uint64_t unique;
+
991 uint64_t nodeid;
+
992 uint32_t uid;
+
993 uint32_t gid;
+
994 uint32_t pid;
+
995 uint16_t total_extlen; /* length of extensions in 8byte units */
+
996 uint16_t padding;
+
997};
+
998
+
999struct fuse_out_header {
+
1000 uint32_t len;
+
1001 int32_t error;
+
1002 uint64_t unique;
+
1003};
+
1004
+
1005struct fuse_dirent {
+
1006 uint64_t ino;
+
1007 uint64_t off;
+
1008 uint32_t namelen;
+
1009 uint32_t type;
+
1010 char name[];
+
1011};
+
1012
+
1013/* Align variable length records to 64bit boundary */
+
1014#define FUSE_REC_ALIGN(x) \
+
1015 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1016
+
1017#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1018#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1019#define FUSE_DIRENT_SIZE(d) \
+
1020 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1021
+
1022struct fuse_direntplus {
+
1023 struct fuse_entry_out entry_out;
+
1024 struct fuse_dirent dirent;
+
1025};
+
1026
+
1027#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1028 offsetof(struct fuse_direntplus, dirent.name)
+
1029#define FUSE_DIRENTPLUS_SIZE(d) \
+
1030 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1031
+
1032struct fuse_notify_inval_inode_out {
+
1033 uint64_t ino;
+
1034 int64_t off;
+
1035 int64_t len;
+
1036};
+
1037
+
1038struct fuse_notify_inval_entry_out {
+
1039 uint64_t parent;
+
1040 uint32_t namelen;
+
1041 uint32_t flags;
+
1042};
+
1043
+
1044struct fuse_notify_delete_out {
+
1045 uint64_t parent;
+
1046 uint64_t child;
+
1047 uint32_t namelen;
+
1048 uint32_t padding;
+
1049};
+
1050
+
1051struct fuse_notify_store_out {
+
1052 uint64_t nodeid;
+
1053 uint64_t offset;
+
1054 uint32_t size;
+
1055 uint32_t padding;
+
1056};
+
1057
+
1058struct fuse_notify_retrieve_out {
+
1059 uint64_t notify_unique;
+
1060 uint64_t nodeid;
+
1061 uint64_t offset;
+
1062 uint32_t size;
+
1063 uint32_t padding;
+
1064};
+
1065
+
1066/* Matches the size of fuse_write_in */
+
1067struct fuse_notify_retrieve_in {
+
1068 uint64_t dummy1;
+
1069 uint64_t offset;
+
1070 uint32_t size;
+
1071 uint32_t dummy2;
+
1072 uint64_t dummy3;
+
1073 uint64_t dummy4;
+
1074};
+
1075
+
1076struct fuse_backing_map {
+
1077 int32_t fd;
+
1078 uint32_t flags;
+
1079 uint64_t padding;
+
1080};
+
1081
+
1082/* Device ioctls: */
+
1083#define FUSE_DEV_IOC_MAGIC 229
+
1084#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1085#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1086 struct fuse_backing_map)
+
1087#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1088
+
1089struct fuse_lseek_in {
+
1090 uint64_t fh;
+
1091 uint64_t offset;
+
1092 uint32_t whence;
+
1093 uint32_t padding;
+
1094};
+
1095
+
1096struct fuse_lseek_out {
+
1097 uint64_t offset;
+
1098};
+
1099
+
1100struct fuse_copy_file_range_in {
+
1101 uint64_t fh_in;
+
1102 uint64_t off_in;
+
1103 uint64_t nodeid_out;
+
1104 uint64_t fh_out;
+
1105 uint64_t off_out;
+
1106 uint64_t len;
+
1107 uint64_t flags;
+
1108};
+
1109
+
1110#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1111#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1112struct fuse_setupmapping_in {
+
1113 /* An already open handle */
+
1114 uint64_t fh;
+
1115 /* Offset into the file to start the mapping */
+
1116 uint64_t foffset;
+
1117 /* Length of mapping required */
+
1118 uint64_t len;
+
1119 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1120 uint64_t flags;
+
1121 /* Offset in Memory Window */
+
1122 uint64_t moffset;
+
1123};
+
1124
+
1125struct fuse_removemapping_in {
+
1126 /* number of fuse_removemapping_one follows */
+
1127 uint32_t count;
+
1128};
+
1129
+
1130struct fuse_removemapping_one {
+
1131 /* Offset into the dax window start the unmapping */
+
1132 uint64_t moffset;
+
1133 /* Length of mapping required */
+
1134 uint64_t len;
+
1135};
+
1136
+
1137#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1138 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1139
+
1140struct fuse_syncfs_in {
+
1141 uint64_t padding;
+
1142};
+
1143
+
1144/*
+
1145 * For each security context, send fuse_secctx with size of security context
+
1146 * fuse_secctx will be followed by security context name and this in turn
+
1147 * will be followed by actual context label.
+
1148 * fuse_secctx, name, context
+
1149 */
+
1150struct fuse_secctx {
+
1151 uint32_t size;
+
1152 uint32_t padding;
+
1153};
+
1154
+
1155/*
+
1156 * Contains the information about how many fuse_secctx structures are being
+
1157 * sent and what's the total size of all security contexts (including
+
1158 * size of fuse_secctx_header).
+
1159 *
+
1160 */
+
1161struct fuse_secctx_header {
+
1162 uint32_t size;
+
1163 uint32_t nr_secctx;
+
1164};
+
1165
+
+ +
1175 uint32_t size;
+
1176 uint32_t type;
+
1177};
+
+
1178
+
+ +
1185 uint32_t nr_groups;
+
1186 uint32_t groups[];
+
1187};
+
+
1188
+
1189#endif /* _LINUX_FUSE_H */
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h.html new file mode 100644 index 0000000..9ded66b --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...)
 
void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 77 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h_source.html new file mode 100644 index 0000000..88fe80e --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__log_8h_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...);
+
77
+
83void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
84
+
88void fuse_log_close_syslog(void);
+
89
+
90#ifdef __cplusplus
+
91}
+
92#endif
+
93
+
94#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..302b248 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h.html @@ -0,0 +1,2362 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1946 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 148 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 289 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 379 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 2957 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2519 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2506 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2500 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2432 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2415 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2625 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2545 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2950 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 483 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 463 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 976 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 527 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 447 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 915 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 431 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 334 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1074 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1095 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1004 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 268 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 959 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1129 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 339 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 508 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1119 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 478 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 937 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 517 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 949 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2675 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3553 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2680 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2693 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2670 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 2967 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3474 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3419 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2787 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3234 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3479 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..3f02bca --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,683 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
57struct fuse_session;
+
58
+
+ + +
69
+
80 uint64_t generation;
+
81
+
89 struct stat attr;
+
90
+ +
96
+ +
102};
+
+
103
+
+
112struct fuse_ctx {
+
114 uid_t uid;
+
115
+
117 gid_t gid;
+
118
+
120 pid_t pid;
+
121
+
123 mode_t umask;
+
124};
+
+
125
+
126struct fuse_forget_data {
+
127 fuse_ino_t ino;
+
128 uint64_t nlookup;
+
129};
+
130
+
131struct fuse_custom_io {
+
132 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
133 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
134 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
135 off_t *offout, size_t len,
+
136 unsigned int flags, void *userdata);
+
137 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 int (*clone_fd)(int master_fd);
+
141};
+
142
+
+ +
149 FUSE_LL_INVALIDATE = 0,
+
150 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
151};
+
+
152
+
153/* 'to_set' flags in setattr */
+
154#define FUSE_SET_ATTR_MODE (1 << 0)
+
155#define FUSE_SET_ATTR_UID (1 << 1)
+
156#define FUSE_SET_ATTR_GID (1 << 2)
+
157#define FUSE_SET_ATTR_SIZE (1 << 3)
+
158#define FUSE_SET_ATTR_ATIME (1 << 4)
+
159#define FUSE_SET_ATTR_MTIME (1 << 5)
+
160#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
161#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
162#define FUSE_SET_ATTR_FORCE (1 << 9)
+
163#define FUSE_SET_ATTR_CTIME (1 << 10)
+
164#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
165#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
166#define FUSE_SET_ATTR_FILE (1 << 13)
+
167#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
168#define FUSE_SET_ATTR_OPEN (1 << 15)
+
169#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
170#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
171
+
172/* ----------------------------------------------------------- *
+
173 * Request methods and replies *
+
174 * ----------------------------------------------------------- */
+
175
+
+ +
223 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
224
+
236 void (*destroy) (void *userdata);
+
237
+
249 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
250
+
287 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
288
+
308 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
309 struct fuse_file_info *fi);
+
310
+
345 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
346 int to_set, struct fuse_file_info *fi);
+
347
+
358 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
359
+
376 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
377 mode_t mode, dev_t rdev);
+
378
+
391 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
392 mode_t mode);
+
393
+
409 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
410
+
426 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
427
+
440 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
441 const char *name);
+
442
+
472 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
473 fuse_ino_t newparent, const char *newname,
+
474 unsigned int flags);
+
475
+
488 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
489 const char *newname);
+
490
+
554 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
555 struct fuse_file_info *fi);
+
556
+
582 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
583 struct fuse_file_info *fi);
+
584
+
611 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
612 size_t size, off_t off, struct fuse_file_info *fi);
+
613
+
652 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
653 struct fuse_file_info *fi);
+
654
+
680 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
681 struct fuse_file_info *fi);
+
682
+
702 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
703 struct fuse_file_info *fi);
+
704
+
734 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
735 struct fuse_file_info *fi);
+
736
+
780 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
781 struct fuse_file_info *fi);
+
782
+ +
800 struct fuse_file_info *fi);
+
801
+
824 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
825 struct fuse_file_info *fi);
+
826
+
837 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
838
+
850 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
851 const char *value, size_t size, int flags);
+
852
+
881 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
882 size_t size);
+
883
+
912 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
913
+
929 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
930
+
951 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
952
+
980 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
981 mode_t mode, struct fuse_file_info *fi);
+
982
+
995 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
996 struct fuse_file_info *fi, struct flock *lock);
+
997
+
1020 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1021 struct fuse_file_info *fi,
+
1022 struct flock *lock, int sleep);
+
1023
+
1044 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1045 uint64_t idx);
+
1046
+
1047#if FUSE_USE_VERSION < 35
+
1048 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1049 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1050 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1051#else
+
1080 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1081 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1082 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1083#endif
+
1084
+
1117 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1118 struct fuse_pollhandle *ph);
+
1119
+ +
1148 struct fuse_bufvec *bufv, off_t off,
+
1149 struct fuse_file_info *fi);
+
1150
+
1163 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1164 off_t offset, struct fuse_bufvec *bufv);
+
1165
+
1177 void (*forget_multi) (fuse_req_t req, size_t count,
+
1178 struct fuse_forget_data *forgets);
+
1179
+
1195 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1196 struct fuse_file_info *fi, int op);
+
1197
+
1218 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1219 off_t offset, off_t length, struct fuse_file_info *fi);
+
1220
+
1246 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1247 struct fuse_file_info *fi);
+
1248
+ +
1280 off_t off_in, struct fuse_file_info *fi_in,
+
1281 fuse_ino_t ino_out, off_t off_out,
+
1282 struct fuse_file_info *fi_out, size_t len,
+
1283 int flags);
+
1284
+
1303 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1304 struct fuse_file_info *fi);
+
1305
+
1306
+
1325 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1326 mode_t mode, struct fuse_file_info *fi);
+
1327
+
1328};
+
+
1329
+
1351int fuse_reply_err(fuse_req_t req, int err);
+
1352
+
1363void fuse_reply_none(fuse_req_t req);
+
1364
+
1378int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1379
+
1398int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1399 const struct fuse_file_info *fi);
+
1400
+
1412int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1413 double attr_timeout);
+
1414
+
1425int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1426
+
1437int fuse_passthrough_open(fuse_req_t req, int fd);
+
1438int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1439
+
1454int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1455
+
1466int fuse_reply_write(fuse_req_t req, size_t count);
+
1467
+
1479int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1480
+
1524int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1526
+
1538int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1539
+
1550int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1551
+
1562int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1563
+
1574int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1575
+
1586int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1587
+
1588/* ----------------------------------------------------------- *
+
1589 * Filling a buffer in readdir *
+
1590 * ----------------------------------------------------------- */
+
1591
+
1619size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1620 const char *name, const struct stat *stbuf,
+
1621 off_t off);
+
1622
+
1636size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1637 const char *name,
+
1638 const struct fuse_entry_param *e, off_t off);
+
1639
+ +
1656 const struct iovec *in_iov, size_t in_count,
+
1657 const struct iovec *out_iov, size_t out_count);
+
1658
+
1670int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1671
+
1683int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1684 int count);
+
1685
+
1692int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1693
+
1704int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1705
+
1706/* ----------------------------------------------------------- *
+
1707 * Notification *
+
1708 * ----------------------------------------------------------- */
+
1709
+
1717int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1718
+
1742int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1743 off_t off, off_t len);
+
1744
+
1769int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1770 const char *name, size_t namelen);
+
1771
+
1800int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1801 const char *name, size_t namelen);
+
1802
+
1831int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1832 fuse_ino_t parent, fuse_ino_t child,
+
1833 const char *name, size_t namelen);
+
1834
+
1860int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1861 off_t offset, struct fuse_bufvec *bufv,
+ +
1892int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1893 size_t size, off_t offset, void *cookie);
+
1894
+
1895
+
1896/* ----------------------------------------------------------- *
+
1897 * Utility functions *
+
1898 * ----------------------------------------------------------- */
+
1899
+
1906void *fuse_req_userdata(fuse_req_t req);
+
1907
+
1917const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1918
+
1938int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1939
+
1946typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1947
+ +
1960 void *data);
+
1961
+ +
1969
+
1970
+
1971/* ----------------------------------------------------------- *
+
1972 * Inquiry functions *
+
1973 * ----------------------------------------------------------- */
+
1974
+
1978void fuse_lowlevel_version(void);
+
1979
+
1985void fuse_lowlevel_help(void);
+
1986
+
1990void fuse_cmdline_help(void);
+
1991
+
1992/* ----------------------------------------------------------- *
+
1993 * Filesystem setup & teardown *
+
1994 * ----------------------------------------------------------- */
+
1995
+
+ +
2002 int singlethread;
+
2003 int foreground;
+
2004 int debug;
+
2005 int nodefault_subtype;
+
2006 char *mountpoint;
+
2007 int show_version;
+
2008 int show_help;
+
2009 int clone_fd;
+
2010 unsigned int max_idle_threads; /* discouraged, due to thread
+
2011 * destruct overhead */
+
2012
+
2013 /* Added in libfuse-3.12 */
+
2014 unsigned int max_threads;
+
2015};
+
+
2016
+
2035#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2036int fuse_parse_cmdline(struct fuse_args *args,
+
2037 struct fuse_cmdline_opts *opts);
+
2038#else
+
2039#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2040int fuse_parse_cmdline_30(struct fuse_args *args,
+
2041 struct fuse_cmdline_opts *opts);
+
2042#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2043#else
+
2044int fuse_parse_cmdline_312(struct fuse_args *args,
+
2045 struct fuse_cmdline_opts *opts);
+
2046#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2047#endif
+
2048#endif
+
2049
+
2078static inline struct fuse_session *
+
2079fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2080 size_t op_size, void *userdata)
+
2081{
+
2082 struct libfuse_version version = {
+
2083 .major = FUSE_MAJOR_VERSION,
+
2084 .minor = FUSE_MINOR_VERSION,
+
2085 .hotfix = FUSE_HOTFIX_VERSION,
+
2086 .padding = 0
+
2087 };
+
2088
+
2089 /* not declared globally, to restrict usage of this function */
+
2090 struct fuse_session *fuse_session_new_versioned(
+
2091 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2092 size_t op_size, struct libfuse_version *version,
+
2093 void *userdata);
+
2094
+
2095 return fuse_session_new_versioned(args, op, op_size, &version,
+
2096 userdata);
+
2097}
+
2098#define fuse_session_new(args, op, op_size, userdata) \
+
2099 fuse_session_new_fn(args, op, op_size, userdata)
+
2100
+
2101/*
+
2102 * This should mostly not be called directly, but instead the
+
2103 * fuse_session_custom_io() should be used.
+
2104 */
+
2105int fuse_session_custom_io_317(struct fuse_session *se,
+
2106 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2107
+
2135#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2136static inline int fuse_session_custom_io(struct fuse_session *se,
+
2137 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2138{
+
2139 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2140}
+
2141#else
+
2142static inline int fuse_session_custom_io(struct fuse_session *se,
+
2143 const struct fuse_custom_io *io, int fd)
+
2144{
+
2145 return fuse_session_custom_io_317(se, io,
+
2146 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2147}
+
2148#endif
+
2149
+
2158int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2159
+
2182int fuse_session_loop(struct fuse_session *se);
+
2183
+
2184#if FUSE_USE_VERSION < 32
+
2185 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2186 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2187#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2188 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2189 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2190#else
+
2191 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2203 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2204 #else
+
2205 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2206 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2207 #endif
+
2208#endif
+
2209
+
2222void fuse_session_exit(struct fuse_session *se);
+
2223
+
2229void fuse_session_reset(struct fuse_session *se);
+
2230
+
2237int fuse_session_exited(struct fuse_session *se);
+
2238
+
2263void fuse_session_unmount(struct fuse_session *se);
+
2264
+
2270void fuse_session_destroy(struct fuse_session *se);
+
2271
+
2272/* ----------------------------------------------------------- *
+
2273 * Custom event loop support *
+
2274 * ----------------------------------------------------------- */
+
2275
+
2290int fuse_session_fd(struct fuse_session *se);
+
2291
+
2300void fuse_session_process_buf(struct fuse_session *se,
+
2301 const struct fuse_buf *buf);
+
2302
+
2314int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2315
+
2316#ifdef __cplusplus
+
2317}
+
2318#endif
+
2319
+
2320#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ +
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__mount__compat_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..05998a1 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h.html new file mode 100644 index 0000000..8b51de5 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h_source.html b/doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..6815fd0 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2include_2fuse__opt_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+ +
118};
+
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2buffer_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2buffer_8c_source.html new file mode 100644 index 0000000..d6dea62 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2buffer_8c_source.html @@ -0,0 +1,410 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2compat_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2compat_8c_source.html new file mode 100644 index 0000000..b1bfeae --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2cuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..a3f7c04 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,451 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
196{
+
197 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
198 struct cuse_init_out outarg;
+
199 struct fuse_session *se = req->se;
+
200 struct cuse_data *cd = se->cuse_data;
+
201 size_t bufsize = se->bufsize;
+
202 struct cuse_lowlevel_ops *clop = req_clop(req);
+
203
+
204 (void) nodeid;
+
205 if (se->debug) {
+
206 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
207 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
208 }
+
209 se->conn.proto_major = arg->major;
+
210 se->conn.proto_minor = arg->minor;
+
211
+
212 /* XXX This is not right.*/
+
213 se->conn.capable_ext = 0;
+
214 se->conn.want_ext = 0;
+
215
+
216 if (arg->major < 7) {
+
217 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
218 arg->major, arg->minor);
+
219 fuse_reply_err(req, EPROTO);
+
220 return;
+
221 }
+
222
+
223 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
224 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
225 bufsize);
+
226 bufsize = FUSE_MIN_READ_BUFFER;
+
227 }
+
228
+
229 bufsize -= 4096;
+
230 if (bufsize < se->conn.max_write)
+
231 se->conn.max_write = bufsize;
+
232
+
233 se->got_init = 1;
+
234 if (se->op.init)
+
235 se->op.init(se->userdata, &se->conn);
+
236
+
237 memset(&outarg, 0, sizeof(outarg));
+
238 outarg.major = FUSE_KERNEL_VERSION;
+
239 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
240 outarg.flags = cd->flags;
+
241 outarg.max_read = cd->max_read;
+
242 outarg.max_write = se->conn.max_write;
+
243 outarg.dev_major = cd->dev_major;
+
244 outarg.dev_minor = cd->dev_minor;
+
245
+
246 if (se->debug) {
+
247 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
248 outarg.major, outarg.minor);
+
249 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
250 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
251 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
252 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
253 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
255 cd->dev_info);
+
256 }
+
257
+
258 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
259
+
260 if (clop->init_done)
+
261 clop->init_done(se->userdata);
+
262
+
263 fuse_free_req(req);
+
264}
+
265
+
266struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
267 const struct cuse_info *ci,
+
268 const struct cuse_lowlevel_ops *clop,
+
269 int *multithreaded, void *userdata)
+
270{
+
271 const char *devname = "/dev/cuse";
+
272 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
275 };
+
276 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
277 struct fuse_session *se;
+
278 struct fuse_cmdline_opts opts;
+
279 int fd;
+
280 int res;
+
281
+
282 if (fuse_parse_cmdline(&args, &opts) == -1)
+
283 return NULL;
+
284 *multithreaded = !opts.singlethread;
+
285
+
286 /* Remove subtype= option */
+
287 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
288 if (res == -1)
+
289 goto out1;
+
290
+
291 /*
+
292 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
293 * would ensue.
+
294 */
+
295 do {
+
296 fd = open("/dev/null", O_RDWR);
+
297 if (fd > 2)
+
298 close(fd);
+
299 } while (fd >= 0 && fd <= 2);
+
300
+
301 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
302 if (se == NULL)
+
303 goto out1;
+
304
+
305 fd = open(devname, O_RDWR);
+
306 if (fd == -1) {
+
307 if (errno == ENODEV || errno == ENOENT)
+
308 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
309 else
+
310 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
311 devname, strerror(errno));
+
312 goto err_se;
+
313 }
+
314 se->fd = fd;
+
315
+
316 res = fuse_set_signal_handlers(se);
+
317 if (res == -1)
+
318 goto err_se;
+
319
+
320 res = fuse_daemonize(opts.foreground);
+
321 if (res == -1)
+
322 goto err_sig;
+
323
+
324 fuse_opt_free_args(&args);
+
325 return se;
+
326
+
327err_sig:
+ +
329err_se:
+ +
331out1:
+
332 free(opts.mountpoint);
+
333 fuse_opt_free_args(&args);
+
334 return NULL;
+
335}
+
336
+
337void cuse_lowlevel_teardown(struct fuse_session *se)
+
338{
+ + +
341}
+
342
+
343int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
344 const struct cuse_lowlevel_ops *clop, void *userdata)
+
345{
+
346 struct fuse_session *se;
+
347 int multithreaded;
+
348 int res;
+
349
+
350 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
351 userdata);
+
352 if (se == NULL)
+
353 return 1;
+
354
+
355 if (multithreaded) {
+
356 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
357 res = fuse_session_loop_mt(se, config);
+
358 fuse_loop_cfg_destroy(config);
+
359 }
+
360 else
+
361 res = fuse_session_loop(se);
+
362
+
363 cuse_lowlevel_teardown(se);
+
364 if (res == -1)
+
365 return 1;
+
366
+
367 return 0;
+
368}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse_8c_source.html new file mode 100644 index 0000000..c84e122 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse_8c_source.html @@ -0,0 +1,5436 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "fuse_kernel.h"
+
20
+
21#include <stdio.h>
+
22#include <string.h>
+
23#include <stdlib.h>
+
24#include <stddef.h>
+
25#include <stdbool.h>
+
26#include <unistd.h>
+
27#include <time.h>
+
28#include <fcntl.h>
+
29#include <limits.h>
+
30#include <errno.h>
+
31#include <signal.h>
+
32#include <dlfcn.h>
+
33#include <assert.h>
+
34#include <poll.h>
+
35#include <sys/param.h>
+
36#include <sys/uio.h>
+
37#include <sys/time.h>
+
38#include <sys/mman.h>
+
39#include <sys/file.h>
+
40
+
41#define FUSE_NODE_SLAB 1
+
42
+
43#ifndef MAP_ANONYMOUS
+
44#undef FUSE_NODE_SLAB
+
45#endif
+
46
+
47#ifndef RENAME_EXCHANGE
+
48#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
49#endif
+
50
+
51#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
52
+
53#define FUSE_UNKNOWN_INO 0xffffffff
+
54#define OFFSET_MAX 0x7fffffffffffffffLL
+
55
+
56#define NODE_TABLE_MIN_SIZE 8192
+
57
+
58struct fuse_fs {
+
59 struct fuse_operations op;
+
60 void *user_data;
+
61 int debug;
+
62};
+
63
+
64struct fusemod_so {
+
65 void *handle;
+
66 int ctr;
+
67};
+
68
+
69struct lock_queue_element {
+
70 struct lock_queue_element *next;
+
71 pthread_cond_t cond;
+
72 fuse_ino_t nodeid1;
+
73 const char *name1;
+
74 char **path1;
+
75 struct node **wnode1;
+
76 fuse_ino_t nodeid2;
+
77 const char *name2;
+
78 char **path2;
+
79 struct node **wnode2;
+
80 int err;
+
81 bool done : 1;
+
82};
+
83
+
84struct node_table {
+
85 struct node **array;
+
86 size_t use;
+
87 size_t size;
+
88 size_t split;
+
89};
+
90
+
91#define container_of(ptr, type, member) ({ \
+
92 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
93 (type *)( (char *)__mptr - offsetof(type,member) );})
+
94
+
95#define list_entry(ptr, type, member) \
+
96 container_of(ptr, type, member)
+
97
+
98struct list_head {
+
99 struct list_head *next;
+
100 struct list_head *prev;
+
101};
+
102
+
103struct node_slab {
+
104 struct list_head list; /* must be the first member */
+
105 struct list_head freelist;
+
106 int used;
+
107};
+
108
+
109struct fuse {
+
110 struct fuse_session *se;
+
111 struct node_table name_table;
+
112 struct node_table id_table;
+
113 struct list_head lru_table;
+
114 fuse_ino_t ctr;
+
115 unsigned int generation;
+
116 unsigned int hidectr;
+
117 pthread_mutex_t lock;
+
118 struct fuse_config conf;
+
119 int intr_installed;
+
120 struct fuse_fs *fs;
+
121 struct lock_queue_element *lockq;
+
122 int pagesize;
+
123 struct list_head partial_slabs;
+
124 struct list_head full_slabs;
+
125 pthread_t prune_thread;
+
126};
+
127
+
128struct lock {
+
129 int type;
+
130 off_t start;
+
131 off_t end;
+
132 pid_t pid;
+
133 uint64_t owner;
+
134 struct lock *next;
+
135};
+
136
+
137struct node {
+
138 struct node *name_next;
+
139 struct node *id_next;
+
140 fuse_ino_t nodeid;
+
141 unsigned int generation;
+
142 int refctr;
+
143 struct node *parent;
+
144 char *name;
+
145 uint64_t nlookup;
+
146 int open_count;
+
147 struct timespec stat_updated;
+
148 struct timespec mtime;
+
149 off_t size;
+
150 struct lock *locks;
+
151 unsigned int is_hidden : 1;
+
152 unsigned int cache_valid : 1;
+
153 int treelock;
+
154 char inline_name[32];
+
155};
+
156
+
157#define TREELOCK_WRITE -1
+
158#define TREELOCK_WAIT_OFFSET INT_MIN
+
159
+
160struct node_lru {
+
161 struct node node;
+
162 struct list_head lru;
+
163 struct timespec forget_time;
+
164};
+
165
+
166struct fuse_direntry {
+
167 struct stat stat;
+
168 enum fuse_fill_dir_flags flags;
+
169 char *name;
+
170 struct fuse_direntry *next;
+
171};
+
172
+
173struct fuse_dh {
+
174 pthread_mutex_t lock;
+
175 struct fuse *fuse;
+
176 fuse_req_t req;
+
177 char *contents;
+
178 struct fuse_direntry *first;
+
179 struct fuse_direntry **last;
+
180 unsigned len;
+
181 unsigned size;
+
182 unsigned needlen;
+
183 int filled;
+
184 uint64_t fh;
+
185 int error;
+
186 fuse_ino_t nodeid;
+
187};
+
188
+
189struct fuse_context_i {
+
190 struct fuse_context ctx;
+
191 fuse_req_t req;
+
192};
+
193
+
194/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
195extern fuse_module_factory_t fuse_module_subdir_factory;
+
196#ifdef HAVE_ICONV
+
197extern fuse_module_factory_t fuse_module_iconv_factory;
+
198#endif
+
199
+
200static pthread_key_t fuse_context_key;
+
201static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
202static int fuse_context_ref;
+
203static struct fuse_module *fuse_modules = NULL;
+
204
+
205static int fuse_register_module(const char *name,
+
206 fuse_module_factory_t factory,
+
207 struct fusemod_so *so)
+
208{
+
209 struct fuse_module *mod;
+
210
+
211 mod = calloc(1, sizeof(struct fuse_module));
+
212 if (!mod) {
+
213 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
214 return -1;
+
215 }
+
216 mod->name = strdup(name);
+
217 if (!mod->name) {
+
218 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
219 free(mod);
+
220 return -1;
+
221 }
+
222 mod->factory = factory;
+
223 mod->ctr = 0;
+
224 mod->so = so;
+
225 if (mod->so)
+
226 mod->so->ctr++;
+
227 mod->next = fuse_modules;
+
228 fuse_modules = mod;
+
229
+
230 return 0;
+
231}
+
232
+
233static void fuse_unregister_module(struct fuse_module *m)
+
234{
+
235 struct fuse_module **mp;
+
236 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
237 if (*mp == m) {
+
238 *mp = (*mp)->next;
+
239 break;
+
240 }
+
241 }
+
242 free(m->name);
+
243 free(m);
+
244}
+
245
+
246static int fuse_load_so_module(const char *module)
+
247{
+
248 int ret = -1;
+
249 char *tmp;
+
250 struct fusemod_so *so;
+
251 fuse_module_factory_t *factory;
+
252
+
253 tmp = malloc(strlen(module) + 64);
+
254 if (!tmp) {
+
255 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
256 return -1;
+
257 }
+
258 sprintf(tmp, "libfusemod_%s.so", module);
+
259 so = calloc(1, sizeof(struct fusemod_so));
+
260 if (!so) {
+
261 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
262 goto out;
+
263 }
+
264
+
265 so->handle = dlopen(tmp, RTLD_NOW);
+
266 if (so->handle == NULL) {
+
267 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
268 tmp, dlerror());
+
269 goto out_free_so;
+
270 }
+
271
+
272 sprintf(tmp, "fuse_module_%s_factory", module);
+
273 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
274 if (factory == NULL) {
+
275 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
276 tmp, dlerror());
+
277 goto out_dlclose;
+
278 }
+
279 ret = fuse_register_module(module, *factory, so);
+
280 if (ret)
+
281 goto out_dlclose;
+
282
+
283out:
+
284 free(tmp);
+
285 return ret;
+
286
+
287out_dlclose:
+
288 dlclose(so->handle);
+
289out_free_so:
+
290 free(so);
+
291 goto out;
+
292}
+
293
+
294static struct fuse_module *fuse_find_module(const char *module)
+
295{
+
296 struct fuse_module *m;
+
297 for (m = fuse_modules; m; m = m->next) {
+
298 if (strcmp(module, m->name) == 0) {
+
299 m->ctr++;
+
300 break;
+
301 }
+
302 }
+
303 return m;
+
304}
+
305
+
306static struct fuse_module *fuse_get_module(const char *module)
+
307{
+
308 struct fuse_module *m;
+
309
+
310 pthread_mutex_lock(&fuse_context_lock);
+
311 m = fuse_find_module(module);
+
312 if (!m) {
+
313 int err = fuse_load_so_module(module);
+
314 if (!err)
+
315 m = fuse_find_module(module);
+
316 }
+
317 pthread_mutex_unlock(&fuse_context_lock);
+
318 return m;
+
319}
+
320
+
321static void fuse_put_module(struct fuse_module *m)
+
322{
+
323 pthread_mutex_lock(&fuse_context_lock);
+
324 if (m->so)
+
325 assert(m->ctr > 0);
+
326 /* Builtin modules may already have m->ctr == 0 */
+
327 if (m->ctr > 0)
+
328 m->ctr--;
+
329 if (!m->ctr && m->so) {
+
330 struct fusemod_so *so = m->so;
+
331 assert(so->ctr > 0);
+
332 so->ctr--;
+
333 if (!so->ctr) {
+
334 struct fuse_module **mp;
+
335 for (mp = &fuse_modules; *mp;) {
+
336 if ((*mp)->so == so)
+
337 fuse_unregister_module(*mp);
+
338 else
+
339 mp = &(*mp)->next;
+
340 }
+
341 dlclose(so->handle);
+
342 free(so);
+
343 }
+
344 } else if (!m->ctr) {
+
345 fuse_unregister_module(m);
+
346 }
+
347 pthread_mutex_unlock(&fuse_context_lock);
+
348}
+
349
+
350static void init_list_head(struct list_head *list)
+
351{
+
352 list->next = list;
+
353 list->prev = list;
+
354}
+
355
+
356static int list_empty(const struct list_head *head)
+
357{
+
358 return head->next == head;
+
359}
+
360
+
361static void list_add(struct list_head *new, struct list_head *prev,
+
362 struct list_head *next)
+
363{
+
364 next->prev = new;
+
365 new->next = next;
+
366 new->prev = prev;
+
367 prev->next = new;
+
368}
+
369
+
370static inline void list_add_head(struct list_head *new, struct list_head *head)
+
371{
+
372 list_add(new, head, head->next);
+
373}
+
374
+
375static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
376{
+
377 list_add(new, head->prev, head);
+
378}
+
379
+
380static inline void list_del(struct list_head *entry)
+
381{
+
382 struct list_head *prev = entry->prev;
+
383 struct list_head *next = entry->next;
+
384
+
385 next->prev = prev;
+
386 prev->next = next;
+
387}
+
388
+
389static inline int lru_enabled(struct fuse *f)
+
390{
+
391 return f->conf.remember > 0;
+
392}
+
393
+
394static struct node_lru *node_lru(struct node *node)
+
395{
+
396 return (struct node_lru *) node;
+
397}
+
398
+
399static size_t get_node_size(struct fuse *f)
+
400{
+
401 if (lru_enabled(f))
+
402 return sizeof(struct node_lru);
+
403 else
+
404 return sizeof(struct node);
+
405}
+
406
+
407#ifdef FUSE_NODE_SLAB
+
408static struct node_slab *list_to_slab(struct list_head *head)
+
409{
+
410 return (struct node_slab *) head;
+
411}
+
412
+
413static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
414{
+
415 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
416}
+
417
+
418static int alloc_slab(struct fuse *f)
+
419{
+
420 void *mem;
+
421 struct node_slab *slab;
+
422 char *start;
+
423 size_t num;
+
424 size_t i;
+
425 size_t node_size = get_node_size(f);
+
426
+
427 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
428 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
429
+
430 if (mem == MAP_FAILED)
+
431 return -1;
+
432
+
433 slab = mem;
+
434 init_list_head(&slab->freelist);
+
435 slab->used = 0;
+
436 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
437
+
438 start = (char *) mem + f->pagesize - num * node_size;
+
439 for (i = 0; i < num; i++) {
+
440 struct list_head *n;
+
441
+
442 n = (struct list_head *) (start + i * node_size);
+
443 list_add_tail(n, &slab->freelist);
+
444 }
+
445 list_add_tail(&slab->list, &f->partial_slabs);
+
446
+
447 return 0;
+
448}
+
449
+
450static struct node *alloc_node(struct fuse *f)
+
451{
+
452 struct node_slab *slab;
+
453 struct list_head *node;
+
454
+
455 if (list_empty(&f->partial_slabs)) {
+
456 int res = alloc_slab(f);
+
457 if (res != 0)
+
458 return NULL;
+
459 }
+
460 slab = list_to_slab(f->partial_slabs.next);
+
461 slab->used++;
+
462 node = slab->freelist.next;
+
463 list_del(node);
+
464 if (list_empty(&slab->freelist)) {
+
465 list_del(&slab->list);
+
466 list_add_tail(&slab->list, &f->full_slabs);
+
467 }
+
468 memset(node, 0, sizeof(struct node));
+
469
+
470 return (struct node *) node;
+
471}
+
472
+
473static void free_slab(struct fuse *f, struct node_slab *slab)
+
474{
+
475 int res;
+
476
+
477 list_del(&slab->list);
+
478 res = munmap(slab, f->pagesize);
+
479 if (res == -1)
+
480 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
481 slab);
+
482}
+
483
+
484static void free_node_mem(struct fuse *f, struct node *node)
+
485{
+
486 struct node_slab *slab = node_to_slab(f, node);
+
487 struct list_head *n = (struct list_head *) node;
+
488
+
489 slab->used--;
+
490 if (slab->used) {
+
491 if (list_empty(&slab->freelist)) {
+
492 list_del(&slab->list);
+
493 list_add_tail(&slab->list, &f->partial_slabs);
+
494 }
+
495 list_add_head(n, &slab->freelist);
+
496 } else {
+
497 free_slab(f, slab);
+
498 }
+
499}
+
500#else
+
501static struct node *alloc_node(struct fuse *f)
+
502{
+
503 return (struct node *) calloc(1, get_node_size(f));
+
504}
+
505
+
506static void free_node_mem(struct fuse *f, struct node *node)
+
507{
+
508 (void) f;
+
509 free(node);
+
510}
+
511#endif
+
512
+
513static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
514{
+
515 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
516 uint64_t oldhash = hash % (f->id_table.size / 2);
+
517
+
518 if (oldhash >= f->id_table.split)
+
519 return oldhash;
+
520 else
+
521 return hash;
+
522}
+
523
+
524static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
525{
+
526 size_t hash = id_hash(f, nodeid);
+
527 struct node *node;
+
528
+
529 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
530 if (node->nodeid == nodeid)
+
531 return node;
+
532
+
533 return NULL;
+
534}
+
535
+
536static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
537{
+
538 struct node *node = get_node_nocheck(f, nodeid);
+
539 if (!node) {
+
540 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
541 (unsigned long long) nodeid);
+
542 abort();
+
543 }
+
544 return node;
+
545}
+
546
+
547static void curr_time(struct timespec *now);
+
548static double diff_timespec(const struct timespec *t1,
+
549 const struct timespec *t2);
+
550
+
551static void remove_node_lru(struct node *node)
+
552{
+
553 struct node_lru *lnode = node_lru(node);
+
554 list_del(&lnode->lru);
+
555 init_list_head(&lnode->lru);
+
556}
+
557
+
558static void set_forget_time(struct fuse *f, struct node *node)
+
559{
+
560 struct node_lru *lnode = node_lru(node);
+
561
+
562 list_del(&lnode->lru);
+
563 list_add_tail(&lnode->lru, &f->lru_table);
+
564 curr_time(&lnode->forget_time);
+
565}
+
566
+
567static void free_node(struct fuse *f, struct node *node)
+
568{
+
569 if (node->name != node->inline_name)
+
570 free(node->name);
+
571 free_node_mem(f, node);
+
572}
+
573
+
574static void node_table_reduce(struct node_table *t)
+
575{
+
576 size_t newsize = t->size / 2;
+
577 void *newarray;
+
578
+
579 if (newsize < NODE_TABLE_MIN_SIZE)
+
580 return;
+
581
+
582 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
583 if (newarray != NULL)
+
584 t->array = newarray;
+
585
+
586 t->size = newsize;
+
587 t->split = t->size / 2;
+
588}
+
589
+
590static void remerge_id(struct fuse *f)
+
591{
+
592 struct node_table *t = &f->id_table;
+
593 int iter;
+
594
+
595 if (t->split == 0)
+
596 node_table_reduce(t);
+
597
+
598 for (iter = 8; t->split > 0 && iter; iter--) {
+
599 struct node **upper;
+
600
+
601 t->split--;
+
602 upper = &t->array[t->split + t->size / 2];
+
603 if (*upper) {
+
604 struct node **nodep;
+
605
+
606 for (nodep = &t->array[t->split]; *nodep;
+
607 nodep = &(*nodep)->id_next);
+
608
+
609 *nodep = *upper;
+
610 *upper = NULL;
+
611 break;
+
612 }
+
613 }
+
614}
+
615
+
616static void unhash_id(struct fuse *f, struct node *node)
+
617{
+
618 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
619
+
620 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
621 if (*nodep == node) {
+
622 *nodep = node->id_next;
+
623 f->id_table.use--;
+
624
+
625 if(f->id_table.use < f->id_table.size / 4)
+
626 remerge_id(f);
+
627 return;
+
628 }
+
629}
+
630
+
631static int node_table_resize(struct node_table *t)
+
632{
+
633 size_t newsize = t->size * 2;
+
634 void *newarray;
+
635
+
636 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
637 if (newarray == NULL)
+
638 return -1;
+
639
+
640 t->array = newarray;
+
641 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
642 t->size = newsize;
+
643 t->split = 0;
+
644
+
645 return 0;
+
646}
+
647
+
648static void rehash_id(struct fuse *f)
+
649{
+
650 struct node_table *t = &f->id_table;
+
651 struct node **nodep;
+
652 struct node **next;
+
653 size_t hash;
+
654
+
655 if (t->split == t->size / 2)
+
656 return;
+
657
+
658 hash = t->split;
+
659 t->split++;
+
660 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
661 struct node *node = *nodep;
+
662 size_t newhash = id_hash(f, node->nodeid);
+
663
+
664 if (newhash != hash) {
+
665 next = nodep;
+
666 *nodep = node->id_next;
+
667 node->id_next = t->array[newhash];
+
668 t->array[newhash] = node;
+
669 } else {
+
670 next = &node->id_next;
+
671 }
+
672 }
+
673 if (t->split == t->size / 2)
+
674 node_table_resize(t);
+
675}
+
676
+
677static void hash_id(struct fuse *f, struct node *node)
+
678{
+
679 size_t hash = id_hash(f, node->nodeid);
+
680 node->id_next = f->id_table.array[hash];
+
681 f->id_table.array[hash] = node;
+
682 f->id_table.use++;
+
683
+
684 if (f->id_table.use >= f->id_table.size / 2)
+
685 rehash_id(f);
+
686}
+
687
+
688static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
689 const char *name)
+
690{
+
691 uint64_t hash = parent;
+
692 uint64_t oldhash;
+
693
+
694 for (; *name; name++)
+
695 hash = hash * 31 + (unsigned char) *name;
+
696
+
697 hash %= f->name_table.size;
+
698 oldhash = hash % (f->name_table.size / 2);
+
699 if (oldhash >= f->name_table.split)
+
700 return oldhash;
+
701 else
+
702 return hash;
+
703}
+
704
+
705static void unref_node(struct fuse *f, struct node *node);
+
706
+
707static void remerge_name(struct fuse *f)
+
708{
+
709 struct node_table *t = &f->name_table;
+
710 int iter;
+
711
+
712 if (t->split == 0)
+
713 node_table_reduce(t);
+
714
+
715 for (iter = 8; t->split > 0 && iter; iter--) {
+
716 struct node **upper;
+
717
+
718 t->split--;
+
719 upper = &t->array[t->split + t->size / 2];
+
720 if (*upper) {
+
721 struct node **nodep;
+
722
+
723 for (nodep = &t->array[t->split]; *nodep;
+
724 nodep = &(*nodep)->name_next);
+
725
+
726 *nodep = *upper;
+
727 *upper = NULL;
+
728 break;
+
729 }
+
730 }
+
731}
+
732
+
733static void unhash_name(struct fuse *f, struct node *node)
+
734{
+
735 if (node->name) {
+
736 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
737 struct node **nodep = &f->name_table.array[hash];
+
738
+
739 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
740 if (*nodep == node) {
+
741 *nodep = node->name_next;
+
742 node->name_next = NULL;
+
743 unref_node(f, node->parent);
+
744 if (node->name != node->inline_name)
+
745 free(node->name);
+
746 node->name = NULL;
+
747 node->parent = NULL;
+
748 f->name_table.use--;
+
749
+
750 if (f->name_table.use < f->name_table.size / 4)
+
751 remerge_name(f);
+
752 return;
+
753 }
+
754 fuse_log(FUSE_LOG_ERR,
+
755 "fuse internal error: unable to unhash node: %llu\n",
+
756 (unsigned long long) node->nodeid);
+
757 abort();
+
758 }
+
759}
+
760
+
761static void rehash_name(struct fuse *f)
+
762{
+
763 struct node_table *t = &f->name_table;
+
764 struct node **nodep;
+
765 struct node **next;
+
766 size_t hash;
+
767
+
768 if (t->split == t->size / 2)
+
769 return;
+
770
+
771 hash = t->split;
+
772 t->split++;
+
773 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
774 struct node *node = *nodep;
+
775 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
776
+
777 if (newhash != hash) {
+
778 next = nodep;
+
779 *nodep = node->name_next;
+
780 node->name_next = t->array[newhash];
+
781 t->array[newhash] = node;
+
782 } else {
+
783 next = &node->name_next;
+
784 }
+
785 }
+
786 if (t->split == t->size / 2)
+
787 node_table_resize(t);
+
788}
+
789
+
790static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
791 const char *name)
+
792{
+
793 size_t hash = name_hash(f, parentid, name);
+
794 struct node *parent = get_node(f, parentid);
+
795 if (strlen(name) < sizeof(node->inline_name)) {
+
796 strcpy(node->inline_name, name);
+
797 node->name = node->inline_name;
+
798 } else {
+
799 node->name = strdup(name);
+
800 if (node->name == NULL)
+
801 return -1;
+
802 }
+
803
+
804 parent->refctr ++;
+
805 node->parent = parent;
+
806 node->name_next = f->name_table.array[hash];
+
807 f->name_table.array[hash] = node;
+
808 f->name_table.use++;
+
809
+
810 if (f->name_table.use >= f->name_table.size / 2)
+
811 rehash_name(f);
+
812
+
813 return 0;
+
814}
+
815
+
816static void delete_node(struct fuse *f, struct node *node)
+
817{
+
818 if (f->conf.debug)
+
819 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
820 (unsigned long long) node->nodeid);
+
821
+
822 assert(node->treelock == 0);
+
823 unhash_name(f, node);
+
824 if (lru_enabled(f))
+
825 remove_node_lru(node);
+
826 unhash_id(f, node);
+
827 free_node(f, node);
+
828}
+
829
+
830static void unref_node(struct fuse *f, struct node *node)
+
831{
+
832 assert(node->refctr > 0);
+
833 node->refctr --;
+
834 if (!node->refctr)
+
835 delete_node(f, node);
+
836}
+
837
+
838static fuse_ino_t next_id(struct fuse *f)
+
839{
+
840 do {
+
841 f->ctr = (f->ctr + 1) & 0xffffffff;
+
842 if (!f->ctr)
+
843 f->generation ++;
+
844 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
845 get_node_nocheck(f, f->ctr) != NULL);
+
846 return f->ctr;
+
847}
+
848
+
849static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
850 const char *name)
+
851{
+
852 size_t hash = name_hash(f, parent, name);
+
853 struct node *node;
+
854
+
855 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
856 if (node->parent->nodeid == parent &&
+
857 strcmp(node->name, name) == 0)
+
858 return node;
+
859
+
860 return NULL;
+
861}
+
862
+
863static void inc_nlookup(struct node *node)
+
864{
+
865 if (!node->nlookup)
+
866 node->refctr++;
+
867 node->nlookup++;
+
868}
+
869
+
870static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
871 const char *name)
+
872{
+
873 struct node *node;
+
874
+
875 pthread_mutex_lock(&f->lock);
+
876 if (!name)
+
877 node = get_node(f, parent);
+
878 else
+
879 node = lookup_node(f, parent, name);
+
880 if (node == NULL) {
+
881 node = alloc_node(f);
+
882 if (node == NULL)
+
883 goto out_err;
+
884
+
885 node->nodeid = next_id(f);
+
886 node->generation = f->generation;
+
887 if (f->conf.remember)
+
888 inc_nlookup(node);
+
889
+
890 if (hash_name(f, node, parent, name) == -1) {
+
891 free_node(f, node);
+
892 node = NULL;
+
893 goto out_err;
+
894 }
+
895 hash_id(f, node);
+
896 if (lru_enabled(f)) {
+
897 struct node_lru *lnode = node_lru(node);
+
898 init_list_head(&lnode->lru);
+
899 }
+
900 } else if (lru_enabled(f) && node->nlookup == 1) {
+
901 remove_node_lru(node);
+
902 }
+
903 inc_nlookup(node);
+
904out_err:
+
905 pthread_mutex_unlock(&f->lock);
+
906 return node;
+
907}
+
908
+
909static int lookup_path_in_cache(struct fuse *f,
+
910 const char *path, fuse_ino_t *inop)
+
911{
+
912 char *tmp = strdup(path);
+
913 if (!tmp)
+
914 return -ENOMEM;
+
915
+
916 pthread_mutex_lock(&f->lock);
+
917 fuse_ino_t ino = FUSE_ROOT_ID;
+
918
+
919 int err = 0;
+
920 char *save_ptr;
+
921 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
922 while (path_element != NULL) {
+
923 struct node *node = lookup_node(f, ino, path_element);
+
924 if (node == NULL) {
+
925 err = -ENOENT;
+
926 break;
+
927 }
+
928 ino = node->nodeid;
+
929 path_element = strtok_r(NULL, "/", &save_ptr);
+
930 }
+
931 pthread_mutex_unlock(&f->lock);
+
932 free(tmp);
+
933
+
934 if (!err)
+
935 *inop = ino;
+
936 return err;
+
937}
+
938
+
939static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
940{
+
941 size_t len = strlen(name);
+
942
+
943 if (s - len <= *buf) {
+
944 unsigned pathlen = *bufsize - (s - *buf);
+
945 unsigned newbufsize = *bufsize;
+
946 char *newbuf;
+
947
+
948 while (newbufsize < pathlen + len + 1) {
+
949 if (newbufsize >= 0x80000000)
+
950 newbufsize = 0xffffffff;
+
951 else
+
952 newbufsize *= 2;
+
953 }
+
954
+
955 newbuf = realloc(*buf, newbufsize);
+
956 if (newbuf == NULL)
+
957 return NULL;
+
958
+
959 *buf = newbuf;
+
960 s = newbuf + newbufsize - pathlen;
+
961 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
962 *bufsize = newbufsize;
+
963 }
+
964 s -= len;
+
965 memcpy(s, name, len);
+
966 s--;
+
967 *s = '/';
+
968
+
969 return s;
+
970}
+
971
+
972static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
973 struct node *end)
+
974{
+
975 struct node *node;
+
976
+
977 if (wnode) {
+
978 assert(wnode->treelock == TREELOCK_WRITE);
+
979 wnode->treelock = 0;
+
980 }
+
981
+
982 for (node = get_node(f, nodeid);
+
983 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
984 assert(node->treelock != 0);
+
985 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
986 assert(node->treelock != TREELOCK_WRITE);
+
987 node->treelock--;
+
988 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
989 node->treelock = 0;
+
990 }
+
991}
+
992
+
993static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
994 char **path, struct node **wnodep, bool need_lock)
+
995{
+
996 unsigned bufsize = 256;
+
997 char *buf;
+
998 char *s;
+
999 struct node *node;
+
1000 struct node *wnode = NULL;
+
1001 int err;
+
1002
+
1003 *path = NULL;
+
1004
+
1005 err = -ENOMEM;
+
1006 buf = malloc(bufsize);
+
1007 if (buf == NULL)
+
1008 goto out_err;
+
1009
+
1010 s = buf + bufsize - 1;
+
1011 *s = '\0';
+
1012
+
1013 if (name != NULL) {
+
1014 s = add_name(&buf, &bufsize, s, name);
+
1015 err = -ENOMEM;
+
1016 if (s == NULL)
+
1017 goto out_free;
+
1018 }
+
1019
+
1020 if (wnodep) {
+
1021 assert(need_lock);
+
1022 wnode = lookup_node(f, nodeid, name);
+
1023 if (wnode) {
+
1024 if (wnode->treelock != 0) {
+
1025 if (wnode->treelock > 0)
+
1026 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1027 err = -EAGAIN;
+
1028 goto out_free;
+
1029 }
+
1030 wnode->treelock = TREELOCK_WRITE;
+
1031 }
+
1032 }
+
1033
+
1034 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1035 node = node->parent) {
+
1036 err = -ESTALE;
+
1037 if (node->name == NULL || node->parent == NULL)
+
1038 goto out_unlock;
+
1039
+
1040 err = -ENOMEM;
+
1041 s = add_name(&buf, &bufsize, s, node->name);
+
1042 if (s == NULL)
+
1043 goto out_unlock;
+
1044
+
1045 if (need_lock) {
+
1046 err = -EAGAIN;
+
1047 if (node->treelock < 0)
+
1048 goto out_unlock;
+
1049
+
1050 node->treelock++;
+
1051 }
+
1052 }
+
1053
+
1054 if (s[0])
+
1055 memmove(buf, s, bufsize - (s - buf));
+
1056 else
+
1057 strcpy(buf, "/");
+
1058
+
1059 *path = buf;
+
1060 if (wnodep)
+
1061 *wnodep = wnode;
+
1062
+
1063 return 0;
+
1064
+
1065 out_unlock:
+
1066 if (need_lock)
+
1067 unlock_path(f, nodeid, wnode, node);
+
1068 out_free:
+
1069 free(buf);
+
1070
+
1071 out_err:
+
1072 return err;
+
1073}
+
1074
+
1075static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1076 fuse_ino_t nodeid2, const char *name2,
+
1077 char **path1, char **path2,
+
1078 struct node **wnode1, struct node **wnode2)
+
1079{
+
1080 int err;
+
1081
+
1082 /* FIXME: locking two paths needs deadlock checking */
+
1083 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1084 if (!err) {
+
1085 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1086 if (err) {
+
1087 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1088
+
1089 unlock_path(f, nodeid1, wn1, NULL);
+
1090 free(*path1);
+
1091 }
+
1092 }
+
1093 return err;
+
1094}
+
1095
+
1096static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1097{
+
1098 int err;
+
1099
+
1100 if (!qe->path1) {
+
1101 /* Just waiting for it to be unlocked */
+
1102 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1103 pthread_cond_signal(&qe->cond);
+
1104
+
1105 return;
+
1106 }
+
1107
+
1108 if (qe->done)
+
1109 return; // Don't try to double-lock the element
+
1110
+
1111 if (!qe->path2) {
+
1112 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1113 qe->wnode1, true);
+
1114 } else {
+
1115 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1116 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1117 qe->wnode2);
+
1118 }
+
1119
+
1120 if (err == -EAGAIN)
+
1121 return; /* keep trying */
+
1122
+
1123 qe->err = err;
+
1124 qe->done = true;
+
1125 pthread_cond_signal(&qe->cond);
+
1126}
+
1127
+
1128static void wake_up_queued(struct fuse *f)
+
1129{
+
1130 struct lock_queue_element *qe;
+
1131
+
1132 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1133 queue_element_wakeup(f, qe);
+
1134}
+
1135
+
1136static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1137 const char *name, bool wr)
+
1138{
+
1139 if (f->conf.debug) {
+
1140 struct node *wnode = NULL;
+
1141
+
1142 if (wr)
+
1143 wnode = lookup_node(f, nodeid, name);
+
1144
+
1145 if (wnode) {
+
1146 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1147 msg, (unsigned long long) wnode->nodeid);
+
1148 } else {
+
1149 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1150 msg, (unsigned long long) nodeid);
+
1151 }
+
1152 }
+
1153}
+
1154
+
1155static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1156{
+
1157 struct lock_queue_element **qp;
+
1158
+
1159 qe->done = false;
+
1160 pthread_cond_init(&qe->cond, NULL);
+
1161 qe->next = NULL;
+
1162 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1163 *qp = qe;
+
1164}
+
1165
+
1166static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1167{
+
1168 struct lock_queue_element **qp;
+
1169
+
1170 pthread_cond_destroy(&qe->cond);
+
1171 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1172 *qp = qe->next;
+
1173}
+
1174
+
1175static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1176{
+
1177 queue_path(f, qe);
+
1178
+
1179 do {
+
1180 pthread_cond_wait(&qe->cond, &f->lock);
+
1181 } while (!qe->done);
+
1182
+
1183 dequeue_path(f, qe);
+
1184
+
1185 return qe->err;
+
1186}
+
1187
+
1188static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1189 char **path, struct node **wnode)
+
1190{
+
1191 int err;
+
1192
+
1193 pthread_mutex_lock(&f->lock);
+
1194 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1195 if (err == -EAGAIN) {
+
1196 struct lock_queue_element qe = {
+
1197 .nodeid1 = nodeid,
+
1198 .name1 = name,
+
1199 .path1 = path,
+
1200 .wnode1 = wnode,
+
1201 };
+
1202 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1203 err = wait_path(f, &qe);
+
1204 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1205 }
+
1206 pthread_mutex_unlock(&f->lock);
+
1207
+
1208 return err;
+
1209}
+
1210
+
1211static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1212{
+
1213 return get_path_common(f, nodeid, NULL, path, NULL);
+
1214}
+
1215
+
1216static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1217{
+
1218 int err = 0;
+
1219
+
1220 if (f->conf.nullpath_ok) {
+
1221 *path = NULL;
+
1222 } else {
+
1223 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1224 if (err == -ESTALE)
+
1225 err = 0;
+
1226 }
+
1227
+
1228 return err;
+
1229}
+
1230
+
1231static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1232 char **path)
+
1233{
+
1234 return get_path_common(f, nodeid, name, path, NULL);
+
1235}
+
1236
+
1237static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1238 char **path, struct node **wnode)
+
1239{
+
1240 return get_path_common(f, nodeid, name, path, wnode);
+
1241}
+
1242
+
1243#if defined(__FreeBSD__)
+
1244#define CHECK_DIR_LOOP
+
1245#endif
+
1246
+
1247#if defined(CHECK_DIR_LOOP)
+
1248static int check_dir_loop(struct fuse *f,
+
1249 fuse_ino_t nodeid1, const char *name1,
+
1250 fuse_ino_t nodeid2, const char *name2)
+
1251{
+
1252 struct node *node, *node1, *node2;
+
1253 fuse_ino_t id1, id2;
+
1254
+
1255 node1 = lookup_node(f, nodeid1, name1);
+
1256 id1 = node1 ? node1->nodeid : nodeid1;
+
1257
+
1258 node2 = lookup_node(f, nodeid2, name2);
+
1259 id2 = node2 ? node2->nodeid : nodeid2;
+
1260
+
1261 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1262 node = node->parent) {
+
1263 if (node->name == NULL || node->parent == NULL)
+
1264 break;
+
1265
+
1266 if (node->nodeid != id2 && node->nodeid == id1)
+
1267 return -EINVAL;
+
1268 }
+
1269
+
1270 if (node2)
+
1271 {
+
1272 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1273 node = node->parent) {
+
1274 if (node->name == NULL || node->parent == NULL)
+
1275 break;
+
1276
+
1277 if (node->nodeid != id1 && node->nodeid == id2)
+
1278 return -ENOTEMPTY;
+
1279 }
+
1280 }
+
1281
+
1282 return 0;
+
1283}
+
1284#endif
+
1285
+
1286static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1287 fuse_ino_t nodeid2, const char *name2,
+
1288 char **path1, char **path2,
+
1289 struct node **wnode1, struct node **wnode2)
+
1290{
+
1291 int err;
+
1292
+
1293 pthread_mutex_lock(&f->lock);
+
1294
+
1295#if defined(CHECK_DIR_LOOP)
+
1296 if (name1)
+
1297 {
+
1298 // called during rename; perform dir loop check
+
1299 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1300 if (err)
+
1301 goto out_unlock;
+
1302 }
+
1303#endif
+
1304
+
1305 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1306 path1, path2, wnode1, wnode2);
+
1307 if (err == -EAGAIN) {
+
1308 struct lock_queue_element qe = {
+
1309 .nodeid1 = nodeid1,
+
1310 .name1 = name1,
+
1311 .path1 = path1,
+
1312 .wnode1 = wnode1,
+
1313 .nodeid2 = nodeid2,
+
1314 .name2 = name2,
+
1315 .path2 = path2,
+
1316 .wnode2 = wnode2,
+
1317 };
+
1318
+
1319 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1320 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1321 err = wait_path(f, &qe);
+
1322 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1323 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1324 }
+
1325
+
1326#if defined(CHECK_DIR_LOOP)
+
1327out_unlock:
+
1328#endif
+
1329 pthread_mutex_unlock(&f->lock);
+
1330
+
1331 return err;
+
1332}
+
1333
+
1334static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1335 struct node *wnode, char *path)
+
1336{
+
1337 pthread_mutex_lock(&f->lock);
+
1338 unlock_path(f, nodeid, wnode, NULL);
+
1339 if (f->lockq)
+
1340 wake_up_queued(f);
+
1341 pthread_mutex_unlock(&f->lock);
+
1342 free(path);
+
1343}
+
1344
+
1345static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1346{
+
1347 if (path)
+
1348 free_path_wrlock(f, nodeid, NULL, path);
+
1349}
+
1350
+
1351static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1352 struct node *wnode1, struct node *wnode2,
+
1353 char *path1, char *path2)
+
1354{
+
1355 pthread_mutex_lock(&f->lock);
+
1356 unlock_path(f, nodeid1, wnode1, NULL);
+
1357 unlock_path(f, nodeid2, wnode2, NULL);
+
1358 wake_up_queued(f);
+
1359 pthread_mutex_unlock(&f->lock);
+
1360 free(path1);
+
1361 free(path2);
+
1362}
+
1363
+
1364static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1365{
+
1366 struct node *node;
+
1367 if (nodeid == FUSE_ROOT_ID)
+
1368 return;
+
1369 pthread_mutex_lock(&f->lock);
+
1370 node = get_node(f, nodeid);
+
1371
+
1372 /*
+
1373 * Node may still be locked due to interrupt idiocy in open,
+
1374 * create and opendir
+
1375 */
+
1376 while (node->nlookup == nlookup && node->treelock) {
+
1377 struct lock_queue_element qe = {
+
1378 .nodeid1 = nodeid,
+
1379 };
+
1380
+
1381 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1382 queue_path(f, &qe);
+
1383
+
1384 do {
+
1385 pthread_cond_wait(&qe.cond, &f->lock);
+
1386 } while (node->nlookup == nlookup && node->treelock);
+
1387
+
1388 dequeue_path(f, &qe);
+
1389 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1390 }
+
1391
+
1392 assert(node->nlookup >= nlookup);
+
1393 node->nlookup -= nlookup;
+
1394 if (!node->nlookup) {
+
1395 unref_node(f, node);
+
1396 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1397 set_forget_time(f, node);
+
1398 }
+
1399 pthread_mutex_unlock(&f->lock);
+
1400}
+
1401
+
1402static void unlink_node(struct fuse *f, struct node *node)
+
1403{
+
1404 if (f->conf.remember) {
+
1405 assert(node->nlookup > 1);
+
1406 node->nlookup--;
+
1407 }
+
1408 unhash_name(f, node);
+
1409}
+
1410
+
1411static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1412{
+
1413 struct node *node;
+
1414
+
1415 pthread_mutex_lock(&f->lock);
+
1416 node = lookup_node(f, dir, name);
+
1417 if (node != NULL)
+
1418 unlink_node(f, node);
+
1419 pthread_mutex_unlock(&f->lock);
+
1420}
+
1421
+
1422static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1423 fuse_ino_t newdir, const char *newname, int hide)
+
1424{
+
1425 struct node *node;
+
1426 struct node *newnode;
+
1427 int err = 0;
+
1428
+
1429 pthread_mutex_lock(&f->lock);
+
1430 node = lookup_node(f, olddir, oldname);
+
1431 newnode = lookup_node(f, newdir, newname);
+
1432 if (node == NULL)
+
1433 goto out;
+
1434
+
1435 if (newnode != NULL) {
+
1436 if (hide) {
+
1437 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1438 err = -EBUSY;
+
1439 goto out;
+
1440 }
+
1441 unlink_node(f, newnode);
+
1442 }
+
1443
+
1444 unhash_name(f, node);
+
1445 if (hash_name(f, node, newdir, newname) == -1) {
+
1446 err = -ENOMEM;
+
1447 goto out;
+
1448 }
+
1449
+
1450 if (hide)
+
1451 node->is_hidden = 1;
+
1452
+
1453out:
+
1454 pthread_mutex_unlock(&f->lock);
+
1455 return err;
+
1456}
+
1457
+
1458static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1459 fuse_ino_t newdir, const char *newname)
+
1460{
+
1461 struct node *oldnode;
+
1462 struct node *newnode;
+
1463 int err;
+
1464
+
1465 pthread_mutex_lock(&f->lock);
+
1466 oldnode = lookup_node(f, olddir, oldname);
+
1467 newnode = lookup_node(f, newdir, newname);
+
1468
+
1469 if (oldnode)
+
1470 unhash_name(f, oldnode);
+
1471 if (newnode)
+
1472 unhash_name(f, newnode);
+
1473
+
1474 err = -ENOMEM;
+
1475 if (oldnode) {
+
1476 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1477 goto out;
+
1478 }
+
1479 if (newnode) {
+
1480 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1481 goto out;
+
1482 }
+
1483 err = 0;
+
1484out:
+
1485 pthread_mutex_unlock(&f->lock);
+
1486 return err;
+
1487}
+
1488
+
1489static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1490{
+
1491 if (!f->conf.use_ino)
+
1492 stbuf->st_ino = nodeid;
+
1493 if (f->conf.set_mode) {
+
1494 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1495 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1496 (0777 & ~f->conf.dmask);
+
1497 else if (f->conf.fmask)
+
1498 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1499 (0777 & ~f->conf.fmask);
+
1500 else
+
1501 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1502 (0777 & ~f->conf.umask);
+
1503 }
+
1504 if (f->conf.set_uid)
+
1505 stbuf->st_uid = f->conf.uid;
+
1506 if (f->conf.set_gid)
+
1507 stbuf->st_gid = f->conf.gid;
+
1508}
+
1509
+
1510static struct fuse *req_fuse(fuse_req_t req)
+
1511{
+
1512 return (struct fuse *) fuse_req_userdata(req);
+
1513}
+
1514
+
1515static void fuse_intr_sighandler(int sig)
+
1516{
+
1517 (void) sig;
+
1518 /* Nothing to do */
+
1519}
+
1520
+
1521struct fuse_intr_data {
+
1522 pthread_t id;
+
1523 pthread_cond_t cond;
+
1524 int finished;
+
1525};
+
1526
+
1527static void fuse_interrupt(fuse_req_t req, void *d_)
+
1528{
+
1529 struct fuse_intr_data *d = d_;
+
1530 struct fuse *f = req_fuse(req);
+
1531
+
1532 if (d->id == pthread_self())
+
1533 return;
+
1534
+
1535 pthread_mutex_lock(&f->lock);
+
1536 while (!d->finished) {
+
1537 struct timeval now;
+
1538 struct timespec timeout;
+
1539
+
1540 pthread_kill(d->id, f->conf.intr_signal);
+
1541 gettimeofday(&now, NULL);
+
1542 timeout.tv_sec = now.tv_sec + 1;
+
1543 timeout.tv_nsec = now.tv_usec * 1000;
+
1544 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1545 }
+
1546 pthread_mutex_unlock(&f->lock);
+
1547}
+
1548
+
1549static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1550 struct fuse_intr_data *d)
+
1551{
+
1552 pthread_mutex_lock(&f->lock);
+
1553 d->finished = 1;
+
1554 pthread_cond_broadcast(&d->cond);
+
1555 pthread_mutex_unlock(&f->lock);
+
1556 fuse_req_interrupt_func(req, NULL, NULL);
+
1557 pthread_cond_destroy(&d->cond);
+
1558}
+
1559
+
1560static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1561{
+
1562 d->id = pthread_self();
+
1563 pthread_cond_init(&d->cond, NULL);
+
1564 d->finished = 0;
+
1565 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1566}
+
1567
+
1568static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1569 struct fuse_intr_data *d)
+
1570{
+
1571 if (f->conf.intr)
+
1572 fuse_do_finish_interrupt(f, req, d);
+
1573}
+
1574
+
1575static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1576 struct fuse_intr_data *d)
+
1577{
+
1578 if (f->conf.intr)
+
1579 fuse_do_prepare_interrupt(req, d);
+
1580}
+
1581
+
1582static const char* file_info_string(struct fuse_file_info *fi,
+
1583 char* buf, size_t len)
+
1584{
+
1585 if(fi == NULL)
+
1586 return "NULL";
+
1587 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1588 return buf;
+
1589}
+
1590
+
1591int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1592 struct fuse_file_info *fi)
+
1593{
+
1594 fuse_get_context()->private_data = fs->user_data;
+
1595 if (fs->op.getattr) {
+
1596 if (fs->debug) {
+
1597 char buf[10];
+
1598 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1599 file_info_string(fi, buf, sizeof(buf)),
+
1600 path);
+
1601 }
+
1602 return fs->op.getattr(path, buf, fi);
+
1603 } else {
+
1604 return -ENOSYS;
+
1605 }
+
1606}
+
1607
+
1608int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1609 const char *newpath, unsigned int flags)
+
1610{
+
1611 fuse_get_context()->private_data = fs->user_data;
+
1612 if (fs->op.rename) {
+
1613 if (fs->debug)
+
1614 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1615 flags);
+
1616
+
1617 return fs->op.rename(oldpath, newpath, flags);
+
1618 } else {
+
1619 return -ENOSYS;
+
1620 }
+
1621}
+
1622
+
1623int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1624{
+
1625 fuse_get_context()->private_data = fs->user_data;
+
1626 if (fs->op.unlink) {
+
1627 if (fs->debug)
+
1628 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1629
+
1630 return fs->op.unlink(path);
+
1631 } else {
+
1632 return -ENOSYS;
+
1633 }
+
1634}
+
1635
+
1636int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1637{
+
1638 fuse_get_context()->private_data = fs->user_data;
+
1639 if (fs->op.rmdir) {
+
1640 if (fs->debug)
+
1641 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1642
+
1643 return fs->op.rmdir(path);
+
1644 } else {
+
1645 return -ENOSYS;
+
1646 }
+
1647}
+
1648
+
1649int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1650{
+
1651 fuse_get_context()->private_data = fs->user_data;
+
1652 if (fs->op.symlink) {
+
1653 if (fs->debug)
+
1654 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1655
+
1656 return fs->op.symlink(linkname, path);
+
1657 } else {
+
1658 return -ENOSYS;
+
1659 }
+
1660}
+
1661
+
1662int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1663{
+
1664 fuse_get_context()->private_data = fs->user_data;
+
1665 if (fs->op.link) {
+
1666 if (fs->debug)
+
1667 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1668
+
1669 return fs->op.link(oldpath, newpath);
+
1670 } else {
+
1671 return -ENOSYS;
+
1672 }
+
1673}
+
1674
+
1675int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1676 struct fuse_file_info *fi)
+
1677{
+
1678 fuse_get_context()->private_data = fs->user_data;
+
1679 if (fs->op.release) {
+
1680 if (fs->debug)
+
1681 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1682 fi->flush ? "+flush" : "",
+
1683 (unsigned long long) fi->fh, fi->flags);
+
1684
+
1685 return fs->op.release(path, fi);
+
1686 } else {
+
1687 return 0;
+
1688 }
+
1689}
+
1690
+
1691int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1692 struct fuse_file_info *fi)
+
1693{
+
1694 fuse_get_context()->private_data = fs->user_data;
+
1695 if (fs->op.opendir) {
+
1696 int err;
+
1697
+
1698 if (fs->debug)
+
1699 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1700 path);
+
1701
+
1702 err = fs->op.opendir(path, fi);
+
1703
+
1704 if (fs->debug && !err)
+
1705 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1706 (unsigned long long) fi->fh, fi->flags, path);
+
1707
+
1708 return err;
+
1709 } else {
+
1710 return 0;
+
1711 }
+
1712}
+
1713
+
1714int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1715 struct fuse_file_info *fi)
+
1716{
+
1717 fuse_get_context()->private_data = fs->user_data;
+
1718 if (fs->op.open) {
+
1719 int err;
+
1720
+
1721 if (fs->debug)
+
1722 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1723 path);
+
1724
+
1725 err = fs->op.open(path, fi);
+
1726
+
1727 if (fs->debug && !err)
+
1728 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1729 (unsigned long long) fi->fh, fi->flags, path);
+
1730
+
1731 return err;
+
1732 } else {
+
1733 return 0;
+
1734 }
+
1735}
+
1736
+
1737static void fuse_free_buf(struct fuse_bufvec *buf)
+
1738{
+
1739 if (buf != NULL) {
+
1740 size_t i;
+
1741
+
1742 for (i = 0; i < buf->count; i++)
+
1743 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1744 free(buf->buf[i].mem);
+
1745 free(buf);
+
1746 }
+
1747}
+
1748
+
1749int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1750 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1751 struct fuse_file_info *fi)
+
1752{
+
1753 fuse_get_context()->private_data = fs->user_data;
+
1754 if (fs->op.read || fs->op.read_buf) {
+
1755 int res;
+
1756
+
1757 if (fs->debug)
+
1758 fuse_log(FUSE_LOG_DEBUG,
+
1759 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1760 (unsigned long long) fi->fh,
+
1761 size, (unsigned long long) off, fi->flags);
+
1762
+
1763 if (fs->op.read_buf) {
+
1764 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1765 } else {
+
1766 struct fuse_bufvec *buf;
+
1767 void *mem;
+
1768
+
1769 buf = malloc(sizeof(struct fuse_bufvec));
+
1770 if (buf == NULL)
+
1771 return -ENOMEM;
+
1772
+
1773 mem = malloc(size);
+
1774 if (mem == NULL) {
+
1775 free(buf);
+
1776 return -ENOMEM;
+
1777 }
+
1778 *buf = FUSE_BUFVEC_INIT(size);
+
1779 buf->buf[0].mem = mem;
+
1780 *bufp = buf;
+
1781
+
1782 res = fs->op.read(path, mem, size, off, fi);
+
1783 if (res >= 0)
+
1784 buf->buf[0].size = res;
+
1785 }
+
1786
+
1787 if (fs->debug && res >= 0)
+
1788 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1789 (unsigned long long) fi->fh,
+
1790 fuse_buf_size(*bufp),
+
1791 (unsigned long long) off);
+
1792 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1793 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1794
+
1795 if (res < 0)
+
1796 return res;
+
1797
+
1798 return 0;
+
1799 } else {
+
1800 return -ENOSYS;
+
1801 }
+
1802}
+
1803
+
1804int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1805 off_t off, struct fuse_file_info *fi)
+
1806{
+
1807 fuse_get_context()->private_data = fs->user_data;
+
1808 if (fs->op.read || fs->op.read_buf) {
+
1809 int res;
+
1810
+
1811 if (fs->debug)
+
1812 fuse_log(FUSE_LOG_DEBUG,
+
1813 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1814 (unsigned long long) fi->fh,
+
1815 size, (unsigned long long) off, fi->flags);
+
1816
+
1817 if (fs->op.read_buf) {
+
1818 struct fuse_bufvec *buf = NULL;
+
1819
+
1820 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1821 if (res == 0) {
+
1822 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1823
+
1824 dst.buf[0].mem = mem;
+
1825 res = fuse_buf_copy(&dst, buf, 0);
+
1826 }
+
1827 fuse_free_buf(buf);
+
1828 } else {
+
1829 res = fs->op.read(path, mem, size, off, fi);
+
1830 }
+
1831
+
1832 if (fs->debug && res >= 0)
+
1833 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1834 (unsigned long long) fi->fh,
+
1835 res,
+
1836 (unsigned long long) off);
+
1837 if (res >= 0 && res > (int) size)
+
1838 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1839
+
1840 return res;
+
1841 } else {
+
1842 return -ENOSYS;
+
1843 }
+
1844}
+
1845
+
1846int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1847 struct fuse_bufvec *buf, off_t off,
+
1848 struct fuse_file_info *fi)
+
1849{
+
1850 fuse_get_context()->private_data = fs->user_data;
+
1851 if (fs->op.write_buf || fs->op.write) {
+
1852 int res;
+
1853 size_t size = fuse_buf_size(buf);
+
1854
+
1855 assert(buf->idx == 0 && buf->off == 0);
+
1856 if (fs->debug)
+
1857 fuse_log(FUSE_LOG_DEBUG,
+
1858 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1859 fi->writepage ? "page" : "",
+
1860 (unsigned long long) fi->fh,
+
1861 size,
+
1862 (unsigned long long) off,
+
1863 fi->flags);
+
1864
+
1865 if (fs->op.write_buf) {
+
1866 res = fs->op.write_buf(path, buf, off, fi);
+
1867 } else {
+
1868 void *mem = NULL;
+
1869 struct fuse_buf *flatbuf;
+
1870 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1871
+
1872 if (buf->count == 1 &&
+
1873 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1874 flatbuf = &buf->buf[0];
+
1875 } else {
+
1876 res = -ENOMEM;
+
1877 mem = malloc(size);
+
1878 if (mem == NULL)
+
1879 goto out;
+
1880
+
1881 tmp.buf[0].mem = mem;
+
1882 res = fuse_buf_copy(&tmp, buf, 0);
+
1883 if (res <= 0)
+
1884 goto out_free;
+
1885
+
1886 tmp.buf[0].size = res;
+
1887 flatbuf = &tmp.buf[0];
+
1888 }
+
1889
+
1890 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1891 off, fi);
+
1892out_free:
+
1893 free(mem);
+
1894 }
+
1895out:
+
1896 if (fs->debug && res >= 0)
+
1897 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1898 fi->writepage ? "page" : "",
+
1899 (unsigned long long) fi->fh, res,
+
1900 (unsigned long long) off);
+
1901 if (res > (int) size)
+
1902 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1903
+
1904 return res;
+
1905 } else {
+
1906 return -ENOSYS;
+
1907 }
+
1908}
+
1909
+
1910int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1911 size_t size, off_t off, struct fuse_file_info *fi)
+
1912{
+
1913 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1914
+
1915 bufv.buf[0].mem = (void *) mem;
+
1916
+
1917 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1918}
+
1919
+
1920int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1921 struct fuse_file_info *fi)
+
1922{
+
1923 fuse_get_context()->private_data = fs->user_data;
+
1924 if (fs->op.fsync) {
+
1925 if (fs->debug)
+
1926 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1927 (unsigned long long) fi->fh, datasync);
+
1928
+
1929 return fs->op.fsync(path, datasync, fi);
+
1930 } else {
+
1931 return -ENOSYS;
+
1932 }
+
1933}
+
1934
+
1935int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1936 struct fuse_file_info *fi)
+
1937{
+
1938 fuse_get_context()->private_data = fs->user_data;
+
1939 if (fs->op.fsyncdir) {
+
1940 if (fs->debug)
+
1941 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1942 (unsigned long long) fi->fh, datasync);
+
1943
+
1944 return fs->op.fsyncdir(path, datasync, fi);
+
1945 } else {
+
1946 return -ENOSYS;
+
1947 }
+
1948}
+
1949
+
1950int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1951 struct fuse_file_info *fi)
+
1952{
+
1953 fuse_get_context()->private_data = fs->user_data;
+
1954 if (fs->op.flush) {
+
1955 if (fs->debug)
+
1956 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1957 (unsigned long long) fi->fh);
+
1958
+
1959 return fs->op.flush(path, fi);
+
1960 } else {
+
1961 return -ENOSYS;
+
1962 }
+
1963}
+
1964
+
1965int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1966{
+
1967 fuse_get_context()->private_data = fs->user_data;
+
1968 if (fs->op.statfs) {
+
1969 if (fs->debug)
+
1970 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1971
+
1972 return fs->op.statfs(path, buf);
+
1973 } else {
+
1974 buf->f_namemax = 255;
+
1975 buf->f_bsize = 512;
+
1976 return 0;
+
1977 }
+
1978}
+
1979
+
1980int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1981 struct fuse_file_info *fi)
+
1982{
+
1983 fuse_get_context()->private_data = fs->user_data;
+
1984 if (fs->op.releasedir) {
+
1985 if (fs->debug)
+
1986 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1987 (unsigned long long) fi->fh, fi->flags);
+
1988
+
1989 return fs->op.releasedir(path, fi);
+
1990 } else {
+
1991 return 0;
+
1992 }
+
1993}
+
1994
+
1995int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1996 fuse_fill_dir_t filler, off_t off,
+
1997 struct fuse_file_info *fi,
+
1998 enum fuse_readdir_flags flags)
+
1999{
+
2000 fuse_get_context()->private_data = fs->user_data;
+
2001 if (fs->op.readdir) {
+
2002 if (fs->debug) {
+
2003 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2004 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2005 (unsigned long long) fi->fh,
+
2006 (unsigned long long) off);
+
2007 }
+
2008
+
2009 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2010 } else {
+
2011 return -ENOSYS;
+
2012 }
+
2013}
+
2014
+
2015int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2016 struct fuse_file_info *fi)
+
2017{
+
2018 fuse_get_context()->private_data = fs->user_data;
+
2019 if (fs->op.create) {
+
2020 int err;
+
2021
+
2022 if (fs->debug)
+
2023 fuse_log(FUSE_LOG_DEBUG,
+
2024 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2025 fi->flags, path, mode,
+
2026 fuse_get_context()->umask);
+
2027
+
2028 err = fs->op.create(path, mode, fi);
+
2029
+
2030 if (fs->debug && !err)
+
2031 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2032 (unsigned long long) fi->fh, fi->flags, path);
+
2033
+
2034 return err;
+
2035 } else {
+
2036 return -ENOSYS;
+
2037 }
+
2038}
+
2039
+
2040int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2041 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2042{
+
2043 fuse_get_context()->private_data = fs->user_data;
+
2044 if (fs->op.lock) {
+
2045 if (fs->debug)
+
2046 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2047 (unsigned long long) fi->fh,
+
2048 (cmd == F_GETLK ? "F_GETLK" :
+
2049 (cmd == F_SETLK ? "F_SETLK" :
+
2050 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2051 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2052 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2053 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2054 "???"))),
+
2055 (unsigned long long) lock->l_start,
+
2056 (unsigned long long) lock->l_len,
+
2057 (unsigned long long) lock->l_pid);
+
2058
+
2059 return fs->op.lock(path, fi, cmd, lock);
+
2060 } else {
+
2061 return -ENOSYS;
+
2062 }
+
2063}
+
2064
+
2065int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2066 struct fuse_file_info *fi, int op)
+
2067{
+
2068 fuse_get_context()->private_data = fs->user_data;
+
2069 if (fs->op.flock) {
+
2070 if (fs->debug) {
+
2071 int xop = op & ~LOCK_NB;
+
2072
+
2073 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2074 (unsigned long long) fi->fh,
+
2075 xop == LOCK_SH ? "LOCK_SH" :
+
2076 (xop == LOCK_EX ? "LOCK_EX" :
+
2077 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2078 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2079 }
+
2080 return fs->op.flock(path, fi, op);
+
2081 } else {
+
2082 return -ENOSYS;
+
2083 }
+
2084}
+
2085
+
2086int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2087 gid_t gid, struct fuse_file_info *fi)
+
2088{
+
2089 fuse_get_context()->private_data = fs->user_data;
+
2090 if (fs->op.chown) {
+
2091 if (fs->debug) {
+
2092 char buf[10];
+
2093 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2094 file_info_string(fi, buf, sizeof(buf)),
+
2095 path, (unsigned long) uid, (unsigned long) gid);
+
2096 }
+
2097 return fs->op.chown(path, uid, gid, fi);
+
2098 } else {
+
2099 return -ENOSYS;
+
2100 }
+
2101}
+
2102
+
2103int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2104 struct fuse_file_info *fi)
+
2105{
+
2106 fuse_get_context()->private_data = fs->user_data;
+
2107 if (fs->op.truncate) {
+
2108 if (fs->debug) {
+
2109 char buf[10];
+
2110 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2111 file_info_string(fi, buf, sizeof(buf)),
+
2112 (unsigned long long) size);
+
2113 }
+
2114 return fs->op.truncate(path, size, fi);
+
2115 } else {
+
2116 return -ENOSYS;
+
2117 }
+
2118}
+
2119
+
2120int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2121 const struct timespec tv[2], struct fuse_file_info *fi)
+
2122{
+
2123 fuse_get_context()->private_data = fs->user_data;
+
2124 if (fs->op.utimens) {
+
2125 if (fs->debug) {
+
2126 char buf[10];
+
2127 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n",
+
2128 file_info_string(fi, buf, sizeof(buf)),
+
2129 path, tv[0].tv_sec, tv[0].tv_nsec,
+
2130 tv[1].tv_sec, tv[1].tv_nsec);
+
2131 }
+
2132 return fs->op.utimens(path, tv, fi);
+
2133 } else {
+
2134 return -ENOSYS;
+
2135 }
+
2136}
+
2137
+
2138int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2139{
+
2140 fuse_get_context()->private_data = fs->user_data;
+
2141 if (fs->op.access) {
+
2142 if (fs->debug)
+
2143 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2144
+
2145 return fs->op.access(path, mask);
+
2146 } else {
+
2147 return -ENOSYS;
+
2148 }
+
2149}
+
2150
+
2151int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2152 size_t len)
+
2153{
+
2154 fuse_get_context()->private_data = fs->user_data;
+
2155 if (fs->op.readlink) {
+
2156 if (fs->debug)
+
2157 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2158 (unsigned long) len);
+
2159
+
2160 return fs->op.readlink(path, buf, len);
+
2161 } else {
+
2162 return -ENOSYS;
+
2163 }
+
2164}
+
2165
+
2166int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2167 dev_t rdev)
+
2168{
+
2169 fuse_get_context()->private_data = fs->user_data;
+
2170 if (fs->op.mknod) {
+
2171 if (fs->debug)
+
2172 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2173 path, mode, (unsigned long long) rdev,
+
2174 fuse_get_context()->umask);
+
2175
+
2176 return fs->op.mknod(path, mode, rdev);
+
2177 } else {
+
2178 return -ENOSYS;
+
2179 }
+
2180}
+
2181
+
2182int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2183{
+
2184 fuse_get_context()->private_data = fs->user_data;
+
2185 if (fs->op.mkdir) {
+
2186 if (fs->debug)
+
2187 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2188 path, mode, fuse_get_context()->umask);
+
2189
+
2190 return fs->op.mkdir(path, mode);
+
2191 } else {
+
2192 return -ENOSYS;
+
2193 }
+
2194}
+
2195
+
2196int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2197 const char *value, size_t size, int flags)
+
2198{
+
2199 fuse_get_context()->private_data = fs->user_data;
+
2200 if (fs->op.setxattr) {
+
2201 if (fs->debug)
+
2202 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2203 path, name, (unsigned long) size, flags);
+
2204
+
2205 return fs->op.setxattr(path, name, value, size, flags);
+
2206 } else {
+
2207 return -ENOSYS;
+
2208 }
+
2209}
+
2210
+
2211int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2212 char *value, size_t size)
+
2213{
+
2214 fuse_get_context()->private_data = fs->user_data;
+
2215 if (fs->op.getxattr) {
+
2216 if (fs->debug)
+
2217 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2218 path, name, (unsigned long) size);
+
2219
+
2220 return fs->op.getxattr(path, name, value, size);
+
2221 } else {
+
2222 return -ENOSYS;
+
2223 }
+
2224}
+
2225
+
2226int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2227 size_t size)
+
2228{
+
2229 fuse_get_context()->private_data = fs->user_data;
+
2230 if (fs->op.listxattr) {
+
2231 if (fs->debug)
+
2232 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2233 path, (unsigned long) size);
+
2234
+
2235 return fs->op.listxattr(path, list, size);
+
2236 } else {
+
2237 return -ENOSYS;
+
2238 }
+
2239}
+
2240
+
2241int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2242 uint64_t *idx)
+
2243{
+
2244 fuse_get_context()->private_data = fs->user_data;
+
2245 if (fs->op.bmap) {
+
2246 if (fs->debug)
+
2247 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2248 path, (unsigned long) blocksize,
+
2249 (unsigned long long) *idx);
+
2250
+
2251 return fs->op.bmap(path, blocksize, idx);
+
2252 } else {
+
2253 return -ENOSYS;
+
2254 }
+
2255}
+
2256
+
2257int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2258{
+
2259 fuse_get_context()->private_data = fs->user_data;
+
2260 if (fs->op.removexattr) {
+
2261 if (fs->debug)
+
2262 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2263
+
2264 return fs->op.removexattr(path, name);
+
2265 } else {
+
2266 return -ENOSYS;
+
2267 }
+
2268}
+
2269
+
2270int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2271 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2272 void *data)
+
2273{
+
2274 fuse_get_context()->private_data = fs->user_data;
+
2275 if (fs->op.ioctl) {
+
2276 if (fs->debug)
+
2277 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2278 (unsigned long long) fi->fh, cmd, flags);
+
2279
+
2280 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2281 } else
+
2282 return -ENOSYS;
+
2283}
+
2284
+
2285int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2286 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2287 unsigned *reventsp)
+
2288{
+
2289 fuse_get_context()->private_data = fs->user_data;
+
2290 if (fs->op.poll) {
+
2291 int res;
+
2292
+
2293 if (fs->debug)
+
2294 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2295 (unsigned long long) fi->fh, ph,
+
2296 fi->poll_events);
+
2297
+
2298 res = fs->op.poll(path, fi, ph, reventsp);
+
2299
+
2300 if (fs->debug && !res)
+
2301 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2302 (unsigned long long) fi->fh, *reventsp);
+
2303
+
2304 return res;
+
2305 } else
+
2306 return -ENOSYS;
+
2307}
+
2308
+
2309int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2310 off_t offset, off_t length, struct fuse_file_info *fi)
+
2311{
+
2312 fuse_get_context()->private_data = fs->user_data;
+
2313 if (fs->op.fallocate) {
+
2314 if (fs->debug)
+
2315 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2316 path,
+
2317 mode,
+
2318 (unsigned long long) offset,
+
2319 (unsigned long long) length);
+
2320
+
2321 return fs->op.fallocate(path, mode, offset, length, fi);
+
2322 } else
+
2323 return -ENOSYS;
+
2324}
+
2325
+
2326ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2327 struct fuse_file_info *fi_in, off_t off_in,
+
2328 const char *path_out,
+
2329 struct fuse_file_info *fi_out, off_t off_out,
+
2330 size_t len, int flags)
+
2331{
+
2332 fuse_get_context()->private_data = fs->user_data;
+
2333 if (fs->op.copy_file_range) {
+
2334 if (fs->debug)
+
2335 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2336 "%s:%llu, length: %llu\n",
+
2337 path_in,
+
2338 (unsigned long long) off_in,
+
2339 path_out,
+
2340 (unsigned long long) off_out,
+
2341 (unsigned long long) len);
+
2342
+
2343 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2344 fi_out, off_out, len, flags);
+
2345 } else
+
2346 return -ENOSYS;
+
2347}
+
2348
+
2349off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2350 struct fuse_file_info *fi)
+
2351{
+
2352 fuse_get_context()->private_data = fs->user_data;
+
2353 if (fs->op.lseek) {
+
2354 if (fs->debug) {
+
2355 char buf[10];
+
2356 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2357 file_info_string(fi, buf, sizeof(buf)),
+
2358 (unsigned long long) off, whence);
+
2359 }
+
2360 return fs->op.lseek(path, off, whence, fi);
+
2361 } else {
+
2362 return -ENOSYS;
+
2363 }
+
2364}
+
2365
+
2366static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2367{
+
2368 struct node *node;
+
2369 int isopen = 0;
+
2370 pthread_mutex_lock(&f->lock);
+
2371 node = lookup_node(f, dir, name);
+
2372 if (node && node->open_count > 0)
+
2373 isopen = 1;
+
2374 pthread_mutex_unlock(&f->lock);
+
2375 return isopen;
+
2376}
+
2377
+
2378static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2379 char *newname, size_t bufsize)
+
2380{
+
2381 struct stat buf;
+
2382 struct node *node;
+
2383 struct node *newnode;
+
2384 char *newpath;
+
2385 int res;
+
2386 int failctr = 10;
+
2387
+
2388 do {
+
2389 pthread_mutex_lock(&f->lock);
+
2390 node = lookup_node(f, dir, oldname);
+
2391 if (node == NULL) {
+
2392 pthread_mutex_unlock(&f->lock);
+
2393 return NULL;
+
2394 }
+
2395 do {
+
2396 f->hidectr ++;
+
2397 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2398 (unsigned int) node->nodeid, f->hidectr);
+
2399 newnode = lookup_node(f, dir, newname);
+
2400 } while(newnode);
+
2401
+
2402 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2403 pthread_mutex_unlock(&f->lock);
+
2404 if (res)
+
2405 break;
+
2406
+
2407 memset(&buf, 0, sizeof(buf));
+
2408 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2409 if (res == -ENOENT)
+
2410 break;
+
2411 free(newpath);
+
2412 newpath = NULL;
+
2413 } while(res == 0 && --failctr);
+
2414
+
2415 return newpath;
+
2416}
+
2417
+
2418static int hide_node(struct fuse *f, const char *oldpath,
+
2419 fuse_ino_t dir, const char *oldname)
+
2420{
+
2421 char newname[64];
+
2422 char *newpath;
+
2423 int err = -EBUSY;
+
2424
+
2425 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2426 if (newpath) {
+
2427 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2428 if (!err)
+
2429 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2430 free(newpath);
+
2431 }
+
2432 return err;
+
2433}
+
2434
+
2435static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2436{
+
2437 return stbuf->st_mtime == ts->tv_sec &&
+
2438 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2439}
+
2440
+
2441#ifndef CLOCK_MONOTONIC
+
2442#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2443#endif
+
2444
+
2445static void curr_time(struct timespec *now)
+
2446{
+
2447 static clockid_t clockid = CLOCK_MONOTONIC;
+
2448 int res = clock_gettime(clockid, now);
+
2449 if (res == -1 && errno == EINVAL) {
+
2450 clockid = CLOCK_REALTIME;
+
2451 res = clock_gettime(clockid, now);
+
2452 }
+
2453 if (res == -1) {
+
2454 perror("fuse: clock_gettime");
+
2455 abort();
+
2456 }
+
2457}
+
2458
+
2459static void update_stat(struct node *node, const struct stat *stbuf)
+
2460{
+
2461 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2462 stbuf->st_size != node->size))
+
2463 node->cache_valid = 0;
+
2464 node->mtime.tv_sec = stbuf->st_mtime;
+
2465 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2466 node->size = stbuf->st_size;
+
2467 curr_time(&node->stat_updated);
+
2468}
+
2469
+
2470static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2471 struct fuse_entry_param *e)
+
2472{
+
2473 struct node *node;
+
2474
+
2475 node = find_node(f, nodeid, name);
+
2476 if (node == NULL)
+
2477 return -ENOMEM;
+
2478
+
2479 e->ino = node->nodeid;
+
2480 e->generation = node->generation;
+
2481 e->entry_timeout = f->conf.entry_timeout;
+
2482 e->attr_timeout = f->conf.attr_timeout;
+
2483 if (f->conf.auto_cache) {
+
2484 pthread_mutex_lock(&f->lock);
+
2485 update_stat(node, &e->attr);
+
2486 pthread_mutex_unlock(&f->lock);
+
2487 }
+
2488 set_stat(f, e->ino, &e->attr);
+
2489 return 0;
+
2490}
+
2491
+
2492static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2493 const char *name, const char *path,
+
2494 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2495{
+
2496 int res;
+
2497
+
2498 memset(e, 0, sizeof(struct fuse_entry_param));
+
2499 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2500 if (res == 0) {
+
2501 res = do_lookup(f, nodeid, name, e);
+
2502 if (res == 0 && f->conf.debug) {
+
2503 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2504 (unsigned long long) e->ino);
+
2505 }
+
2506 }
+
2507 return res;
+
2508}
+
2509
+
2510static struct fuse_context_i *fuse_get_context_internal(void)
+
2511{
+
2512 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2513}
+
2514
+
2515static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2516{
+
2517 struct fuse_context_i *c = fuse_get_context_internal();
+
2518 if (c == NULL) {
+
2519 c = (struct fuse_context_i *)
+
2520 calloc(1, sizeof(struct fuse_context_i));
+
2521 if (c == NULL) {
+
2522 /* This is hard to deal with properly, so just
+
2523 abort. If memory is so low that the
+
2524 context cannot be allocated, there's not
+
2525 much hope for the filesystem anyway */
+
2526 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2527 abort();
+
2528 }
+
2529 pthread_setspecific(fuse_context_key, c);
+
2530 } else {
+
2531 memset(c, 0, sizeof(*c));
+
2532 }
+
2533 c->ctx.fuse = f;
+
2534
+
2535 return c;
+
2536}
+
2537
+
2538static void fuse_freecontext(void *data)
+
2539{
+
2540 free(data);
+
2541}
+
2542
+
2543static int fuse_create_context_key(void)
+
2544{
+
2545 int err = 0;
+
2546 pthread_mutex_lock(&fuse_context_lock);
+
2547 if (!fuse_context_ref) {
+
2548 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2549 if (err) {
+
2550 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2551 strerror(err));
+
2552 pthread_mutex_unlock(&fuse_context_lock);
+
2553 return -1;
+
2554 }
+
2555 }
+
2556 fuse_context_ref++;
+
2557 pthread_mutex_unlock(&fuse_context_lock);
+
2558 return 0;
+
2559}
+
2560
+
2561static void fuse_delete_context_key(void)
+
2562{
+
2563 pthread_mutex_lock(&fuse_context_lock);
+
2564 fuse_context_ref--;
+
2565 if (!fuse_context_ref) {
+
2566 free(pthread_getspecific(fuse_context_key));
+
2567 pthread_key_delete(fuse_context_key);
+
2568 }
+
2569 pthread_mutex_unlock(&fuse_context_lock);
+
2570}
+
2571
+
2572static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2573{
+
2574 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2575 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2576 c->req = req;
+
2577 c->ctx.uid = ctx->uid;
+
2578 c->ctx.gid = ctx->gid;
+
2579 c->ctx.pid = ctx->pid;
+
2580 c->ctx.umask = ctx->umask;
+
2581 return c->ctx.fuse;
+
2582}
+
2583
+
2584static inline void reply_err(fuse_req_t req, int err)
+
2585{
+
2586 /* fuse_reply_err() uses non-negated errno values */
+
2587 fuse_reply_err(req, -err);
+
2588}
+
2589
+
2590static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2591 int err)
+
2592{
+
2593 if (!err) {
+
2594 struct fuse *f = req_fuse(req);
+
2595 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2596 /* Skip forget for negative result */
+
2597 if (e->ino != 0)
+
2598 forget_node(f, e->ino, 1);
+
2599 }
+
2600 } else
+
2601 reply_err(req, err);
+
2602}
+
2603
+
2604void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2605 struct fuse_config *cfg)
+
2606{
+
2607 fuse_get_context()->private_data = fs->user_data;
+
2608 if (!fs->op.write_buf)
+
2609 conn->want &= ~FUSE_CAP_SPLICE_READ;
+
2610 if (!fs->op.lock)
+
2611 conn->want &= ~FUSE_CAP_POSIX_LOCKS;
+
2612 if (!fs->op.flock)
+
2613 conn->want &= ~FUSE_CAP_FLOCK_LOCKS;
+
2614 if (fs->op.init)
+
2615 fs->user_data = fs->op.init(conn, cfg);
+
2616}
+
2617
+
2618static int fuse_init_intr_signal(int signum, int *installed);
+
2619
+
2620static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2621{
+
2622 struct fuse *f = (struct fuse *) data;
+
2623
+
2624 fuse_create_context(f);
+
2625 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
+
2626 fuse_fs_init(f->fs, conn, &f->conf);
+
2627
+
2628 if (f->conf.intr) {
+
2629 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2630 &f->intr_installed) == -1)
+
2631 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2632 } else {
+
2633 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2634 conn->no_interrupt = 1;
+
2635 }
+
2636}
+
2637
+
2638void fuse_fs_destroy(struct fuse_fs *fs)
+
2639{
+
2640 fuse_get_context()->private_data = fs->user_data;
+
2641 if (fs->op.destroy)
+
2642 fs->op.destroy(fs->user_data);
+
2643}
+
2644
+
2645static void fuse_lib_destroy(void *data)
+
2646{
+
2647 struct fuse *f = (struct fuse *) data;
+
2648
+
2649 fuse_create_context(f);
+
2650 fuse_fs_destroy(f->fs);
+
2651}
+
2652
+
2653static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2654 const char *name)
+
2655{
+
2656 struct fuse *f = req_fuse_prepare(req);
+
2657 struct fuse_entry_param e;
+
2658 char *path;
+
2659 int err;
+
2660 struct node *dot = NULL;
+
2661
+
2662 if (name[0] == '.') {
+
2663 int len = strlen(name);
+
2664
+
2665 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2666 pthread_mutex_lock(&f->lock);
+
2667 if (len == 1) {
+
2668 if (f->conf.debug)
+
2669 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2670 dot = get_node_nocheck(f, parent);
+
2671 if (dot == NULL) {
+
2672 pthread_mutex_unlock(&f->lock);
+
2673 reply_entry(req, &e, -ESTALE);
+
2674 return;
+
2675 }
+
2676 dot->refctr++;
+
2677 } else {
+
2678 if (f->conf.debug)
+
2679 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2680 parent = get_node(f, parent)->parent->nodeid;
+
2681 }
+
2682 pthread_mutex_unlock(&f->lock);
+
2683 name = NULL;
+
2684 }
+
2685 }
+
2686
+
2687 err = get_path_name(f, parent, name, &path);
+
2688 if (!err) {
+
2689 struct fuse_intr_data d;
+
2690 if (f->conf.debug)
+
2691 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2692 fuse_prepare_interrupt(f, req, &d);
+
2693 err = lookup_path(f, parent, name, path, &e, NULL);
+
2694 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2695 e.ino = 0;
+
2696 e.entry_timeout = f->conf.negative_timeout;
+
2697 err = 0;
+
2698 }
+
2699 fuse_finish_interrupt(f, req, &d);
+
2700 free_path(f, parent, path);
+
2701 }
+
2702 if (dot) {
+
2703 pthread_mutex_lock(&f->lock);
+
2704 unref_node(f, dot);
+
2705 pthread_mutex_unlock(&f->lock);
+
2706 }
+
2707 reply_entry(req, &e, err);
+
2708}
+
2709
+
2710static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2711{
+
2712 if (f->conf.debug)
+
2713 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2714 (unsigned long long) nlookup);
+
2715 forget_node(f, ino, nlookup);
+
2716}
+
2717
+
2718static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2719{
+
2720 do_forget(req_fuse(req), ino, nlookup);
+
2721 fuse_reply_none(req);
+
2722}
+
2723
+
2724static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2725 struct fuse_forget_data *forgets)
+
2726{
+
2727 struct fuse *f = req_fuse(req);
+
2728 size_t i;
+
2729
+
2730 for (i = 0; i < count; i++)
+
2731 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2732
+
2733 fuse_reply_none(req);
+
2734}
+
2735
+
2736
+
2737static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2738 struct fuse_file_info *fi)
+
2739{
+
2740 struct fuse *f = req_fuse_prepare(req);
+
2741 struct stat buf;
+
2742 char *path;
+
2743 int err;
+
2744
+
2745 memset(&buf, 0, sizeof(buf));
+
2746
+
2747 if (fi != NULL)
+
2748 err = get_path_nullok(f, ino, &path);
+
2749 else
+
2750 err = get_path(f, ino, &path);
+
2751 if (!err) {
+
2752 struct fuse_intr_data d;
+
2753 fuse_prepare_interrupt(f, req, &d);
+
2754 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2755 fuse_finish_interrupt(f, req, &d);
+
2756 free_path(f, ino, path);
+
2757 }
+
2758 if (!err) {
+
2759 struct node *node;
+
2760
+
2761 pthread_mutex_lock(&f->lock);
+
2762 node = get_node(f, ino);
+
2763 if (node->is_hidden && buf.st_nlink > 0)
+
2764 buf.st_nlink--;
+
2765 if (f->conf.auto_cache)
+
2766 update_stat(node, &buf);
+
2767 pthread_mutex_unlock(&f->lock);
+
2768 set_stat(f, ino, &buf);
+
2769 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2770 } else
+
2771 reply_err(req, err);
+
2772}
+
2773
+
2774int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2775 struct fuse_file_info *fi)
+
2776{
+
2777 fuse_get_context()->private_data = fs->user_data;
+
2778 if (fs->op.chmod) {
+
2779 if (fs->debug) {
+
2780 char buf[10];
+
2781 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2782 file_info_string(fi, buf, sizeof(buf)),
+
2783 path, (unsigned long long) mode);
+
2784 }
+
2785 return fs->op.chmod(path, mode, fi);
+
2786 }
+
2787 else
+
2788 return -ENOSYS;
+
2789}
+
2790
+
2791static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2792 int valid, struct fuse_file_info *fi)
+
2793{
+
2794 struct fuse *f = req_fuse_prepare(req);
+
2795 struct stat buf;
+
2796 char *path;
+
2797 int err;
+
2798
+
2799 memset(&buf, 0, sizeof(buf));
+
2800 if (fi != NULL)
+
2801 err = get_path_nullok(f, ino, &path);
+
2802 else
+
2803 err = get_path(f, ino, &path);
+
2804 if (!err) {
+
2805 struct fuse_intr_data d;
+
2806 fuse_prepare_interrupt(f, req, &d);
+
2807 err = 0;
+
2808 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2809 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2810 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2811 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2812 attr->st_uid : (uid_t) -1;
+
2813 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2814 attr->st_gid : (gid_t) -1;
+
2815 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2816 }
+
2817 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2818 err = fuse_fs_truncate(f->fs, path,
+
2819 attr->st_size, fi);
+
2820 }
+
2821#ifdef HAVE_UTIMENSAT
+
2822 if (!err &&
+
2823 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2824 struct timespec tv[2];
+
2825
+
2826 tv[0].tv_sec = 0;
+
2827 tv[1].tv_sec = 0;
+
2828 tv[0].tv_nsec = UTIME_OMIT;
+
2829 tv[1].tv_nsec = UTIME_OMIT;
+
2830
+
2831 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2832 tv[0].tv_nsec = UTIME_NOW;
+
2833 else if (valid & FUSE_SET_ATTR_ATIME)
+
2834 tv[0] = attr->st_atim;
+
2835
+
2836 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2837 tv[1].tv_nsec = UTIME_NOW;
+
2838 else if (valid & FUSE_SET_ATTR_MTIME)
+
2839 tv[1] = attr->st_mtim;
+
2840
+
2841 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2842 } else
+
2843#endif
+
2844 if (!err &&
+
2845 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2846 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2847 struct timespec tv[2];
+
2848 tv[0].tv_sec = attr->st_atime;
+
2849 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2850 tv[1].tv_sec = attr->st_mtime;
+
2851 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2852 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2853 }
+
2854 if (!err) {
+
2855 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2856 }
+
2857 fuse_finish_interrupt(f, req, &d);
+
2858 free_path(f, ino, path);
+
2859 }
+
2860 if (!err) {
+
2861 if (f->conf.auto_cache) {
+
2862 pthread_mutex_lock(&f->lock);
+
2863 update_stat(get_node(f, ino), &buf);
+
2864 pthread_mutex_unlock(&f->lock);
+
2865 }
+
2866 set_stat(f, ino, &buf);
+
2867 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2868 } else
+
2869 reply_err(req, err);
+
2870}
+
2871
+
2872static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2873{
+
2874 struct fuse *f = req_fuse_prepare(req);
+
2875 char *path;
+
2876 int err;
+
2877
+
2878 err = get_path(f, ino, &path);
+
2879 if (!err) {
+
2880 struct fuse_intr_data d;
+
2881
+
2882 fuse_prepare_interrupt(f, req, &d);
+
2883 err = fuse_fs_access(f->fs, path, mask);
+
2884 fuse_finish_interrupt(f, req, &d);
+
2885 free_path(f, ino, path);
+
2886 }
+
2887 reply_err(req, err);
+
2888}
+
2889
+
2890static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2891{
+
2892 struct fuse *f = req_fuse_prepare(req);
+
2893 char linkname[PATH_MAX + 1];
+
2894 char *path;
+
2895 int err;
+
2896
+
2897 err = get_path(f, ino, &path);
+
2898 if (!err) {
+
2899 struct fuse_intr_data d;
+
2900 fuse_prepare_interrupt(f, req, &d);
+
2901 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2902 fuse_finish_interrupt(f, req, &d);
+
2903 free_path(f, ino, path);
+
2904 }
+
2905 if (!err) {
+
2906 linkname[PATH_MAX] = '\0';
+
2907 fuse_reply_readlink(req, linkname);
+
2908 } else
+
2909 reply_err(req, err);
+
2910}
+
2911
+
2912static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2913 mode_t mode, dev_t rdev)
+
2914{
+
2915 struct fuse *f = req_fuse_prepare(req);
+
2916 struct fuse_entry_param e;
+
2917 char *path;
+
2918 int err;
+
2919
+
2920 err = get_path_name(f, parent, name, &path);
+
2921 if (!err) {
+
2922 struct fuse_intr_data d;
+
2923
+
2924 fuse_prepare_interrupt(f, req, &d);
+
2925 err = -ENOSYS;
+
2926 if (S_ISREG(mode)) {
+
2927 struct fuse_file_info fi;
+
2928
+
2929 memset(&fi, 0, sizeof(fi));
+
2930 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2931 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2932 if (!err) {
+
2933 err = lookup_path(f, parent, name, path, &e,
+
2934 &fi);
+
2935 fuse_fs_release(f->fs, path, &fi);
+
2936 }
+
2937 }
+
2938 if (err == -ENOSYS) {
+
2939 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2940 if (!err)
+
2941 err = lookup_path(f, parent, name, path, &e,
+
2942 NULL);
+
2943 }
+
2944 fuse_finish_interrupt(f, req, &d);
+
2945 free_path(f, parent, path);
+
2946 }
+
2947 reply_entry(req, &e, err);
+
2948}
+
2949
+
2950static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2951 mode_t mode)
+
2952{
+
2953 struct fuse *f = req_fuse_prepare(req);
+
2954 struct fuse_entry_param e;
+
2955 char *path;
+
2956 int err;
+
2957
+
2958 err = get_path_name(f, parent, name, &path);
+
2959 if (!err) {
+
2960 struct fuse_intr_data d;
+
2961
+
2962 fuse_prepare_interrupt(f, req, &d);
+
2963 err = fuse_fs_mkdir(f->fs, path, mode);
+
2964 if (!err)
+
2965 err = lookup_path(f, parent, name, path, &e, NULL);
+
2966 fuse_finish_interrupt(f, req, &d);
+
2967 free_path(f, parent, path);
+
2968 }
+
2969 reply_entry(req, &e, err);
+
2970}
+
2971
+
2972static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2973 const char *name)
+
2974{
+
2975 struct fuse *f = req_fuse_prepare(req);
+
2976 struct node *wnode;
+
2977 char *path;
+
2978 int err;
+
2979
+
2980 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
2981 if (!err) {
+
2982 struct fuse_intr_data d;
+
2983
+
2984 fuse_prepare_interrupt(f, req, &d);
+
2985 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
2986 err = hide_node(f, path, parent, name);
+
2987 if (!err) {
+
2988 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
2989 if (!is_open(f, parent, wnode->name)) {
+
2990 char *unlinkpath;
+
2991
+
2992 /* get the hidden file path, to unlink it */
+
2993 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
2994 err = fuse_fs_unlink(f->fs, unlinkpath);
+
2995 if (!err)
+
2996 remove_node(f, parent, wnode->name);
+
2997 free(unlinkpath);
+
2998 }
+
2999 }
+
3000 }
+
3001 } else {
+
3002 err = fuse_fs_unlink(f->fs, path);
+
3003 if (!err)
+
3004 remove_node(f, parent, name);
+
3005 }
+
3006 fuse_finish_interrupt(f, req, &d);
+
3007 free_path_wrlock(f, parent, wnode, path);
+
3008 }
+
3009 reply_err(req, err);
+
3010}
+
3011
+
3012static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3013{
+
3014 struct fuse *f = req_fuse_prepare(req);
+
3015 struct node *wnode;
+
3016 char *path;
+
3017 int err;
+
3018
+
3019 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3020 if (!err) {
+
3021 struct fuse_intr_data d;
+
3022
+
3023 fuse_prepare_interrupt(f, req, &d);
+
3024 err = fuse_fs_rmdir(f->fs, path);
+
3025 fuse_finish_interrupt(f, req, &d);
+
3026 if (!err)
+
3027 remove_node(f, parent, name);
+
3028 free_path_wrlock(f, parent, wnode, path);
+
3029 }
+
3030 reply_err(req, err);
+
3031}
+
3032
+
3033static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3034 fuse_ino_t parent, const char *name)
+
3035{
+
3036 struct fuse *f = req_fuse_prepare(req);
+
3037 struct fuse_entry_param e;
+
3038 char *path;
+
3039 int err;
+
3040
+
3041 err = get_path_name(f, parent, name, &path);
+
3042 if (!err) {
+
3043 struct fuse_intr_data d;
+
3044
+
3045 fuse_prepare_interrupt(f, req, &d);
+
3046 err = fuse_fs_symlink(f->fs, linkname, path);
+
3047 if (!err)
+
3048 err = lookup_path(f, parent, name, path, &e, NULL);
+
3049 fuse_finish_interrupt(f, req, &d);
+
3050 free_path(f, parent, path);
+
3051 }
+
3052 reply_entry(req, &e, err);
+
3053}
+
3054
+
3055static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3056 const char *oldname, fuse_ino_t newdir,
+
3057 const char *newname, unsigned int flags)
+
3058{
+
3059 struct fuse *f = req_fuse_prepare(req);
+
3060 char *oldpath;
+
3061 char *newpath;
+
3062 struct node *wnode1;
+
3063 struct node *wnode2;
+
3064 int err;
+
3065
+
3066 err = get_path2(f, olddir, oldname, newdir, newname,
+
3067 &oldpath, &newpath, &wnode1, &wnode2);
+
3068 if (!err) {
+
3069 struct fuse_intr_data d;
+
3070 err = 0;
+
3071 fuse_prepare_interrupt(f, req, &d);
+
3072 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3073 is_open(f, newdir, newname))
+
3074 err = hide_node(f, newpath, newdir, newname);
+
3075 if (!err) {
+
3076 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3077 if (!err) {
+
3078 if (flags & RENAME_EXCHANGE) {
+
3079 err = exchange_node(f, olddir, oldname,
+
3080 newdir, newname);
+
3081 } else {
+
3082 err = rename_node(f, olddir, oldname,
+
3083 newdir, newname, 0);
+
3084 }
+
3085 }
+
3086 }
+
3087 fuse_finish_interrupt(f, req, &d);
+
3088 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3089 }
+
3090 reply_err(req, err);
+
3091}
+
3092
+
3093static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3094 const char *newname)
+
3095{
+
3096 struct fuse *f = req_fuse_prepare(req);
+
3097 struct fuse_entry_param e;
+
3098 char *oldpath;
+
3099 char *newpath;
+
3100 int err;
+
3101
+
3102 err = get_path2(f, ino, NULL, newparent, newname,
+
3103 &oldpath, &newpath, NULL, NULL);
+
3104 if (!err) {
+
3105 struct fuse_intr_data d;
+
3106
+
3107 fuse_prepare_interrupt(f, req, &d);
+
3108 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3109 if (!err)
+
3110 err = lookup_path(f, newparent, newname, newpath,
+
3111 &e, NULL);
+
3112 fuse_finish_interrupt(f, req, &d);
+
3113 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3114 }
+
3115 reply_entry(req, &e, err);
+
3116}
+
3117
+
3118static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3119 struct fuse_file_info *fi)
+
3120{
+
3121 struct node *node;
+
3122 int unlink_hidden = 0;
+
3123
+
3124 fuse_fs_release(f->fs, path, fi);
+
3125
+
3126 pthread_mutex_lock(&f->lock);
+
3127 node = get_node(f, ino);
+
3128 assert(node->open_count > 0);
+
3129 --node->open_count;
+
3130 if (node->is_hidden && !node->open_count) {
+
3131 unlink_hidden = 1;
+
3132 node->is_hidden = 0;
+
3133 }
+
3134 pthread_mutex_unlock(&f->lock);
+
3135
+
3136 if(unlink_hidden) {
+
3137 if (path) {
+
3138 fuse_fs_unlink(f->fs, path);
+
3139 } else if (f->conf.nullpath_ok) {
+
3140 char *unlinkpath;
+
3141
+
3142 if (get_path(f, ino, &unlinkpath) == 0)
+
3143 fuse_fs_unlink(f->fs, unlinkpath);
+
3144
+
3145 free_path(f, ino, unlinkpath);
+
3146 }
+
3147 }
+
3148}
+
3149
+
3150static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3151 const char *name, mode_t mode,
+
3152 struct fuse_file_info *fi)
+
3153{
+
3154 struct fuse *f = req_fuse_prepare(req);
+
3155 struct fuse_intr_data d;
+
3156 struct fuse_entry_param e;
+
3157 char *path;
+
3158 int err;
+
3159
+
3160 err = get_path_name(f, parent, name, &path);
+
3161 if (!err) {
+
3162 fuse_prepare_interrupt(f, req, &d);
+
3163 err = fuse_fs_create(f->fs, path, mode, fi);
+
3164 if (!err) {
+
3165 err = lookup_path(f, parent, name, path, &e, fi);
+
3166 if (err)
+
3167 fuse_fs_release(f->fs, path, fi);
+
3168 else if (!S_ISREG(e.attr.st_mode)) {
+
3169 err = -EIO;
+
3170 fuse_fs_release(f->fs, path, fi);
+
3171 forget_node(f, e.ino, 1);
+
3172 } else {
+
3173 if (f->conf.direct_io)
+
3174 fi->direct_io = 1;
+
3175 if (f->conf.kernel_cache)
+
3176 fi->keep_cache = 1;
+
3177 if (fi->direct_io &&
+
3178 f->conf.parallel_direct_writes)
+
3179 fi->parallel_direct_writes = 1;
+
3180 }
+
3181 }
+
3182 fuse_finish_interrupt(f, req, &d);
+
3183 }
+
3184 if (!err) {
+
3185 pthread_mutex_lock(&f->lock);
+
3186 get_node(f, e.ino)->open_count++;
+
3187 pthread_mutex_unlock(&f->lock);
+
3188 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3189 /* The open syscall was interrupted, so it
+
3190 must be cancelled */
+
3191 fuse_do_release(f, e.ino, path, fi);
+
3192 forget_node(f, e.ino, 1);
+
3193 }
+
3194 } else {
+
3195 reply_err(req, err);
+
3196 }
+
3197
+
3198 free_path(f, parent, path);
+
3199}
+
3200
+
3201static double diff_timespec(const struct timespec *t1,
+
3202 const struct timespec *t2)
+
3203{
+
3204 return (t1->tv_sec - t2->tv_sec) +
+
3205 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3206}
+
3207
+
3208static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3209 struct fuse_file_info *fi)
+
3210{
+
3211 struct node *node;
+
3212
+
3213 pthread_mutex_lock(&f->lock);
+
3214 node = get_node(f, ino);
+
3215 if (node->cache_valid) {
+
3216 struct timespec now;
+
3217
+
3218 curr_time(&now);
+
3219 if (diff_timespec(&now, &node->stat_updated) >
+
3220 f->conf.ac_attr_timeout) {
+
3221 struct stat stbuf;
+
3222 int err;
+
3223 pthread_mutex_unlock(&f->lock);
+
3224 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3225 pthread_mutex_lock(&f->lock);
+
3226 if (!err)
+
3227 update_stat(node, &stbuf);
+
3228 else
+
3229 node->cache_valid = 0;
+
3230 }
+
3231 }
+
3232 if (node->cache_valid)
+
3233 fi->keep_cache = 1;
+
3234
+
3235 node->cache_valid = 1;
+
3236 pthread_mutex_unlock(&f->lock);
+
3237}
+
3238
+
3239static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3240 struct fuse_file_info *fi)
+
3241{
+
3242 struct fuse *f = req_fuse_prepare(req);
+
3243 struct fuse_intr_data d;
+
3244 char *path;
+
3245 int err;
+
3246
+
3247 err = get_path(f, ino, &path);
+
3248 if (!err) {
+
3249 fuse_prepare_interrupt(f, req, &d);
+
3250 err = fuse_fs_open(f->fs, path, fi);
+
3251 if (!err) {
+
3252 if (f->conf.direct_io)
+
3253 fi->direct_io = 1;
+
3254 if (f->conf.kernel_cache)
+
3255 fi->keep_cache = 1;
+
3256
+
3257 if (f->conf.auto_cache)
+
3258 open_auto_cache(f, ino, path, fi);
+
3259
+
3260 if (f->conf.no_rofd_flush &&
+
3261 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3262 fi->noflush = 1;
+
3263
+
3264 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3265 fi->parallel_direct_writes = 1;
+
3266
+
3267 }
+
3268 fuse_finish_interrupt(f, req, &d);
+
3269 }
+
3270 if (!err) {
+
3271 pthread_mutex_lock(&f->lock);
+
3272 get_node(f, ino)->open_count++;
+
3273 pthread_mutex_unlock(&f->lock);
+
3274 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3275 /* The open syscall was interrupted, so it
+
3276 must be cancelled */
+
3277 fuse_do_release(f, ino, path, fi);
+
3278 }
+
3279 } else
+
3280 reply_err(req, err);
+
3281
+
3282 free_path(f, ino, path);
+
3283}
+
3284
+
3285static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3286 off_t off, struct fuse_file_info *fi)
+
3287{
+
3288 struct fuse *f = req_fuse_prepare(req);
+
3289 struct fuse_bufvec *buf = NULL;
+
3290 char *path;
+
3291 int res;
+
3292
+
3293 res = get_path_nullok(f, ino, &path);
+
3294 if (res == 0) {
+
3295 struct fuse_intr_data d;
+
3296
+
3297 fuse_prepare_interrupt(f, req, &d);
+
3298 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3299 fuse_finish_interrupt(f, req, &d);
+
3300 free_path(f, ino, path);
+
3301 }
+
3302
+
3303 if (res == 0)
+ +
3305 else
+
3306 reply_err(req, res);
+
3307
+
3308 fuse_free_buf(buf);
+
3309}
+
3310
+
3311static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3312 struct fuse_bufvec *buf, off_t off,
+
3313 struct fuse_file_info *fi)
+
3314{
+
3315 struct fuse *f = req_fuse_prepare(req);
+
3316 char *path;
+
3317 int res;
+
3318
+
3319 res = get_path_nullok(f, ino, &path);
+
3320 if (res == 0) {
+
3321 struct fuse_intr_data d;
+
3322
+
3323 fuse_prepare_interrupt(f, req, &d);
+
3324 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3325 fuse_finish_interrupt(f, req, &d);
+
3326 free_path(f, ino, path);
+
3327 }
+
3328
+
3329 if (res >= 0)
+
3330 fuse_reply_write(req, res);
+
3331 else
+
3332 reply_err(req, res);
+
3333}
+
3334
+
3335static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3336 struct fuse_file_info *fi)
+
3337{
+
3338 struct fuse *f = req_fuse_prepare(req);
+
3339 char *path;
+
3340 int err;
+
3341
+
3342 err = get_path_nullok(f, ino, &path);
+
3343 if (!err) {
+
3344 struct fuse_intr_data d;
+
3345
+
3346 fuse_prepare_interrupt(f, req, &d);
+
3347 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3348 fuse_finish_interrupt(f, req, &d);
+
3349 free_path(f, ino, path);
+
3350 }
+
3351 reply_err(req, err);
+
3352}
+
3353
+
3354static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3355 struct fuse_file_info *fi)
+
3356{
+
3357 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3358 memset(fi, 0, sizeof(struct fuse_file_info));
+
3359 fi->fh = dh->fh;
+
3360 return dh;
+
3361}
+
3362
+
3363static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3364 struct fuse_file_info *llfi)
+
3365{
+
3366 struct fuse *f = req_fuse_prepare(req);
+
3367 struct fuse_intr_data d;
+
3368 struct fuse_dh *dh;
+
3369 struct fuse_file_info fi;
+
3370 char *path;
+
3371 int err;
+
3372
+
3373 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3374 if (dh == NULL) {
+
3375 reply_err(req, -ENOMEM);
+
3376 return;
+
3377 }
+
3378 memset(dh, 0, sizeof(struct fuse_dh));
+
3379 dh->fuse = f;
+
3380 dh->contents = NULL;
+
3381 dh->first = NULL;
+
3382 dh->len = 0;
+
3383 dh->filled = 0;
+
3384 dh->nodeid = ino;
+
3385 pthread_mutex_init(&dh->lock, NULL);
+
3386
+
3387 llfi->fh = (uintptr_t) dh;
+
3388
+
3389 memset(&fi, 0, sizeof(fi));
+
3390 fi.flags = llfi->flags;
+
3391
+
3392 err = get_path(f, ino, &path);
+
3393 if (!err) {
+
3394 fuse_prepare_interrupt(f, req, &d);
+
3395 err = fuse_fs_opendir(f->fs, path, &fi);
+
3396 fuse_finish_interrupt(f, req, &d);
+
3397 dh->fh = fi.fh;
+
3398 llfi->cache_readdir = fi.cache_readdir;
+
3399 llfi->keep_cache = fi.keep_cache;
+
3400 }
+
3401 if (!err) {
+
3402 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3403 /* The opendir syscall was interrupted, so it
+
3404 must be cancelled */
+
3405 fuse_fs_releasedir(f->fs, path, &fi);
+
3406 pthread_mutex_destroy(&dh->lock);
+
3407 free(dh);
+
3408 }
+
3409 } else {
+
3410 reply_err(req, err);
+
3411 pthread_mutex_destroy(&dh->lock);
+
3412 free(dh);
+
3413 }
+
3414 free_path(f, ino, path);
+
3415}
+
3416
+
3417static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3418{
+
3419 if (minsize > dh->size) {
+
3420 char *newptr;
+
3421 unsigned newsize = dh->size;
+
3422 if (!newsize)
+
3423 newsize = 1024;
+
3424 while (newsize < minsize) {
+
3425 if (newsize >= 0x80000000)
+
3426 newsize = 0xffffffff;
+
3427 else
+
3428 newsize *= 2;
+
3429 }
+
3430
+
3431 newptr = (char *) realloc(dh->contents, newsize);
+
3432 if (!newptr) {
+
3433 dh->error = -ENOMEM;
+
3434 return -1;
+
3435 }
+
3436 dh->contents = newptr;
+
3437 dh->size = newsize;
+
3438 }
+
3439 return 0;
+
3440}
+
3441
+
3442static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3443 struct stat *st, enum fuse_fill_dir_flags flags)
+
3444{
+
3445 struct fuse_direntry *de;
+
3446
+
3447 de = malloc(sizeof(struct fuse_direntry));
+
3448 if (!de) {
+
3449 dh->error = -ENOMEM;
+
3450 return -1;
+
3451 }
+
3452 de->name = strdup(name);
+
3453 if (!de->name) {
+
3454 dh->error = -ENOMEM;
+
3455 free(de);
+
3456 return -1;
+
3457 }
+
3458 de->flags = flags;
+
3459 de->stat = *st;
+
3460 de->next = NULL;
+
3461
+
3462 *dh->last = de;
+
3463 dh->last = &de->next;
+
3464
+
3465 return 0;
+
3466}
+
3467
+
3468static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3469 const char *name)
+
3470{
+
3471 struct node *node;
+
3472 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3473
+
3474 pthread_mutex_lock(&f->lock);
+
3475 node = lookup_node(f, parent, name);
+
3476 if (node)
+
3477 res = node->nodeid;
+
3478 pthread_mutex_unlock(&f->lock);
+
3479
+
3480 return res;
+
3481}
+
3482
+
3483static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3484 off_t off, enum fuse_fill_dir_flags flags)
+
3485{
+
3486 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3487 struct stat stbuf;
+
3488
+
3489 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3490 dh->error = -EIO;
+
3491 return 1;
+
3492 }
+
3493
+
3494 if (statp)
+
3495 stbuf = *statp;
+
3496 else {
+
3497 memset(&stbuf, 0, sizeof(stbuf));
+
3498 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3499 }
+
3500
+
3501 if (!dh->fuse->conf.use_ino) {
+
3502 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3503 if (dh->fuse->conf.readdir_ino) {
+
3504 stbuf.st_ino = (ino_t)
+
3505 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3506 }
+
3507 }
+
3508
+
3509 if (off) {
+
3510 size_t newlen;
+
3511
+
3512 if (dh->filled) {
+
3513 dh->error = -EIO;
+
3514 return 1;
+
3515 }
+
3516
+
3517 if (dh->first) {
+
3518 dh->error = -EIO;
+
3519 return 1;
+
3520 }
+
3521
+
3522 if (extend_contents(dh, dh->needlen) == -1)
+
3523 return 1;
+
3524
+
3525 newlen = dh->len +
+
3526 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3527 dh->needlen - dh->len, name,
+
3528 &stbuf, off);
+
3529 if (newlen > dh->needlen)
+
3530 return 1;
+
3531
+
3532 dh->len = newlen;
+
3533 } else {
+
3534 dh->filled = 1;
+
3535
+
3536 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3537 return 1;
+
3538 }
+
3539 return 0;
+
3540}
+
3541
+
3542static int is_dot_or_dotdot(const char *name)
+
3543{
+
3544 return name[0] == '.' && (name[1] == '\0' ||
+
3545 (name[1] == '.' && name[2] == '\0'));
+
3546}
+
3547
+
3548static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3549 off_t off, enum fuse_fill_dir_flags flags)
+
3550{
+
3551 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3552 struct fuse_entry_param e = {
+
3553 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3554 .ino = 0,
+
3555 };
+
3556 struct fuse *f = dh->fuse;
+
3557 int res;
+
3558
+
3559 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3560 dh->error = -EIO;
+
3561 return 1;
+
3562 }
+
3563
+
3564 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3565 e.attr = *statp;
+
3566 } else {
+
3567 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3568 if (statp) {
+
3569 e.attr.st_mode = statp->st_mode;
+
3570 if (f->conf.use_ino)
+
3571 e.attr.st_ino = statp->st_ino;
+
3572 }
+
3573 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3574 e.attr.st_ino = (ino_t)
+
3575 lookup_nodeid(f, dh->nodeid, name);
+
3576 }
+
3577 }
+
3578
+
3579 if (off) {
+
3580 size_t newlen;
+
3581
+
3582 if (dh->filled) {
+
3583 dh->error = -EIO;
+
3584 return 1;
+
3585 }
+
3586
+
3587 if (dh->first) {
+
3588 dh->error = -EIO;
+
3589 return 1;
+
3590 }
+
3591 if (extend_contents(dh, dh->needlen) == -1)
+
3592 return 1;
+
3593
+
3594 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3595 if (!is_dot_or_dotdot(name)) {
+
3596 res = do_lookup(f, dh->nodeid, name, &e);
+
3597 if (res) {
+
3598 dh->error = res;
+
3599 return 1;
+
3600 }
+
3601 }
+
3602 }
+
3603
+
3604 newlen = dh->len +
+
3605 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3606 dh->needlen - dh->len, name,
+
3607 &e, off);
+
3608 if (newlen > dh->needlen)
+
3609 return 1;
+
3610 dh->len = newlen;
+
3611 } else {
+
3612 dh->filled = 1;
+
3613
+
3614 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3615 return 1;
+
3616 }
+
3617
+
3618 return 0;
+
3619}
+
3620
+
3621static void free_direntries(struct fuse_direntry *de)
+
3622{
+
3623 while (de) {
+
3624 struct fuse_direntry *next = de->next;
+
3625 free(de->name);
+
3626 free(de);
+
3627 de = next;
+
3628 }
+
3629}
+
3630
+
3631static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3632 size_t size, off_t off, struct fuse_dh *dh,
+
3633 struct fuse_file_info *fi,
+
3634 enum fuse_readdir_flags flags)
+
3635{
+
3636 char *path;
+
3637 int err;
+
3638
+
3639 if (f->fs->op.readdir)
+
3640 err = get_path_nullok(f, ino, &path);
+
3641 else
+
3642 err = get_path(f, ino, &path);
+
3643 if (!err) {
+
3644 struct fuse_intr_data d;
+
3645 fuse_fill_dir_t filler = fill_dir;
+
3646
+
3647 if (flags & FUSE_READDIR_PLUS)
+
3648 filler = fill_dir_plus;
+
3649
+
3650 free_direntries(dh->first);
+
3651 dh->first = NULL;
+
3652 dh->last = &dh->first;
+
3653 dh->len = 0;
+
3654 dh->error = 0;
+
3655 dh->needlen = size;
+
3656 dh->filled = 0;
+
3657 dh->req = req;
+
3658 fuse_prepare_interrupt(f, req, &d);
+
3659 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3660 fuse_finish_interrupt(f, req, &d);
+
3661 dh->req = NULL;
+
3662 if (!err)
+
3663 err = dh->error;
+
3664 if (err)
+
3665 dh->filled = 0;
+
3666 free_path(f, ino, path);
+
3667 }
+
3668 return err;
+
3669}
+
3670
+
3671static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3672 off_t off, enum fuse_readdir_flags flags)
+
3673{
+
3674 off_t pos;
+
3675 struct fuse_direntry *de = dh->first;
+
3676 int res;
+
3677
+
3678 dh->len = 0;
+
3679
+
3680 if (extend_contents(dh, dh->needlen) == -1)
+
3681 return dh->error;
+
3682
+
3683 for (pos = 0; pos < off; pos++) {
+
3684 if (!de)
+
3685 break;
+
3686
+
3687 de = de->next;
+
3688 }
+
3689 while (de) {
+
3690 char *p = dh->contents + dh->len;
+
3691 unsigned rem = dh->needlen - dh->len;
+
3692 unsigned thislen;
+
3693 unsigned newlen;
+
3694 pos++;
+
3695
+
3696 if (flags & FUSE_READDIR_PLUS) {
+
3697 struct fuse_entry_param e = {
+
3698 .ino = 0,
+
3699 .attr = de->stat,
+
3700 };
+
3701
+
3702 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3703 !is_dot_or_dotdot(de->name)) {
+
3704 res = do_lookup(dh->fuse, dh->nodeid,
+
3705 de->name, &e);
+
3706 if (res) {
+
3707 dh->error = res;
+
3708 return 1;
+
3709 }
+
3710 }
+
3711
+
3712 thislen = fuse_add_direntry_plus(req, p, rem,
+
3713 de->name, &e, pos);
+
3714 } else {
+
3715 thislen = fuse_add_direntry(req, p, rem,
+
3716 de->name, &de->stat, pos);
+
3717 }
+
3718 newlen = dh->len + thislen;
+
3719 if (newlen > dh->needlen)
+
3720 break;
+
3721 dh->len = newlen;
+
3722 de = de->next;
+
3723 }
+
3724 return 0;
+
3725}
+
3726
+
3727static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3728 off_t off, struct fuse_file_info *llfi,
+
3729 enum fuse_readdir_flags flags)
+
3730{
+
3731 struct fuse *f = req_fuse_prepare(req);
+
3732 struct fuse_file_info fi;
+
3733 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3734 int err;
+
3735
+
3736 pthread_mutex_lock(&dh->lock);
+
3737 /* According to SUS, directory contents need to be refreshed on
+
3738 rewinddir() */
+
3739 if (!off)
+
3740 dh->filled = 0;
+
3741
+
3742 if (!dh->filled) {
+
3743 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3744 if (err) {
+
3745 reply_err(req, err);
+
3746 goto out;
+
3747 }
+
3748 }
+
3749 if (dh->filled) {
+
3750 dh->needlen = size;
+
3751 err = readdir_fill_from_list(req, dh, off, flags);
+
3752 if (err) {
+
3753 reply_err(req, err);
+
3754 goto out;
+
3755 }
+
3756 }
+
3757 fuse_reply_buf(req, dh->contents, dh->len);
+
3758out:
+
3759 pthread_mutex_unlock(&dh->lock);
+
3760}
+
3761
+
3762static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3763 off_t off, struct fuse_file_info *llfi)
+
3764{
+
3765 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3766}
+
3767
+
3768static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3769 off_t off, struct fuse_file_info *llfi)
+
3770{
+
3771 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3772}
+
3773
+
3774static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3775 struct fuse_file_info *llfi)
+
3776{
+
3777 struct fuse *f = req_fuse_prepare(req);
+
3778 struct fuse_intr_data d;
+
3779 struct fuse_file_info fi;
+
3780 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3781 char *path;
+
3782
+
3783 get_path_nullok(f, ino, &path);
+
3784
+
3785 fuse_prepare_interrupt(f, req, &d);
+
3786 fuse_fs_releasedir(f->fs, path, &fi);
+
3787 fuse_finish_interrupt(f, req, &d);
+
3788 free_path(f, ino, path);
+
3789
+
3790 pthread_mutex_lock(&dh->lock);
+
3791 pthread_mutex_unlock(&dh->lock);
+
3792 pthread_mutex_destroy(&dh->lock);
+
3793 free_direntries(dh->first);
+
3794 free(dh->contents);
+
3795 free(dh);
+
3796 reply_err(req, 0);
+
3797}
+
3798
+
3799static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3800 struct fuse_file_info *llfi)
+
3801{
+
3802 struct fuse *f = req_fuse_prepare(req);
+
3803 struct fuse_file_info fi;
+
3804 char *path;
+
3805 int err;
+
3806
+
3807 get_dirhandle(llfi, &fi);
+
3808
+
3809 err = get_path_nullok(f, ino, &path);
+
3810 if (!err) {
+
3811 struct fuse_intr_data d;
+
3812 fuse_prepare_interrupt(f, req, &d);
+
3813 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3814 fuse_finish_interrupt(f, req, &d);
+
3815 free_path(f, ino, path);
+
3816 }
+
3817 reply_err(req, err);
+
3818}
+
3819
+
3820static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3821{
+
3822 struct fuse *f = req_fuse_prepare(req);
+
3823 struct statvfs buf;
+
3824 char *path = NULL;
+
3825 int err = 0;
+
3826
+
3827 memset(&buf, 0, sizeof(buf));
+
3828 if (ino)
+
3829 err = get_path(f, ino, &path);
+
3830
+
3831 if (!err) {
+
3832 struct fuse_intr_data d;
+
3833 fuse_prepare_interrupt(f, req, &d);
+
3834 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3835 fuse_finish_interrupt(f, req, &d);
+
3836 free_path(f, ino, path);
+
3837 }
+
3838
+
3839 if (!err)
+
3840 fuse_reply_statfs(req, &buf);
+
3841 else
+
3842 reply_err(req, err);
+
3843}
+
3844
+
3845static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3846 const char *value, size_t size, int flags)
+
3847{
+
3848 struct fuse *f = req_fuse_prepare(req);
+
3849 char *path;
+
3850 int err;
+
3851
+
3852 err = get_path(f, ino, &path);
+
3853 if (!err) {
+
3854 struct fuse_intr_data d;
+
3855 fuse_prepare_interrupt(f, req, &d);
+
3856 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3857 fuse_finish_interrupt(f, req, &d);
+
3858 free_path(f, ino, path);
+
3859 }
+
3860 reply_err(req, err);
+
3861}
+
3862
+
3863static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3864 const char *name, char *value, size_t size)
+
3865{
+
3866 int err;
+
3867 char *path;
+
3868
+
3869 err = get_path(f, ino, &path);
+
3870 if (!err) {
+
3871 struct fuse_intr_data d;
+
3872 fuse_prepare_interrupt(f, req, &d);
+
3873 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3874 fuse_finish_interrupt(f, req, &d);
+
3875 free_path(f, ino, path);
+
3876 }
+
3877 return err;
+
3878}
+
3879
+
3880static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3881 size_t size)
+
3882{
+
3883 struct fuse *f = req_fuse_prepare(req);
+
3884 int res;
+
3885
+
3886 if (size) {
+
3887 char *value = (char *) malloc(size);
+
3888 if (value == NULL) {
+
3889 reply_err(req, -ENOMEM);
+
3890 return;
+
3891 }
+
3892 res = common_getxattr(f, req, ino, name, value, size);
+
3893 if (res > 0)
+
3894 fuse_reply_buf(req, value, res);
+
3895 else
+
3896 reply_err(req, res);
+
3897 free(value);
+
3898 } else {
+
3899 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3900 if (res >= 0)
+
3901 fuse_reply_xattr(req, res);
+
3902 else
+
3903 reply_err(req, res);
+
3904 }
+
3905}
+
3906
+
3907static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3908 char *list, size_t size)
+
3909{
+
3910 char *path;
+
3911 int err;
+
3912
+
3913 err = get_path(f, ino, &path);
+
3914 if (!err) {
+
3915 struct fuse_intr_data d;
+
3916 fuse_prepare_interrupt(f, req, &d);
+
3917 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3918 fuse_finish_interrupt(f, req, &d);
+
3919 free_path(f, ino, path);
+
3920 }
+
3921 return err;
+
3922}
+
3923
+
3924static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3925{
+
3926 struct fuse *f = req_fuse_prepare(req);
+
3927 int res;
+
3928
+
3929 if (size) {
+
3930 char *list = (char *) malloc(size);
+
3931 if (list == NULL) {
+
3932 reply_err(req, -ENOMEM);
+
3933 return;
+
3934 }
+
3935 res = common_listxattr(f, req, ino, list, size);
+
3936 if (res > 0)
+
3937 fuse_reply_buf(req, list, res);
+
3938 else
+
3939 reply_err(req, res);
+
3940 free(list);
+
3941 } else {
+
3942 res = common_listxattr(f, req, ino, NULL, 0);
+
3943 if (res >= 0)
+
3944 fuse_reply_xattr(req, res);
+
3945 else
+
3946 reply_err(req, res);
+
3947 }
+
3948}
+
3949
+
3950static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3951 const char *name)
+
3952{
+
3953 struct fuse *f = req_fuse_prepare(req);
+
3954 char *path;
+
3955 int err;
+
3956
+
3957 err = get_path(f, ino, &path);
+
3958 if (!err) {
+
3959 struct fuse_intr_data d;
+
3960 fuse_prepare_interrupt(f, req, &d);
+
3961 err = fuse_fs_removexattr(f->fs, path, name);
+
3962 fuse_finish_interrupt(f, req, &d);
+
3963 free_path(f, ino, path);
+
3964 }
+
3965 reply_err(req, err);
+
3966}
+
3967
+
3968static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3969{
+
3970 struct lock *l;
+
3971
+
3972 for (l = node->locks; l; l = l->next)
+
3973 if (l->owner != lock->owner &&
+
3974 lock->start <= l->end && l->start <= lock->end &&
+
3975 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
3976 break;
+
3977
+
3978 return l;
+
3979}
+
3980
+
3981static void delete_lock(struct lock **lockp)
+
3982{
+
3983 struct lock *l = *lockp;
+
3984 *lockp = l->next;
+
3985 free(l);
+
3986}
+
3987
+
3988static void insert_lock(struct lock **pos, struct lock *lock)
+
3989{
+
3990 lock->next = *pos;
+
3991 *pos = lock;
+
3992}
+
3993
+
3994static int locks_insert(struct node *node, struct lock *lock)
+
3995{
+
3996 struct lock **lp;
+
3997 struct lock *newl1 = NULL;
+
3998 struct lock *newl2 = NULL;
+
3999
+
4000 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4001 lock->end != OFFSET_MAX) {
+
4002 newl1 = malloc(sizeof(struct lock));
+
4003 newl2 = malloc(sizeof(struct lock));
+
4004
+
4005 if (!newl1 || !newl2) {
+
4006 free(newl1);
+
4007 free(newl2);
+
4008 return -ENOLCK;
+
4009 }
+
4010 }
+
4011
+
4012 for (lp = &node->locks; *lp;) {
+
4013 struct lock *l = *lp;
+
4014 if (l->owner != lock->owner)
+
4015 goto skip;
+
4016
+
4017 if (lock->type == l->type) {
+
4018 if (l->end < lock->start - 1)
+
4019 goto skip;
+
4020 if (lock->end < l->start - 1)
+
4021 break;
+
4022 if (l->start <= lock->start && lock->end <= l->end)
+
4023 goto out;
+
4024 if (l->start < lock->start)
+
4025 lock->start = l->start;
+
4026 if (lock->end < l->end)
+
4027 lock->end = l->end;
+
4028 goto delete;
+
4029 } else {
+
4030 if (l->end < lock->start)
+
4031 goto skip;
+
4032 if (lock->end < l->start)
+
4033 break;
+
4034 if (lock->start <= l->start && l->end <= lock->end)
+
4035 goto delete;
+
4036 if (l->end <= lock->end) {
+
4037 l->end = lock->start - 1;
+
4038 goto skip;
+
4039 }
+
4040 if (lock->start <= l->start) {
+
4041 l->start = lock->end + 1;
+
4042 break;
+
4043 }
+
4044 *newl2 = *l;
+
4045 newl2->start = lock->end + 1;
+
4046 l->end = lock->start - 1;
+
4047 insert_lock(&l->next, newl2);
+
4048 newl2 = NULL;
+
4049 }
+
4050 skip:
+
4051 lp = &l->next;
+
4052 continue;
+
4053
+
4054 delete:
+
4055 delete_lock(lp);
+
4056 }
+
4057 if (lock->type != F_UNLCK) {
+
4058 *newl1 = *lock;
+
4059 insert_lock(lp, newl1);
+
4060 newl1 = NULL;
+
4061 }
+
4062out:
+
4063 free(newl1);
+
4064 free(newl2);
+
4065 return 0;
+
4066}
+
4067
+
4068static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4069{
+
4070 memset(lock, 0, sizeof(struct lock));
+
4071 lock->type = flock->l_type;
+
4072 lock->start = flock->l_start;
+
4073 lock->end =
+
4074 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4075 lock->pid = flock->l_pid;
+
4076}
+
4077
+
4078static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4079{
+
4080 flock->l_type = lock->type;
+
4081 flock->l_start = lock->start;
+
4082 flock->l_len =
+
4083 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4084 flock->l_pid = lock->pid;
+
4085}
+
4086
+
4087static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4088 const char *path, struct fuse_file_info *fi)
+
4089{
+
4090 struct fuse_intr_data d;
+
4091 struct flock lock;
+
4092 struct lock l;
+
4093 int err;
+
4094 int errlock;
+
4095
+
4096 fuse_prepare_interrupt(f, req, &d);
+
4097 memset(&lock, 0, sizeof(lock));
+
4098 lock.l_type = F_UNLCK;
+
4099 lock.l_whence = SEEK_SET;
+
4100 err = fuse_fs_flush(f->fs, path, fi);
+
4101 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4102 fuse_finish_interrupt(f, req, &d);
+
4103
+
4104 if (errlock != -ENOSYS) {
+
4105 flock_to_lock(&lock, &l);
+
4106 l.owner = fi->lock_owner;
+
4107 pthread_mutex_lock(&f->lock);
+
4108 locks_insert(get_node(f, ino), &l);
+
4109 pthread_mutex_unlock(&f->lock);
+
4110
+
4111 /* if op.lock() is defined FLUSH is needed regardless
+
4112 of op.flush() */
+
4113 if (err == -ENOSYS)
+
4114 err = 0;
+
4115 }
+
4116 return err;
+
4117}
+
4118
+
4119static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4120 struct fuse_file_info *fi)
+
4121{
+
4122 struct fuse *f = req_fuse_prepare(req);
+
4123 struct fuse_intr_data d;
+
4124 char *path;
+
4125 int err = 0;
+
4126
+
4127 get_path_nullok(f, ino, &path);
+
4128 if (fi->flush) {
+
4129 err = fuse_flush_common(f, req, ino, path, fi);
+
4130 if (err == -ENOSYS)
+
4131 err = 0;
+
4132 }
+
4133
+
4134 fuse_prepare_interrupt(f, req, &d);
+
4135 fuse_do_release(f, ino, path, fi);
+
4136 fuse_finish_interrupt(f, req, &d);
+
4137 free_path(f, ino, path);
+
4138
+
4139 reply_err(req, err);
+
4140}
+
4141
+
4142static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4143 struct fuse_file_info *fi)
+
4144{
+
4145 struct fuse *f = req_fuse_prepare(req);
+
4146 char *path;
+
4147 int err;
+
4148
+
4149 get_path_nullok(f, ino, &path);
+
4150 err = fuse_flush_common(f, req, ino, path, fi);
+
4151 free_path(f, ino, path);
+
4152
+
4153 reply_err(req, err);
+
4154}
+
4155
+
4156static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4157 struct fuse_file_info *fi, struct flock *lock,
+
4158 int cmd)
+
4159{
+
4160 struct fuse *f = req_fuse_prepare(req);
+
4161 char *path;
+
4162 int err;
+
4163
+
4164 err = get_path_nullok(f, ino, &path);
+
4165 if (!err) {
+
4166 struct fuse_intr_data d;
+
4167 fuse_prepare_interrupt(f, req, &d);
+
4168 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4169 fuse_finish_interrupt(f, req, &d);
+
4170 free_path(f, ino, path);
+
4171 }
+
4172 return err;
+
4173}
+
4174
+
4175static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4176 struct fuse_file_info *fi, struct flock *lock)
+
4177{
+
4178 int err;
+
4179 struct lock l;
+
4180 struct lock *conflict;
+
4181 struct fuse *f = req_fuse(req);
+
4182
+
4183 flock_to_lock(lock, &l);
+
4184 l.owner = fi->lock_owner;
+
4185 pthread_mutex_lock(&f->lock);
+
4186 conflict = locks_conflict(get_node(f, ino), &l);
+
4187 if (conflict)
+
4188 lock_to_flock(conflict, lock);
+
4189 pthread_mutex_unlock(&f->lock);
+
4190 if (!conflict)
+
4191 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4192 else
+
4193 err = 0;
+
4194
+
4195 if (!err)
+
4196 fuse_reply_lock(req, lock);
+
4197 else
+
4198 reply_err(req, err);
+
4199}
+
4200
+
4201static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4202 struct fuse_file_info *fi, struct flock *lock,
+
4203 int sleep)
+
4204{
+
4205 int err = fuse_lock_common(req, ino, fi, lock,
+
4206 sleep ? F_SETLKW : F_SETLK);
+
4207 if (!err) {
+
4208 struct fuse *f = req_fuse(req);
+
4209 struct lock l;
+
4210 flock_to_lock(lock, &l);
+
4211 l.owner = fi->lock_owner;
+
4212 pthread_mutex_lock(&f->lock);
+
4213 locks_insert(get_node(f, ino), &l);
+
4214 pthread_mutex_unlock(&f->lock);
+
4215 }
+
4216 reply_err(req, err);
+
4217}
+
4218
+
4219static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4220 struct fuse_file_info *fi, int op)
+
4221{
+
4222 struct fuse *f = req_fuse_prepare(req);
+
4223 char *path;
+
4224 int err;
+
4225
+
4226 err = get_path_nullok(f, ino, &path);
+
4227 if (err == 0) {
+
4228 struct fuse_intr_data d;
+
4229 fuse_prepare_interrupt(f, req, &d);
+
4230 err = fuse_fs_flock(f->fs, path, fi, op);
+
4231 fuse_finish_interrupt(f, req, &d);
+
4232 free_path(f, ino, path);
+
4233 }
+
4234 reply_err(req, err);
+
4235}
+
4236
+
4237static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4238 uint64_t idx)
+
4239{
+
4240 struct fuse *f = req_fuse_prepare(req);
+
4241 struct fuse_intr_data d;
+
4242 char *path;
+
4243 int err;
+
4244
+
4245 err = get_path(f, ino, &path);
+
4246 if (!err) {
+
4247 fuse_prepare_interrupt(f, req, &d);
+
4248 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4249 fuse_finish_interrupt(f, req, &d);
+
4250 free_path(f, ino, path);
+
4251 }
+
4252 if (!err)
+
4253 fuse_reply_bmap(req, idx);
+
4254 else
+
4255 reply_err(req, err);
+
4256}
+
4257
+
4258static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4259 void *arg, struct fuse_file_info *llfi,
+
4260 unsigned int flags, const void *in_buf,
+
4261 size_t in_bufsz, size_t out_bufsz)
+
4262{
+
4263 struct fuse *f = req_fuse_prepare(req);
+
4264 struct fuse_intr_data d;
+
4265 struct fuse_file_info fi;
+
4266 char *path, *out_buf = NULL;
+
4267 int err;
+
4268
+
4269 err = -EPERM;
+
4270 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4271 goto err;
+
4272
+
4273 if (flags & FUSE_IOCTL_DIR)
+
4274 get_dirhandle(llfi, &fi);
+
4275 else
+
4276 fi = *llfi;
+
4277
+
4278 if (out_bufsz) {
+
4279 err = -ENOMEM;
+
4280 out_buf = malloc(out_bufsz);
+
4281 if (!out_buf)
+
4282 goto err;
+
4283 }
+
4284
+
4285 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4286 if (out_buf && in_bufsz)
+
4287 memcpy(out_buf, in_buf, in_bufsz);
+
4288
+
4289 err = get_path_nullok(f, ino, &path);
+
4290 if (err)
+
4291 goto err;
+
4292
+
4293 fuse_prepare_interrupt(f, req, &d);
+
4294
+
4295 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4296 out_buf ? out_buf : (void *)in_buf);
+
4297
+
4298 fuse_finish_interrupt(f, req, &d);
+
4299 free_path(f, ino, path);
+
4300
+
4301 if (err < 0)
+
4302 goto err;
+
4303 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4304 goto out;
+
4305err:
+
4306 reply_err(req, err);
+
4307out:
+
4308 free(out_buf);
+
4309}
+
4310
+
4311static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4312 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4313{
+
4314 struct fuse *f = req_fuse_prepare(req);
+
4315 struct fuse_intr_data d;
+
4316 char *path;
+
4317 int err;
+
4318 unsigned revents = 0;
+
4319
+
4320 err = get_path_nullok(f, ino, &path);
+
4321 if (!err) {
+
4322 fuse_prepare_interrupt(f, req, &d);
+
4323 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4324 fuse_finish_interrupt(f, req, &d);
+
4325 free_path(f, ino, path);
+
4326 }
+
4327 if (!err)
+
4328 fuse_reply_poll(req, revents);
+
4329 else
+
4330 reply_err(req, err);
+
4331}
+
4332
+
4333static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4334 off_t offset, off_t length, struct fuse_file_info *fi)
+
4335{
+
4336 struct fuse *f = req_fuse_prepare(req);
+
4337 struct fuse_intr_data d;
+
4338 char *path;
+
4339 int err;
+
4340
+
4341 err = get_path_nullok(f, ino, &path);
+
4342 if (!err) {
+
4343 fuse_prepare_interrupt(f, req, &d);
+
4344 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4345 fuse_finish_interrupt(f, req, &d);
+
4346 free_path(f, ino, path);
+
4347 }
+
4348 reply_err(req, err);
+
4349}
+
4350
+
4351static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4352 off_t off_in, struct fuse_file_info *fi_in,
+
4353 fuse_ino_t nodeid_out, off_t off_out,
+
4354 struct fuse_file_info *fi_out, size_t len,
+
4355 int flags)
+
4356{
+
4357 struct fuse *f = req_fuse_prepare(req);
+
4358 struct fuse_intr_data d;
+
4359 char *path_in, *path_out;
+
4360 int err;
+
4361 ssize_t res;
+
4362
+
4363 err = get_path_nullok(f, nodeid_in, &path_in);
+
4364 if (err) {
+
4365 reply_err(req, err);
+
4366 return;
+
4367 }
+
4368
+
4369 err = get_path_nullok(f, nodeid_out, &path_out);
+
4370 if (err) {
+
4371 free_path(f, nodeid_in, path_in);
+
4372 reply_err(req, err);
+
4373 return;
+
4374 }
+
4375
+
4376 fuse_prepare_interrupt(f, req, &d);
+
4377 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4378 fi_out, off_out, len, flags);
+
4379 fuse_finish_interrupt(f, req, &d);
+
4380
+
4381 if (res >= 0)
+
4382 fuse_reply_write(req, res);
+
4383 else
+
4384 reply_err(req, res);
+
4385
+
4386 free_path(f, nodeid_in, path_in);
+
4387 free_path(f, nodeid_out, path_out);
+
4388}
+
4389
+
4390static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4391 struct fuse_file_info *fi)
+
4392{
+
4393 struct fuse *f = req_fuse_prepare(req);
+
4394 struct fuse_intr_data d;
+
4395 char *path;
+
4396 int err;
+
4397 off_t res;
+
4398
+
4399 err = get_path(f, ino, &path);
+
4400 if (err) {
+
4401 reply_err(req, err);
+
4402 return;
+
4403 }
+
4404
+
4405 fuse_prepare_interrupt(f, req, &d);
+
4406 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4407 fuse_finish_interrupt(f, req, &d);
+
4408 free_path(f, ino, path);
+
4409 if (res >= 0)
+
4410 fuse_reply_lseek(req, res);
+
4411 else
+
4412 reply_err(req, res);
+
4413}
+
4414
+
4415static int clean_delay(struct fuse *f)
+
4416{
+
4417 /*
+
4418 * This is calculating the delay between clean runs. To
+
4419 * reduce the number of cleans we are doing them 10 times
+
4420 * within the remember window.
+
4421 */
+
4422 int min_sleep = 60;
+
4423 int max_sleep = 3600;
+
4424 int sleep_time = f->conf.remember / 10;
+
4425
+
4426 if (sleep_time > max_sleep)
+
4427 return max_sleep;
+
4428 if (sleep_time < min_sleep)
+
4429 return min_sleep;
+
4430 return sleep_time;
+
4431}
+
4432
+
+
4433int fuse_clean_cache(struct fuse *f)
+
4434{
+
4435 struct node_lru *lnode;
+
4436 struct list_head *curr, *next;
+
4437 struct node *node;
+
4438 struct timespec now;
+
4439
+
4440 pthread_mutex_lock(&f->lock);
+
4441
+
4442 curr_time(&now);
+
4443
+
4444 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4445 double age;
+
4446
+
4447 next = curr->next;
+
4448 lnode = list_entry(curr, struct node_lru, lru);
+
4449 node = &lnode->node;
+
4450
+
4451 age = diff_timespec(&now, &lnode->forget_time);
+
4452 if (age <= f->conf.remember)
+
4453 break;
+
4454
+
4455 assert(node->nlookup == 1);
+
4456
+
4457 /* Don't forget active directories */
+
4458 if (node->refctr > 1)
+
4459 continue;
+
4460
+
4461 node->nlookup = 0;
+
4462 unhash_name(f, node);
+
4463 unref_node(f, node);
+
4464 }
+
4465 pthread_mutex_unlock(&f->lock);
+
4466
+
4467 return clean_delay(f);
+
4468}
+
+
4469
+
4470static struct fuse_lowlevel_ops fuse_path_ops = {
+
4471 .init = fuse_lib_init,
+
4472 .destroy = fuse_lib_destroy,
+
4473 .lookup = fuse_lib_lookup,
+
4474 .forget = fuse_lib_forget,
+
4475 .forget_multi = fuse_lib_forget_multi,
+
4476 .getattr = fuse_lib_getattr,
+
4477 .setattr = fuse_lib_setattr,
+
4478 .access = fuse_lib_access,
+
4479 .readlink = fuse_lib_readlink,
+
4480 .mknod = fuse_lib_mknod,
+
4481 .mkdir = fuse_lib_mkdir,
+
4482 .unlink = fuse_lib_unlink,
+
4483 .rmdir = fuse_lib_rmdir,
+
4484 .symlink = fuse_lib_symlink,
+
4485 .rename = fuse_lib_rename,
+
4486 .link = fuse_lib_link,
+
4487 .create = fuse_lib_create,
+
4488 .open = fuse_lib_open,
+
4489 .read = fuse_lib_read,
+
4490 .write_buf = fuse_lib_write_buf,
+
4491 .flush = fuse_lib_flush,
+
4492 .release = fuse_lib_release,
+
4493 .fsync = fuse_lib_fsync,
+
4494 .opendir = fuse_lib_opendir,
+
4495 .readdir = fuse_lib_readdir,
+
4496 .readdirplus = fuse_lib_readdirplus,
+
4497 .releasedir = fuse_lib_releasedir,
+
4498 .fsyncdir = fuse_lib_fsyncdir,
+
4499 .statfs = fuse_lib_statfs,
+
4500 .setxattr = fuse_lib_setxattr,
+
4501 .getxattr = fuse_lib_getxattr,
+
4502 .listxattr = fuse_lib_listxattr,
+
4503 .removexattr = fuse_lib_removexattr,
+
4504 .getlk = fuse_lib_getlk,
+
4505 .setlk = fuse_lib_setlk,
+
4506 .flock = fuse_lib_flock,
+
4507 .bmap = fuse_lib_bmap,
+
4508 .ioctl = fuse_lib_ioctl,
+
4509 .poll = fuse_lib_poll,
+
4510 .fallocate = fuse_lib_fallocate,
+
4511 .copy_file_range = fuse_lib_copy_file_range,
+
4512 .lseek = fuse_lib_lseek,
+
4513};
+
4514
+
4515int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4516{
+
4517 return fuse_lowlevel_notify_poll(ph);
+
4518}
+
4519
+
+
4520struct fuse_session *fuse_get_session(struct fuse *f)
+
4521{
+
4522 return f->se;
+
4523}
+
+
4524
+
4525static int fuse_session_loop_remember(struct fuse *f)
+
4526{
+
4527 struct fuse_session *se = f->se;
+
4528 int res = 0;
+
4529 struct timespec now;
+
4530 time_t next_clean;
+
4531 struct pollfd fds = {
+
4532 .fd = se->fd,
+
4533 .events = POLLIN
+
4534 };
+
4535 struct fuse_buf fbuf = {
+
4536 .mem = NULL,
+
4537 };
+
4538
+
4539 curr_time(&now);
+
4540 next_clean = now.tv_sec;
+
4541 while (!fuse_session_exited(se)) {
+
4542 unsigned timeout;
+
4543
+
4544 curr_time(&now);
+
4545 if (now.tv_sec < next_clean)
+
4546 timeout = next_clean - now.tv_sec;
+
4547 else
+
4548 timeout = 0;
+
4549
+
4550 res = poll(&fds, 1, timeout * 1000);
+
4551 if (res == -1) {
+
4552 if (errno == EINTR)
+
4553 continue;
+
4554 else
+
4555 break;
+
4556 } else if (res > 0) {
+
4557 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4558 NULL);
+
4559 if (res == -EINTR)
+
4560 continue;
+
4561 if (res <= 0)
+
4562 break;
+
4563
+
4564 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4565 } else {
+
4566 timeout = fuse_clean_cache(f);
+
4567 curr_time(&now);
+
4568 next_clean = now.tv_sec + timeout;
+
4569 }
+
4570 }
+
4571
+
4572 free(fbuf.mem);
+ +
4574 return res < 0 ? -1 : 0;
+
4575}
+
4576
+
+
4577int fuse_loop(struct fuse *f)
+
4578{
+
4579 if (!f)
+
4580 return -1;
+
4581
+
4582 if (lru_enabled(f))
+
4583 return fuse_session_loop_remember(f);
+
4584
+
4585 return fuse_session_loop(f->se);
+
4586}
+
+
4587
+
4588FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4589int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4590{
+
4591 if (f == NULL)
+
4592 return -1;
+
4593
+
4594 int res = fuse_start_cleanup_thread(f);
+
4595 if (res)
+
4596 return -1;
+
4597
+
4598 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4600 return res;
+
4601}
+
4602
+
4603int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4604FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4605int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4606{
+
4607 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4608 if (config == NULL)
+
4609 return ENOMEM;
+
4610
+
4611 fuse_loop_cfg_convert(config, config_v1);
+
4612
+
4613 int res = fuse_loop_mt_312(f, config);
+
4614
+
4615 fuse_loop_cfg_destroy(config);
+
4616
+
4617 return res;
+
4618}
+
4619
+
4620int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4621FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4622int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4623{
+
4624 int err;
+
4625 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4626
+
4627 if (config == NULL)
+
4628 return ENOMEM;
+
4629
+
4630 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4631
+
4632 err = fuse_loop_mt_312(f, config);
+
4633
+
4634 fuse_loop_cfg_destroy(config);
+
4635
+
4636 return err;
+
4637}
+
4638
+
+
4639void fuse_exit(struct fuse *f)
+
4640{
+
4641 fuse_session_exit(f->se);
+
4642}
+
+
4643
+
+ +
4645{
+
4646 struct fuse_context_i *c = fuse_get_context_internal();
+
4647
+
4648 if (c)
+
4649 return &c->ctx;
+
4650 else
+
4651 return NULL;
+
4652}
+
+
4653
+
+
4654int fuse_getgroups(int size, gid_t list[])
+
4655{
+
4656 struct fuse_context_i *c = fuse_get_context_internal();
+
4657 if (!c)
+
4658 return -EINVAL;
+
4659
+
4660 return fuse_req_getgroups(c->req, size, list);
+
4661}
+
+
4662
+
+ +
4664{
+
4665 struct fuse_context_i *c = fuse_get_context_internal();
+
4666
+
4667 if (c)
+
4668 return fuse_req_interrupted(c->req);
+
4669 else
+
4670 return 0;
+
4671}
+
+
4672
+
+
4673int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4674 fuse_ino_t ino;
+
4675 int err = lookup_path_in_cache(f, path, &ino);
+
4676 if (err) {
+
4677 return err;
+
4678 }
+
4679
+
4680 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4681}
+
+
4682
+
4683#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4684
+
4685static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4688 FUSE_LIB_OPT("debug", debug, 1),
+
4689 FUSE_LIB_OPT("-d", debug, 1),
+
4690 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4691 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4692 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4693 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4694 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4695 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4696 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4697 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4698 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4699 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4700 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4701 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4702 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4703 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4704 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4705 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4706 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4707 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4708 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4709 FUSE_LIB_OPT("noforget", remember, -1),
+
4710 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4711 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4712 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4714};
+
4715
+
4716static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4717 struct fuse_args *outargs)
+
4718{
+
4719 (void) arg; (void) outargs; (void) data; (void) key;
+
4720
+
4721 /* Pass through unknown options */
+
4722 return 1;
+
4723}
+
4724
+
4725
+
4726static const struct fuse_opt fuse_help_opts[] = {
+
4727 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4728 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4730};
+
4731
+
4732static void print_module_help(const char *name,
+ +
4734{
+
4735 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4736 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4737 fuse_opt_add_arg(&a, "-h") == -1)
+
4738 return;
+
4739 printf("\nOptions for %s module:\n", name);
+
4740 (*fac)(&a, NULL);
+ +
4742}
+
4743
+
+
4744void fuse_lib_help(struct fuse_args *args)
+
4745{
+
4746 /* These are not all options, but only the ones that
+
4747 may be of interest to an end-user */
+
4748 printf(
+
4749" -o kernel_cache cache files in kernel\n"
+
4750" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4751" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4752" -o umask=M set file permissions (octal)\n"
+
4753" -o fmask=M set file permissions (octal)\n"
+
4754" -o dmask=M set dir permissions (octal)\n"
+
4755" -o uid=N set file owner\n"
+
4756" -o gid=N set file group\n"
+
4757" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4758" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4759" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4760" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4761" -o noforget never forget cached inodes\n"
+
4762" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4763" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4764
+
4765
+
4766 /* Print low-level help */
+ +
4768
+
4769 /* Print help for builtin modules */
+
4770 print_module_help("subdir", &fuse_module_subdir_factory);
+
4771#ifdef HAVE_ICONV
+
4772 print_module_help("iconv", &fuse_module_iconv_factory);
+
4773#endif
+
4774
+
4775 /* Parse command line options in case we need to
+
4776 activate more modules */
+
4777 struct fuse_config conf = { .modules = NULL };
+
4778 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4779 fuse_lib_opt_proc) == -1
+
4780 || !conf.modules)
+
4781 return;
+
4782
+
4783 char *module;
+
4784 char *next;
+
4785 struct fuse_module *m;
+
4786
+
4787 // Iterate over all modules
+
4788 for (module = conf.modules; module; module = next) {
+
4789 char *p;
+
4790 for (p = module; *p && *p != ':'; p++);
+
4791 next = *p ? p + 1 : NULL;
+
4792 *p = '\0';
+
4793
+
4794 m = fuse_get_module(module);
+
4795 if (m)
+
4796 print_module_help(module, &m->factory);
+
4797 }
+
4798}
+
+
4799
+
4800static int fuse_init_intr_signal(int signum, int *installed)
+
4801{
+
4802 struct sigaction old_sa;
+
4803
+
4804 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4805 perror("fuse: cannot get old signal handler");
+
4806 return -1;
+
4807 }
+
4808
+
4809 if (old_sa.sa_handler == SIG_DFL) {
+
4810 struct sigaction sa;
+
4811
+
4812 memset(&sa, 0, sizeof(struct sigaction));
+
4813 sa.sa_handler = fuse_intr_sighandler;
+
4814 sigemptyset(&sa.sa_mask);
+
4815
+
4816 if (sigaction(signum, &sa, NULL) == -1) {
+
4817 perror("fuse: cannot set interrupt signal handler");
+
4818 return -1;
+
4819 }
+
4820 *installed = 1;
+
4821 }
+
4822 return 0;
+
4823}
+
4824
+
4825static void fuse_restore_intr_signal(int signum)
+
4826{
+
4827 struct sigaction sa;
+
4828
+
4829 memset(&sa, 0, sizeof(struct sigaction));
+
4830 sa.sa_handler = SIG_DFL;
+
4831 sigaction(signum, &sa, NULL);
+
4832}
+
4833
+
4834
+
4835static int fuse_push_module(struct fuse *f, const char *module,
+
4836 struct fuse_args *args)
+
4837{
+
4838 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4839 struct fuse_fs *newfs;
+
4840 struct fuse_module *m = fuse_get_module(module);
+
4841
+
4842 if (!m)
+
4843 return -1;
+
4844
+
4845 newfs = m->factory(args, fs);
+
4846 if (!newfs) {
+
4847 fuse_put_module(m);
+
4848 return -1;
+
4849 }
+
4850 f->fs = newfs;
+
4851 return 0;
+
4852}
+
4853
+
+
4854struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4855 void *user_data)
+
4856{
+
4857 struct fuse_fs *fs;
+
4858
+
4859 if (sizeof(struct fuse_operations) < op_size) {
+
4860 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4861 op_size = sizeof(struct fuse_operations);
+
4862 }
+
4863
+
4864 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4865 if (!fs) {
+
4866 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4867 return NULL;
+
4868 }
+
4869
+
4870 fs->user_data = user_data;
+
4871 if (op)
+
4872 memcpy(&fs->op, op, op_size);
+
4873 return fs;
+
4874}
+
+
4875
+
4876static int node_table_init(struct node_table *t)
+
4877{
+
4878 t->size = NODE_TABLE_MIN_SIZE;
+
4879 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4880 if (t->array == NULL) {
+
4881 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4882 return -1;
+
4883 }
+
4884 t->use = 0;
+
4885 t->split = 0;
+
4886
+
4887 return 0;
+
4888}
+
4889
+
4890static void *fuse_prune_nodes(void *fuse)
+
4891{
+
4892 struct fuse *f = fuse;
+
4893 int sleep_time;
+
4894
+
4895 pthread_setname_np(pthread_self(), "fuse_prune_nodes");
+
4896
+
4897 while(1) {
+
4898 sleep_time = fuse_clean_cache(f);
+
4899 sleep(sleep_time);
+
4900 }
+
4901 return NULL;
+
4902}
+
4903
+
+
4904int fuse_start_cleanup_thread(struct fuse *f)
+
4905{
+
4906 if (lru_enabled(f))
+
4907 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4908
+
4909 return 0;
+
4910}
+
+
4911
+
+
4912void fuse_stop_cleanup_thread(struct fuse *f)
+
4913{
+
4914 if (lru_enabled(f)) {
+
4915 pthread_mutex_lock(&f->lock);
+
4916 pthread_cancel(f->prune_thread);
+
4917 pthread_mutex_unlock(&f->lock);
+
4918 pthread_join(f->prune_thread, NULL);
+
4919 }
+
4920}
+
+
4921
+
4922/*
+
4923 * Not supposed to be called directly, but supposed to be called
+
4924 * through the fuse_new macro
+
4925 */
+
4926struct fuse *_fuse_new_31(struct fuse_args *args,
+
4927 const struct fuse_operations *op,
+
4928 size_t op_size, struct libfuse_version *version,
+
4929 void *user_data);
+
4930struct fuse *_fuse_new_31(struct fuse_args *args,
+
4931 const struct fuse_operations *op,
+
4932 size_t op_size, struct libfuse_version *version,
+
4933 void *user_data)
+
4934{
+
4935 struct fuse *f;
+
4936 struct node *root;
+
4937 struct fuse_fs *fs;
+
4938 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4939
+
4940 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4941 if (f == NULL) {
+
4942 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4943 goto out;
+
4944 }
+
4945
+
4946 f->conf.entry_timeout = 1.0;
+
4947 f->conf.attr_timeout = 1.0;
+
4948 f->conf.negative_timeout = 0.0;
+
4949 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
4950
+
4951 /* Parse options */
+
4952 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
4953 fuse_lib_opt_proc) == -1)
+
4954 goto out_free;
+
4955
+
4956 pthread_mutex_lock(&fuse_context_lock);
+
4957 static int builtin_modules_registered = 0;
+
4958 /* Have the builtin modules already been registered? */
+
4959 if (builtin_modules_registered == 0) {
+
4960 /* If not, register them. */
+
4961 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
4962#ifdef HAVE_ICONV
+
4963 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
4964#endif
+
4965 builtin_modules_registered= 1;
+
4966 }
+
4967 pthread_mutex_unlock(&fuse_context_lock);
+
4968
+
4969 if (fuse_create_context_key() == -1)
+
4970 goto out_free;
+
4971
+
4972 fs = fuse_fs_new(op, op_size, user_data);
+
4973 if (!fs)
+
4974 goto out_delete_context_key;
+
4975
+
4976 f->fs = fs;
+
4977
+
4978 /* Oh f**k, this is ugly! */
+
4979 if (!fs->op.lock) {
+
4980 llop.getlk = NULL;
+
4981 llop.setlk = NULL;
+
4982 }
+
4983
+
4984 f->pagesize = getpagesize();
+
4985 init_list_head(&f->partial_slabs);
+
4986 init_list_head(&f->full_slabs);
+
4987 init_list_head(&f->lru_table);
+
4988
+
4989 if (f->conf.modules) {
+
4990 char *module;
+
4991 char *next;
+
4992
+
4993 for (module = f->conf.modules; module; module = next) {
+
4994 char *p;
+
4995 for (p = module; *p && *p != ':'; p++);
+
4996 next = *p ? p + 1 : NULL;
+
4997 *p = '\0';
+
4998 if (module[0] &&
+
4999 fuse_push_module(f, module, args) == -1)
+
5000 goto out_free_fs;
+
5001 }
+
5002 }
+
5003
+
5004 if (!f->conf.ac_attr_timeout_set)
+
5005 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5006
+
5007#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5008 /*
+
5009 * In FreeBSD, we always use these settings as inode numbers
+
5010 * are needed to make getcwd(3) work.
+
5011 */
+
5012 f->conf.readdir_ino = 1;
+
5013#endif
+
5014
+
5015 /* not declared globally, to restrict usage of this function */
+
5016 struct fuse_session *fuse_session_new_versioned(
+
5017 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5018 size_t op_size, struct libfuse_version *version,
+
5019 void *userdata);
+
5020 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5021 f);
+
5022 if (f->se == NULL)
+
5023 goto out_free_fs;
+
5024
+
5025 if (f->conf.debug) {
+
5026 fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok);
+
5027 }
+
5028
+
5029 /* Trace topmost layer by default */
+
5030 f->fs->debug = f->conf.debug;
+
5031 f->ctr = 0;
+
5032 f->generation = 0;
+
5033 if (node_table_init(&f->name_table) == -1)
+
5034 goto out_free_session;
+
5035
+
5036 if (node_table_init(&f->id_table) == -1)
+
5037 goto out_free_name_table;
+
5038
+
5039 pthread_mutex_init(&f->lock, NULL);
+
5040
+
5041 root = alloc_node(f);
+
5042 if (root == NULL) {
+
5043 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5044 goto out_free_id_table;
+
5045 }
+
5046 if (lru_enabled(f)) {
+
5047 struct node_lru *lnode = node_lru(root);
+
5048 init_list_head(&lnode->lru);
+
5049 }
+
5050
+
5051 strcpy(root->inline_name, "/");
+
5052 root->name = root->inline_name;
+
5053 root->parent = NULL;
+
5054 root->nodeid = FUSE_ROOT_ID;
+
5055 inc_nlookup(root);
+
5056 hash_id(f, root);
+
5057
+
5058 return f;
+
5059
+
5060out_free_id_table:
+
5061 free(f->id_table.array);
+
5062out_free_name_table:
+
5063 free(f->name_table.array);
+
5064out_free_session:
+
5065 fuse_session_destroy(f->se);
+
5066out_free_fs:
+
5067 free(f->fs);
+
5068 free(f->conf.modules);
+
5069out_delete_context_key:
+
5070 fuse_delete_context_key();
+
5071out_free:
+
5072 free(f);
+
5073out:
+
5074 return NULL;
+
5075}
+
5076
+
5077/* Emulates 3.0-style fuse_new(), which processes --help */
+
5078struct fuse *_fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5079 size_t op_size,
+
5080 struct libfuse_version *version,
+
5081 void *user_data);
+
5082FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5083struct fuse *_fuse_new_30(struct fuse_args *args,
+
5084 const struct fuse_operations *op,
+
5085 size_t op_size,
+
5086 struct libfuse_version *version,
+
5087 void *user_data)
+
5088{
+
5089 struct fuse_config conf = {0};
+
5090
+
5091 const struct fuse_opt opts[] = {
+
5092 FUSE_LIB_OPT("-h", show_help, 1),
+
5093 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5095 };
+
5096
+
5097 if (fuse_opt_parse(args, &conf, opts,
+
5098 fuse_lib_opt_proc) == -1)
+
5099 return NULL;
+
5100
+
5101 if (conf.show_help) {
+
5102 fuse_lib_help(args);
+
5103 return NULL;
+
5104 } else
+
5105 return _fuse_new_31(args, op, op_size, version, user_data);
+
5106}
+
5107
+
5108/* ABI compat version */
+
5109struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5110 size_t op_size, void *user_data);
+
5111FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5112struct fuse *fuse_new_31(struct fuse_args *args,
+
5113 const struct fuse_operations *op,
+
5114 size_t op_size, void *user_data)
+
5115{
+
5116 /* unknown version */
+
5117 struct libfuse_version version = { 0 };
+
5118
+
5119 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5120}
+
5121
+
5122/*
+
5123 * ABI compat version
+
5124 * Emulates 3.0-style fuse_new(), which processes --help
+
5125 */
+
5126struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5127 size_t op_size, void *user_data);
+
5128FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5129struct fuse *fuse_new_30(struct fuse_args *args,
+
5130 const struct fuse_operations *op,
+
5131 size_t op_size, void *user_data)
+
5132{
+
5133 struct fuse_config conf = {0};
+
5134
+
5135 const struct fuse_opt opts[] = {
+
5136 FUSE_LIB_OPT("-h", show_help, 1),
+
5137 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5139 };
+
5140
+
5141 if (fuse_opt_parse(args, &conf, opts,
+
5142 fuse_lib_opt_proc) == -1)
+
5143 return NULL;
+
5144
+
5145 if (conf.show_help) {
+
5146 fuse_lib_help(args);
+
5147 return NULL;
+
5148 } else
+
5149 return fuse_new_31(args, op, op_size, user_data);
+
5150}
+
5151
+
5152
+
+
5153void fuse_destroy(struct fuse *f)
+
5154{
+
5155 size_t i;
+
5156
+
5157 if (f->conf.intr && f->intr_installed)
+
5158 fuse_restore_intr_signal(f->conf.intr_signal);
+
5159
+
5160 if (f->fs) {
+
5161 fuse_create_context(f);
+
5162
+
5163 for (i = 0; i < f->id_table.size; i++) {
+
5164 struct node *node;
+
5165
+
5166 for (node = f->id_table.array[i]; node != NULL;
+
5167 node = node->id_next) {
+
5168 if (node->is_hidden) {
+
5169 char *path;
+
5170 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5171 fuse_fs_unlink(f->fs, path);
+
5172 free(path);
+
5173 }
+
5174 }
+
5175 }
+
5176 }
+
5177 }
+
5178 for (i = 0; i < f->id_table.size; i++) {
+
5179 struct node *node;
+
5180 struct node *next;
+
5181
+
5182 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5183 next = node->id_next;
+
5184 free_node(f, node);
+
5185 f->id_table.use--;
+
5186 }
+
5187 }
+
5188 assert(list_empty(&f->partial_slabs));
+
5189 assert(list_empty(&f->full_slabs));
+
5190
+
5191 while (fuse_modules) {
+
5192 fuse_put_module(fuse_modules);
+
5193 }
+
5194 free(f->id_table.array);
+
5195 free(f->name_table.array);
+
5196 pthread_mutex_destroy(&f->lock);
+
5197 fuse_session_destroy(f->se);
+
5198 free(f->fs);
+
5199 free(f->conf.modules);
+
5200 free(f);
+
5201 fuse_delete_context_key();
+
5202}
+
+
5203
+
+
5204int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5205 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5206}
+
+
5207
+
5208
+
+
5209void fuse_unmount(struct fuse *f) {
+ +
5211}
+
+
5212
+
+ +
5214{
+
5215 return FUSE_VERSION;
+
5216}
+
+
5217
+
+
5218const char *fuse_pkgversion(void)
+
5219{
+
5220 return PACKAGE_VERSION;
+
5221}
+
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4904
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1403
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4912
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
int fuse_version(void)
Definition fuse.c:5213
+
@ FUSE_BUF_SPLICE_MOVE
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
#define FUSE_CAP_EXPORT_SUPPORT
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ + +
uint32_t no_interrupt
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__i_8h_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..67ca95c --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__i_8h_source.html @@ -0,0 +1,251 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include "fuse.h"
+
10#include "fuse_lowlevel.h"
+
11
+
12#include <stdbool.h>
+
13
+
14#define MIN(a, b) \
+
15({ \
+
16 typeof(a) _a = (a); \
+
17 typeof(b) _b = (b); \
+
18 _a < _b ? _a : _b; \
+
19})
+
20
+
21struct mount_opts;
+
22
+
23struct fuse_req {
+
24 struct fuse_session *se;
+
25 uint64_t unique;
+
26 _Atomic int ref_cnt;
+
27 pthread_mutex_t lock;
+
28 struct fuse_ctx ctx;
+
29 struct fuse_chan *ch;
+
30 int interrupted;
+
31 unsigned int ioctl_64bit : 1;
+
32 union {
+
33 struct {
+
34 uint64_t unique;
+
35 } i;
+
36 struct {
+ +
38 void *data;
+
39 } ni;
+
40 } u;
+
41 struct fuse_req *next;
+
42 struct fuse_req *prev;
+
43};
+
44
+
45struct fuse_notify_req {
+
46 uint64_t unique;
+
47 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
48 const void *, const struct fuse_buf *);
+
49 struct fuse_notify_req *next;
+
50 struct fuse_notify_req *prev;
+
51};
+
52
+
53struct fuse_session {
+
54 char *mountpoint;
+
55 volatile int exited;
+
56 int fd;
+
57 struct fuse_custom_io *io;
+
58 struct mount_opts *mo;
+
59 int debug;
+
60 int deny_others;
+
61 struct fuse_lowlevel_ops op;
+
62 int got_init;
+
63 struct cuse_data *cuse_data;
+
64 void *userdata;
+
65 uid_t owner;
+
66 struct fuse_conn_info conn;
+
67 struct fuse_req list;
+
68 struct fuse_req interrupts;
+
69 pthread_mutex_t lock;
+
70 int got_destroy;
+
71 pthread_key_t pipe_key;
+
72 int broken_splice_nonblock;
+
73 uint64_t notify_ctr;
+
74 struct fuse_notify_req notify_list;
+
75 size_t bufsize;
+
76 int error;
+
77
+
78 /* This is useful if any kind of ABI incompatibility is found at
+
79 * a later version, to 'fix' it at run time.
+
80 */
+
81 struct libfuse_version version;
+
82 bool buf_reallocable;
+
83};
+
84
+
85struct fuse_chan {
+
86 pthread_mutex_t lock;
+
87 int ctr;
+
88 int fd;
+
89};
+
90
+
+ +
99 char *name;
+
100 fuse_module_factory_t factory;
+
101 struct fuse_module *next;
+
102 struct fusemod_so *so;
+
103 int ctr;
+
104};
+
+
105
+
114#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
115struct fuse_loop_config
+
116{
+
117 /* verififier that a correct struct was was passed. This is especially
+
118 * needed, as versions below (3, 12) were using a public struct
+
119 * (now called fuse_loop_config_v1), which was hard to extend with
+
120 * additional parameters, without risking that file system implementations
+
121 * would not have noticed and might either pass uninitialized members
+
122 * or even too small structs.
+
123 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
124 * or 1. v2 or even higher version just need to set a value here
+
125 * which not conflicting and very unlikely as having been set by
+
126 * file system implementation.
+
127 */
+
128 int version_id;
+
129
+
134 int clone_fd;
+ +
147
+
153 unsigned int max_threads;
+
154};
+
155#endif
+
156
+
157/* ----------------------------------------------------------- *
+
158 * Channel interface (when using -o clone_fd) *
+
159 * ----------------------------------------------------------- */
+
160
+
167struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
168
+
174void fuse_chan_put(struct fuse_chan *ch);
+
175
+
176struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
177void destroy_mount_opts(struct mount_opts *mo);
+
178void fuse_mount_version(void);
+
179unsigned get_max_read(struct mount_opts *o);
+
180void fuse_kern_unmount(const char *mountpoint, int fd);
+
181int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
182
+
183int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
184 int count);
+
185void fuse_free_req(fuse_req_t req);
+
186
+
187void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
188
+
189int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
190
+
191void fuse_buf_free(struct fuse_buf *buf);
+
192
+
193int fuse_session_receive_buf_internal(struct fuse_session *se,
+
194 struct fuse_buf *buf,
+
195 struct fuse_chan *ch);
+
196void fuse_session_process_buf_internal(struct fuse_session *se,
+
197 const struct fuse_buf *buf,
+
198 struct fuse_chan *ch);
+
199
+
200struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
201 size_t op_size, void *private_data);
+
202int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
203int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
204
+
210int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
211
+
212
+
213/*
+
214 * This can be changed dynamically on recent kernels through the
+
215 * /proc/sys/fs/fuse/max_pages_limit interface.
+
216 *
+
217 * Older kernels will always use the default value.
+
218 */
+
219#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
220#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
221
+
222/* room needed in buffer to accommodate header */
+
223#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
224
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1403
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + +
unsigned int max_threads
Definition fuse_i.h:153
+
int max_idle_threads
Definition fuse_i.h:146
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__log_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..5161578 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__log_8c_source.html @@ -0,0 +1,170 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdarg.h>
+
14#include <stdio.h>
+
15#include <stdbool.h>
+
16#include <syslog.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(__attribute__((unused)) enum fuse_log_level level,
+
23 const char *fmt, va_list ap)
+
24{
+
25 if (to_syslog) {
+
26 int sys_log_level = LOG_ERR;
+
27
+
28 /*
+
29 * with glibc fuse_log_level has identical values as
+
30 * syslog levels, but we also support BSD - better we convert to
+
31 * be sure.
+
32 */
+
33 switch (level) {
+
34 case FUSE_LOG_DEBUG:
+
35 sys_log_level = LOG_DEBUG;
+
36 break;
+
37 case FUSE_LOG_INFO:
+
38 sys_log_level = LOG_INFO;
+
39 break;
+
40 case FUSE_LOG_NOTICE:
+
41 sys_log_level = LOG_NOTICE;
+
42 break;
+
43 case FUSE_LOG_WARNING:
+
44 sys_log_level = LOG_WARNING;
+
45 break;
+
46 case FUSE_LOG_ERR:
+
47 sys_log_level = LOG_ERR;
+
48 break;
+
49 case FUSE_LOG_CRIT:
+
50 sys_log_level = LOG_CRIT;
+
51 break;
+
52 case FUSE_LOG_ALERT:
+
53 sys_log_level = LOG_ALERT;
+
54 break;
+
55 case FUSE_LOG_EMERG:
+
56 sys_log_level = LOG_EMERG;
+
57 }
+
58
+
59 char log[MAX_SYSLOG_LINE_LEN];
+
60 vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap);
+
61 syslog(sys_log_level, "%s", log);
+
62 } else {
+
63 vfprintf(stderr, fmt, ap);
+
64 }
+
65}
+
66
+
67static fuse_log_func_t log_func = default_log_func;
+
68
+
+ +
70{
+
71 if (!func)
+
72 func = default_log_func;
+
73
+
74 log_func = func;
+
75}
+
+
76
+
+
77void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
78{
+
79 va_list ap;
+
80
+
81 va_start(ap, fmt);
+
82 log_func(level, fmt, ap);
+
83 va_end(ap);
+
84}
+
+
85
+
+
86void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
87{
+
88 to_syslog = true;
+
89
+
90 openlog(ident, option, facility);
+
91}
+
+
92
+
+ +
94{
+
95 closelog();
+
96}
+
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..63ad006 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop_8c_source.html @@ -0,0 +1,114 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+ +
45 return res;
+
46}
+
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_reset(struct fuse_session *se)
+ +
void * mem
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop__mt_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..5f793df --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,599 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <string.h>
+
23#include <unistd.h>
+
24#include <signal.h>
+
25#include <semaphore.h>
+
26#include <errno.h>
+
27#include <sys/time.h>
+
28#include <sys/ioctl.h>
+
29#include <assert.h>
+
30#include <limits.h>
+
31
+
32/* Environment var controlling the thread stack size */
+
33#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
34
+
35#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
36#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
37#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
38#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
39 * by default */
+
40
+
41/* an arbitrary large value that cannot be valid */
+
42#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
43
+
44struct fuse_worker {
+
45 struct fuse_worker *prev;
+
46 struct fuse_worker *next;
+
47 pthread_t thread_id;
+
48
+
49 // We need to include fuse_buf so that we can properly free
+
50 // it when a thread is terminated by pthread_cancel().
+
51 struct fuse_buf fbuf;
+
52 struct fuse_chan *ch;
+
53 struct fuse_mt *mt;
+
54};
+
55
+
56struct fuse_mt {
+
57 pthread_mutex_t lock;
+
58 int numworker;
+
59 int numavail;
+
60 struct fuse_session *se;
+
61 struct fuse_worker main;
+
62 sem_t finish;
+
63 int exit;
+
64 int error;
+
65 int clone_fd;
+
66 int max_idle;
+
67 int max_threads;
+
68};
+
69
+
70static struct fuse_chan *fuse_chan_new(int fd)
+
71{
+
72 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
73 if (ch == NULL) {
+
74 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
75 return NULL;
+
76 }
+
77
+
78 memset(ch, 0, sizeof(*ch));
+
79 ch->fd = fd;
+
80 ch->ctr = 1;
+
81 pthread_mutex_init(&ch->lock, NULL);
+
82
+
83 return ch;
+
84}
+
85
+
86struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
87{
+
88 assert(ch->ctr > 0);
+
89 pthread_mutex_lock(&ch->lock);
+
90 ch->ctr++;
+
91 pthread_mutex_unlock(&ch->lock);
+
92
+
93 return ch;
+
94}
+
95
+
96void fuse_chan_put(struct fuse_chan *ch)
+
97{
+
98 if (ch == NULL)
+
99 return;
+
100 pthread_mutex_lock(&ch->lock);
+
101 ch->ctr--;
+
102 if (!ch->ctr) {
+
103 pthread_mutex_unlock(&ch->lock);
+
104 close(ch->fd);
+
105 pthread_mutex_destroy(&ch->lock);
+
106 free(ch);
+
107 } else
+
108 pthread_mutex_unlock(&ch->lock);
+
109}
+
110
+
111static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
112{
+
113 struct fuse_worker *prev = next->prev;
+
114 w->next = next;
+
115 w->prev = prev;
+
116 prev->next = w;
+
117 next->prev = w;
+
118}
+
119
+
120static void list_del_worker(struct fuse_worker *w)
+
121{
+
122 struct fuse_worker *prev = w->prev;
+
123 struct fuse_worker *next = w->next;
+
124 prev->next = next;
+
125 next->prev = prev;
+
126}
+
127
+
128static int fuse_loop_start_thread(struct fuse_mt *mt);
+
129
+
130static void *fuse_do_work(void *data)
+
131{
+
132 struct fuse_worker *w = (struct fuse_worker *) data;
+
133 struct fuse_mt *mt = w->mt;
+
134
+
135 pthread_setname_np(pthread_self(), "fuse_worker");
+
136
+
137 while (!fuse_session_exited(mt->se)) {
+
138 int isforget = 0;
+
139 int res;
+
140
+
141 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
142 res = fuse_session_receive_buf_internal(mt->se, &w->fbuf,
+
143 w->ch);
+
144 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
145 if (res == -EINTR)
+
146 continue;
+
147 if (res <= 0) {
+
148 if (res < 0) {
+
149 fuse_session_exit(mt->se);
+
150 mt->error = res;
+
151 }
+
152 break;
+
153 }
+
154
+
155 pthread_mutex_lock(&mt->lock);
+
156 if (mt->exit) {
+
157 pthread_mutex_unlock(&mt->lock);
+
158 return NULL;
+
159 }
+
160
+
161 /*
+
162 * This disgusting hack is needed so that zillions of threads
+
163 * are not created on a burst of FORGET messages
+
164 */
+
165 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
166 struct fuse_in_header *in = w->fbuf.mem;
+
167
+
168 if (in->opcode == FUSE_FORGET ||
+
169 in->opcode == FUSE_BATCH_FORGET)
+
170 isforget = 1;
+
171 }
+
172
+
173 if (!isforget)
+
174 mt->numavail--;
+
175 if (mt->numavail == 0 && mt->numworker < mt->max_threads)
+
176 fuse_loop_start_thread(mt);
+
177 pthread_mutex_unlock(&mt->lock);
+
178
+
179 fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch);
+
180
+
181 pthread_mutex_lock(&mt->lock);
+
182 if (!isforget)
+
183 mt->numavail++;
+
184
+
185 /* creating and destroying threads is rather expensive - and there is
+
186 * not much gain from destroying existing threads. It is therefore
+
187 * discouraged to set max_idle to anything else than -1. If there
+
188 * is indeed a good reason to destruct threads it should be done
+
189 * delayed, a moving average might be useful for that.
+
190 */
+
191 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
192 if (mt->exit) {
+
193 pthread_mutex_unlock(&mt->lock);
+
194 return NULL;
+
195 }
+
196 list_del_worker(w);
+
197 mt->numavail--;
+
198 mt->numworker--;
+
199 pthread_mutex_unlock(&mt->lock);
+
200
+
201 pthread_detach(w->thread_id);
+
202 fuse_buf_free(&w->fbuf);
+
203 fuse_chan_put(w->ch);
+
204 free(w);
+
205 return NULL;
+
206 }
+
207 pthread_mutex_unlock(&mt->lock);
+
208 }
+
209
+
210 sem_post(&mt->finish);
+
211
+
212 return NULL;
+
213}
+
214
+
215int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
216{
+
217 sigset_t oldset;
+
218 sigset_t newset;
+
219 int res;
+
220 pthread_attr_t attr;
+
221 char *stack_size;
+
222
+
223 /* Override default stack size
+
224 * XXX: This should ideally be a parameter option. It is rather
+
225 * well hidden here.
+
226 */
+
227 pthread_attr_init(&attr);
+
228 stack_size = getenv(ENVNAME_THREAD_STACK);
+
229 if (stack_size) {
+
230 long size;
+
231
+
232 res = libfuse_strtol(stack_size, &size);
+
233 if (res)
+
234 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
235 stack_size);
+
236 else if (pthread_attr_setstacksize(&attr, size))
+
237 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
238 size);
+
239 }
+
240
+
241 /* Disallow signal reception in worker threads */
+
242 sigemptyset(&newset);
+
243 sigaddset(&newset, SIGTERM);
+
244 sigaddset(&newset, SIGINT);
+
245 sigaddset(&newset, SIGHUP);
+
246 sigaddset(&newset, SIGQUIT);
+
247 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
248 res = pthread_create(thread_id, &attr, func, arg);
+
249 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
250 pthread_attr_destroy(&attr);
+
251 if (res != 0) {
+
252 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
253 strerror(res));
+
254 return -1;
+
255 }
+
256
+
257 return 0;
+
258}
+
259
+
260static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
261{
+
262 int res;
+
263 int clonefd;
+
264 uint32_t masterfd;
+
265 const char *devname = "/dev/fuse";
+
266
+
267#ifndef O_CLOEXEC
+
268#define O_CLOEXEC 0
+
269#endif
+
270 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
271 if (clonefd == -1) {
+
272 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
273 strerror(errno));
+
274 return -1;
+
275 }
+
276#ifndef O_CLOEXEC
+
277 fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
278#endif
+
279
+
280 masterfd = se->fd;
+
281 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
282 if (res == -1) {
+
283 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
284 strerror(errno));
+
285 close(clonefd);
+
286 return -1;
+
287 }
+
288 return clonefd;
+
289}
+
290
+
291static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
292{
+
293 int clonefd;
+
294 struct fuse_session *se = mt->se;
+
295 struct fuse_chan *newch;
+
296
+
297 if (se->io != NULL) {
+
298 if (se->io->clone_fd != NULL)
+
299 clonefd = se->io->clone_fd(se->fd);
+
300 else
+
301 return NULL;
+
302 } else {
+
303 clonefd = fuse_clone_chan_fd_default(se);
+
304 }
+
305 if (clonefd < 0)
+
306 return NULL;
+
307
+
308 newch = fuse_chan_new(clonefd);
+
309 if (newch == NULL)
+
310 close(clonefd);
+
311
+
312 return newch;
+
313}
+
314
+
315static int fuse_loop_start_thread(struct fuse_mt *mt)
+
316{
+
317 int res;
+
318
+
319 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
320 if (!w) {
+
321 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
322 return -1;
+
323 }
+
324 memset(w, 0, sizeof(struct fuse_worker));
+
325 w->fbuf.mem = NULL;
+
326 w->mt = mt;
+
327
+
328 w->ch = NULL;
+
329 if (mt->clone_fd) {
+
330 w->ch = fuse_clone_chan(mt);
+
331 if(!w->ch) {
+
332 /* Don't attempt this again */
+
333 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
334 "without -o clone_fd.\n");
+
335 mt->clone_fd = 0;
+
336 }
+
337 }
+
338
+
339 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
340 if (res == -1) {
+
341 fuse_chan_put(w->ch);
+
342 free(w);
+
343 return -1;
+
344 }
+
345 list_add_worker(w, &mt->main);
+
346 mt->numavail ++;
+
347 mt->numworker ++;
+
348
+
349 return 0;
+
350}
+
351
+
352static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
353{
+
354 pthread_join(w->thread_id, NULL);
+
355 pthread_mutex_lock(&mt->lock);
+
356 list_del_worker(w);
+
357 pthread_mutex_unlock(&mt->lock);
+
358 fuse_buf_free(&w->fbuf);
+
359 fuse_chan_put(w->ch);
+
360 free(w);
+
361}
+
362
+
363int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
364FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
365int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
366{
+
367int err;
+
368 struct fuse_mt mt;
+
369 struct fuse_worker *w;
+
370 int created_config = 0;
+
371
+
372 if (config) {
+
373 err = fuse_loop_cfg_verify(config);
+
374 if (err)
+
375 return err;
+
376 } else {
+
377 /* The caller does not care about parameters - use the default */
+
378 config = fuse_loop_cfg_create();
+
379 created_config = 1;
+
380 }
+
381
+
382
+
383 memset(&mt, 0, sizeof(struct fuse_mt));
+
384 mt.se = se;
+
385 mt.clone_fd = config->clone_fd;
+
386 mt.error = 0;
+
387 mt.numworker = 0;
+
388 mt.numavail = 0;
+
389 mt.max_idle = config->max_idle_threads;
+
390 mt.max_threads = config->max_threads;
+
391 mt.main.thread_id = pthread_self();
+
392 mt.main.prev = mt.main.next = &mt.main;
+
393 sem_init(&mt.finish, 0, 0);
+
394 pthread_mutex_init(&mt.lock, NULL);
+
395
+
396 pthread_mutex_lock(&mt.lock);
+
397 err = fuse_loop_start_thread(&mt);
+
398 pthread_mutex_unlock(&mt.lock);
+
399 if (!err) {
+
400 /* sem_wait() is interruptible */
+
401 while (!fuse_session_exited(se))
+
402 sem_wait(&mt.finish);
+
403
+
404 pthread_mutex_lock(&mt.lock);
+
405 for (w = mt.main.next; w != &mt.main; w = w->next)
+
406 pthread_cancel(w->thread_id);
+
407 mt.exit = 1;
+
408 pthread_mutex_unlock(&mt.lock);
+
409
+
410 while (mt.main.next != &mt.main)
+
411 fuse_join_worker(&mt, mt.main.next);
+
412
+
413 err = mt.error;
+
414 }
+
415
+
416 pthread_mutex_destroy(&mt.lock);
+
417 sem_destroy(&mt.finish);
+
418 if(se->error != 0)
+
419 err = se->error;
+ +
421
+
422 if (created_config) {
+
423 fuse_loop_cfg_destroy(config);
+
424 config = NULL;
+
425 }
+
426
+
427 return err;
+
428}
+
429
+
430int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
431FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
432int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
433{
+
434 int err;
+
435 struct fuse_loop_config *config = NULL;
+
436
+
437 if (config_v1 != NULL) {
+
438 /* convert the given v1 config */
+
439 config = fuse_loop_cfg_create();
+
440 if (config == NULL)
+
441 return ENOMEM;
+
442
+
443 fuse_loop_cfg_convert(config, config_v1);
+
444 }
+
445
+
446 err = fuse_session_loop_mt_312(se, config);
+
447
+
448 fuse_loop_cfg_destroy(config);
+
449
+
450 return err;
+
451}
+
452
+
453
+
454int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
455FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
456int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
457{
+
458 int err;
+
459 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
460 if (clone_fd > 0)
+
461 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
462 err = fuse_session_loop_mt_312(se, config);
+
463
+
464 fuse_loop_cfg_destroy(config);
+
465
+
466 return err;
+
467}
+
468
+
469struct fuse_loop_config *fuse_loop_cfg_create(void)
+
470{
+
471 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
472 if (config == NULL)
+
473 return NULL;
+
474
+
475 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
476 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
477 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
478 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
479
+
480 return config;
+
481}
+
482
+
483void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
484{
+
485 free(config);
+
486}
+
487
+
488int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
489{
+
490 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
491 return -EINVAL;
+
492
+
493 return 0;
+
494}
+
495
+
496void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
497 struct fuse_loop_config_v1 *v1_conf)
+
498{
+
499 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
500
+
501 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
502}
+
503
+
504void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
505 unsigned int value)
+
506{
+
507 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
508 if (value != UINT_MAX)
+
509 fuse_log(FUSE_LOG_ERR,
+
510 "Ignoring invalid max threads value "
+
511 "%u > max (%u).\n", value,
+
512 FUSE_LOOP_MT_MAX_THREADS);
+
513 return;
+
514 }
+
515 config->max_idle_threads = value;
+
516}
+
517
+
518void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
519 unsigned int value)
+
520{
+
521 config->max_threads = value;
+
522}
+
523
+
524void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
525 unsigned int value)
+
526{
+
527 config->clone_fd = value;
+
528}
+
529
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_reset(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:153
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..5129c26 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,3841 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include <stdbool.h>
+
13#define _GNU_SOURCE
+
14
+
15#include "fuse_config.h"
+
16#include "fuse_i.h"
+
17#include "fuse_kernel.h"
+
18#include "fuse_opt.h"
+
19#include "fuse_misc.h"
+
20#include "mount_util.h"
+
21#include "util.h"
+
22
+
23#include <stdio.h>
+
24#include <stdlib.h>
+
25#include <stddef.h>
+
26#include <stdalign.h>
+
27#include <string.h>
+
28#include <unistd.h>
+
29#include <limits.h>
+
30#include <errno.h>
+
31#include <assert.h>
+
32#include <sys/file.h>
+
33#include <sys/ioctl.h>
+
34
+
35#ifndef F_LINUX_SPECIFIC_BASE
+
36#define F_LINUX_SPECIFIC_BASE 1024
+
37#endif
+
38#ifndef F_SETPIPE_SZ
+
39#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
40#endif
+
41
+
42
+
43#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
44#define OFFSET_MAX 0x7fffffffffffffffLL
+
45
+
46#define container_of(ptr, type, member) ({ \
+
47 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
48 (type *)( (char *)__mptr - offsetof(type,member) );})
+
49
+
50struct fuse_pollhandle {
+
51 uint64_t kh;
+
52 struct fuse_session *se;
+
53};
+
54
+
55static size_t pagesize;
+
56
+
57static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
58{
+
59 pagesize = getpagesize();
+
60}
+
61
+
62static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
63{
+
64 attr->ino = stbuf->st_ino;
+
65 attr->mode = stbuf->st_mode;
+
66 attr->nlink = stbuf->st_nlink;
+
67 attr->uid = stbuf->st_uid;
+
68 attr->gid = stbuf->st_gid;
+
69 attr->rdev = stbuf->st_rdev;
+
70 attr->size = stbuf->st_size;
+
71 attr->blksize = stbuf->st_blksize;
+
72 attr->blocks = stbuf->st_blocks;
+
73 attr->atime = stbuf->st_atime;
+
74 attr->mtime = stbuf->st_mtime;
+
75 attr->ctime = stbuf->st_ctime;
+
76 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
77 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
78 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
79}
+
80
+
81static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
82{
+
83 stbuf->st_mode = attr->mode;
+
84 stbuf->st_uid = attr->uid;
+
85 stbuf->st_gid = attr->gid;
+
86 stbuf->st_size = attr->size;
+
87 stbuf->st_atime = attr->atime;
+
88 stbuf->st_mtime = attr->mtime;
+
89 stbuf->st_ctime = attr->ctime;
+
90 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
91 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
92 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
93}
+
94
+
95static size_t iov_length(const struct iovec *iov, size_t count)
+
96{
+
97 size_t seg;
+
98 size_t ret = 0;
+
99
+
100 for (seg = 0; seg < count; seg++)
+
101 ret += iov[seg].iov_len;
+
102 return ret;
+
103}
+
104
+
105static void list_init_req(struct fuse_req *req)
+
106{
+
107 req->next = req;
+
108 req->prev = req;
+
109}
+
110
+
111static void list_del_req(struct fuse_req *req)
+
112{
+
113 struct fuse_req *prev = req->prev;
+
114 struct fuse_req *next = req->next;
+
115 prev->next = next;
+
116 next->prev = prev;
+
117}
+
118
+
119static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
120{
+
121 struct fuse_req *prev = next->prev;
+
122 req->next = next;
+
123 req->prev = prev;
+
124 prev->next = req;
+
125 next->prev = req;
+
126}
+
127
+
128static void destroy_req(fuse_req_t req)
+
129{
+
130 assert(req->ch == NULL);
+
131 pthread_mutex_destroy(&req->lock);
+
132 free(req);
+
133}
+
134
+
135void fuse_free_req(fuse_req_t req)
+
136{
+
137 int ctr;
+
138 struct fuse_session *se = req->se;
+
139
+
140 if (se->conn.no_interrupt) {
+
141 ctr = --req->ref_cnt;
+
142 fuse_chan_put(req->ch);
+
143 req->ch = NULL;
+
144 } else {
+
145 pthread_mutex_lock(&se->lock);
+
146 req->u.ni.func = NULL;
+
147 req->u.ni.data = NULL;
+
148 list_del_req(req);
+
149 ctr = --req->ref_cnt;
+
150 fuse_chan_put(req->ch);
+
151 req->ch = NULL;
+
152 pthread_mutex_unlock(&se->lock);
+
153 }
+
154 if (!ctr)
+
155 destroy_req(req);
+
156}
+
157
+
158static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
159{
+
160 struct fuse_req *req;
+
161
+
162 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
163 if (req == NULL) {
+
164 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
165 } else {
+
166 req->se = se;
+
167 req->ref_cnt = 1;
+
168 list_init_req(req);
+
169 pthread_mutex_init(&req->lock, NULL);
+
170 }
+
171
+
172 return req;
+
173}
+
174
+
175/* Send data. If *ch* is NULL, send via session master fd */
+
176static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
177 struct iovec *iov, int count)
+
178{
+
179 struct fuse_out_header *out = iov[0].iov_base;
+
180
+
181 assert(se != NULL);
+
182 out->len = iov_length(iov, count);
+
183 if (se->debug) {
+
184 if (out->unique == 0) {
+
185 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
186 out->error, out->len);
+
187 } else if (out->error) {
+
188 fuse_log(FUSE_LOG_DEBUG,
+
189 " unique: %llu, error: %i (%s), outsize: %i\n",
+
190 (unsigned long long) out->unique, out->error,
+
191 strerror(-out->error), out->len);
+
192 } else {
+
193 fuse_log(FUSE_LOG_DEBUG,
+
194 " unique: %llu, success, outsize: %i\n",
+
195 (unsigned long long) out->unique, out->len);
+
196 }
+
197 }
+
198
+
199 ssize_t res;
+
200 if (se->io != NULL)
+
201 /* se->io->writev is never NULL if se->io is not NULL as
+
202 specified by fuse_session_custom_io()*/
+
203 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
204 se->userdata);
+
205 else
+
206 res = writev(ch ? ch->fd : se->fd, iov, count);
+
207
+
208 int err = errno;
+
209
+
210 if (res == -1) {
+
211 /* ENOENT means the operation was interrupted */
+
212 if (!fuse_session_exited(se) && err != ENOENT)
+
213 perror("fuse: writing device");
+
214 return -err;
+
215 }
+
216
+
217 return 0;
+
218}
+
219
+
220
+
221int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
222 int count)
+
223{
+
224 struct fuse_out_header out;
+
225
+
226#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
227 const char *str = strerrordesc_np(error * -1);
+
228 if ((str == NULL && error != 0) || error > 0) {
+
229#else
+
230 if (error <= -1000 || error > 0) {
+
231#endif
+
232 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
233 error = -ERANGE;
+
234 }
+
235
+
236 out.unique = req->unique;
+
237 out.error = error;
+
238
+
239 iov[0].iov_base = &out;
+
240 iov[0].iov_len = sizeof(struct fuse_out_header);
+
241
+
242 return fuse_send_msg(req->se, req->ch, iov, count);
+
243}
+
244
+
245static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
246 int count)
+
247{
+
248 int res;
+
249
+
250 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
251 fuse_free_req(req);
+
252 return res;
+
253}
+
254
+
255static int send_reply(fuse_req_t req, int error, const void *arg,
+
256 size_t argsize)
+
257{
+
258 struct iovec iov[2];
+
259 int count = 1;
+
260 if (argsize) {
+
261 iov[1].iov_base = (void *) arg;
+
262 iov[1].iov_len = argsize;
+
263 count++;
+
264 }
+
265 return send_reply_iov(req, error, iov, count);
+
266}
+
267
+
+
268int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
269{
+
270 int res;
+
271 struct iovec *padded_iov;
+
272
+
273 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
274 if (padded_iov == NULL)
+
275 return fuse_reply_err(req, ENOMEM);
+
276
+
277 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
278 count++;
+
279
+
280 res = send_reply_iov(req, 0, padded_iov, count);
+
281 free(padded_iov);
+
282
+
283 return res;
+
284}
+
+
285
+
286
+
287/* `buf` is allowed to be empty so that the proper size may be
+
288 allocated by the caller */
+
+
289size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
290 const char *name, const struct stat *stbuf, off_t off)
+
291{
+
292 (void)req;
+
293 size_t namelen;
+
294 size_t entlen;
+
295 size_t entlen_padded;
+
296 struct fuse_dirent *dirent;
+
297
+
298 namelen = strlen(name);
+
299 entlen = FUSE_NAME_OFFSET + namelen;
+
300 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
301
+
302 if ((buf == NULL) || (entlen_padded > bufsize))
+
303 return entlen_padded;
+
304
+
305 dirent = (struct fuse_dirent*) buf;
+
306 dirent->ino = stbuf->st_ino;
+
307 dirent->off = off;
+
308 dirent->namelen = namelen;
+
309 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
310 memcpy(dirent->name, name, namelen);
+
311 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
312
+
313 return entlen_padded;
+
314}
+
+
315
+
316static void convert_statfs(const struct statvfs *stbuf,
+
317 struct fuse_kstatfs *kstatfs)
+
318{
+
319 kstatfs->bsize = stbuf->f_bsize;
+
320 kstatfs->frsize = stbuf->f_frsize;
+
321 kstatfs->blocks = stbuf->f_blocks;
+
322 kstatfs->bfree = stbuf->f_bfree;
+
323 kstatfs->bavail = stbuf->f_bavail;
+
324 kstatfs->files = stbuf->f_files;
+
325 kstatfs->ffree = stbuf->f_ffree;
+
326 kstatfs->namelen = stbuf->f_namemax;
+
327}
+
328
+
329static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
330{
+
331 return send_reply(req, 0, arg, argsize);
+
332}
+
333
+
+
334int fuse_reply_err(fuse_req_t req, int err)
+
335{
+
336 return send_reply(req, -err, NULL, 0);
+
337}
+
+
338
+
+ +
340{
+
341 fuse_free_req(req);
+
342}
+
+
343
+
344static unsigned long calc_timeout_sec(double t)
+
345{
+
346 if (t > (double) ULONG_MAX)
+
347 return ULONG_MAX;
+
348 else if (t < 0.0)
+
349 return 0;
+
350 else
+
351 return (unsigned long) t;
+
352}
+
353
+
354static unsigned int calc_timeout_nsec(double t)
+
355{
+
356 double f = t - (double) calc_timeout_sec(t);
+
357 if (f < 0.0)
+
358 return 0;
+
359 else if (f >= 0.999999999)
+
360 return 999999999;
+
361 else
+
362 return (unsigned int) (f * 1.0e9);
+
363}
+
364
+
365static void fill_entry(struct fuse_entry_out *arg,
+
366 const struct fuse_entry_param *e)
+
367{
+
368 arg->nodeid = e->ino;
+
369 arg->generation = e->generation;
+
370 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
371 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
372 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
373 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
374 convert_stat(&e->attr, &arg->attr);
+
375}
+
376
+
377/* `buf` is allowed to be empty so that the proper size may be
+
378 allocated by the caller */
+
+
379size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
380 const char *name,
+
381 const struct fuse_entry_param *e, off_t off)
+
382{
+
383 (void)req;
+
384 size_t namelen;
+
385 size_t entlen;
+
386 size_t entlen_padded;
+
387
+
388 namelen = strlen(name);
+
389 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
390 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
391 if ((buf == NULL) || (entlen_padded > bufsize))
+
392 return entlen_padded;
+
393
+
394 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
395 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
396 fill_entry(&dp->entry_out, e);
+
397
+
398 struct fuse_dirent *dirent = &dp->dirent;
+
399 dirent->ino = e->attr.st_ino;
+
400 dirent->off = off;
+
401 dirent->namelen = namelen;
+
402 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
403 memcpy(dirent->name, name, namelen);
+
404 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
405
+
406 return entlen_padded;
+
407}
+
+
408
+
409static void fill_open(struct fuse_open_out *arg,
+
410 const struct fuse_file_info *f)
+
411{
+
412 arg->fh = f->fh;
+
413 if (f->backing_id > 0) {
+
414 arg->backing_id = f->backing_id;
+
415 arg->open_flags |= FOPEN_PASSTHROUGH;
+
416 }
+
417 if (f->direct_io)
+
418 arg->open_flags |= FOPEN_DIRECT_IO;
+
419 if (f->keep_cache)
+
420 arg->open_flags |= FOPEN_KEEP_CACHE;
+
421 if (f->cache_readdir)
+
422 arg->open_flags |= FOPEN_CACHE_DIR;
+
423 if (f->nonseekable)
+
424 arg->open_flags |= FOPEN_NONSEEKABLE;
+
425 if (f->noflush)
+
426 arg->open_flags |= FOPEN_NOFLUSH;
+ +
428 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
429}
+
430
+
+ +
432{
+
433 struct fuse_entry_out arg;
+
434 size_t size = req->se->conn.proto_minor < 9 ?
+
435 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
436
+
437 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
438 negative entry */
+
439 if (!e->ino && req->se->conn.proto_minor < 4)
+
440 return fuse_reply_err(req, ENOENT);
+
441
+
442 memset(&arg, 0, sizeof(arg));
+
443 fill_entry(&arg, e);
+
444 return send_reply_ok(req, &arg, size);
+
445}
+
+
446
+
+ +
448 const struct fuse_file_info *f)
+
449{
+
450 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
451 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
452 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
453 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
454 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
455
+
456 memset(buf, 0, sizeof(buf));
+
457 fill_entry(earg, e);
+
458 fill_open(oarg, f);
+
459 return send_reply_ok(req, buf,
+
460 entrysize + sizeof(struct fuse_open_out));
+
461}
+
+
462
+
+
463int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
464 double attr_timeout)
+
465{
+
466 struct fuse_attr_out arg;
+
467 size_t size = req->se->conn.proto_minor < 9 ?
+
468 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
469
+
470 memset(&arg, 0, sizeof(arg));
+
471 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
472 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
473 convert_stat(attr, &arg.attr);
+
474
+
475 return send_reply_ok(req, &arg, size);
+
476}
+
+
477
+
+
478int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
479{
+
480 return send_reply_ok(req, linkname, strlen(linkname));
+
481}
+
+
482
+
+ +
484{
+
485 struct fuse_backing_map map = { .fd = fd };
+
486 int ret;
+
487
+
488 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
489 if (ret <= 0) {
+
490 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
491 return 0;
+
492 }
+
493
+
494 return ret;
+
495}
+
+
496
+
497int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
498{
+
499 int ret;
+
500
+
501 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
502 if (ret < 0)
+
503 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
504
+
505 return ret;
+
506}
+
507
+
+ +
509{
+
510 struct fuse_open_out arg;
+
511
+
512 memset(&arg, 0, sizeof(arg));
+
513 fill_open(&arg, f);
+
514 return send_reply_ok(req, &arg, sizeof(arg));
+
515}
+
+
516
+
+
517int fuse_reply_write(fuse_req_t req, size_t count)
+
518{
+
519 struct fuse_write_out arg;
+
520
+
521 memset(&arg, 0, sizeof(arg));
+
522 arg.size = count;
+
523
+
524 return send_reply_ok(req, &arg, sizeof(arg));
+
525}
+
+
526
+
+
527int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
528{
+
529 return send_reply_ok(req, buf, size);
+
530}
+
+
531
+
532static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
533 struct fuse_chan *ch,
+
534 struct iovec *iov, int iov_count,
+
535 struct fuse_bufvec *buf,
+
536 size_t len)
+
537{
+
538 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
539 void *mbuf;
+
540 int res;
+
541
+
542 /* Optimize common case */
+
543 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
544 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
545 /* FIXME: also avoid memory copy if there are multiple buffers
+
546 but none of them contain an fd */
+
547
+
548 iov[iov_count].iov_base = buf->buf[0].mem;
+
549 iov[iov_count].iov_len = len;
+
550 iov_count++;
+
551 return fuse_send_msg(se, ch, iov, iov_count);
+
552 }
+
553
+
554 res = posix_memalign(&mbuf, pagesize, len);
+
555 if (res != 0)
+
556 return res;
+
557
+
558 mem_buf.buf[0].mem = mbuf;
+
559 res = fuse_buf_copy(&mem_buf, buf, 0);
+
560 if (res < 0) {
+
561 free(mbuf);
+
562 return -res;
+
563 }
+
564 len = res;
+
565
+
566 iov[iov_count].iov_base = mbuf;
+
567 iov[iov_count].iov_len = len;
+
568 iov_count++;
+
569 res = fuse_send_msg(se, ch, iov, iov_count);
+
570 free(mbuf);
+
571
+
572 return res;
+
573}
+
574
+
575struct fuse_ll_pipe {
+
576 size_t size;
+
577 int can_grow;
+
578 int pipe[2];
+
579};
+
580
+
581static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
582{
+
583 close(llp->pipe[0]);
+
584 close(llp->pipe[1]);
+
585 free(llp);
+
586}
+
587
+
588#ifdef HAVE_SPLICE
+
589#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
590static int fuse_pipe(int fds[2])
+
591{
+
592 int rv = pipe(fds);
+
593
+
594 if (rv == -1)
+
595 return rv;
+
596
+
597 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
598 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
599 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
600 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
601 close(fds[0]);
+
602 close(fds[1]);
+
603 rv = -1;
+
604 }
+
605 return rv;
+
606}
+
607#else
+
608static int fuse_pipe(int fds[2])
+
609{
+
610 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
611}
+
612#endif
+
613
+
614static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
615{
+
616 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
617 if (llp == NULL) {
+
618 int res;
+
619
+
620 llp = malloc(sizeof(struct fuse_ll_pipe));
+
621 if (llp == NULL)
+
622 return NULL;
+
623
+
624 res = fuse_pipe(llp->pipe);
+
625 if (res == -1) {
+
626 free(llp);
+
627 return NULL;
+
628 }
+
629
+
630 /*
+
631 *the default size is 16 pages on linux
+
632 */
+
633 llp->size = pagesize * 16;
+
634 llp->can_grow = 1;
+
635
+
636 pthread_setspecific(se->pipe_key, llp);
+
637 }
+
638
+
639 return llp;
+
640}
+
641#endif
+
642
+
643static void fuse_ll_clear_pipe(struct fuse_session *se)
+
644{
+
645 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
646 if (llp) {
+
647 pthread_setspecific(se->pipe_key, NULL);
+
648 fuse_ll_pipe_free(llp);
+
649 }
+
650}
+
651
+
652#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
653static int read_back(int fd, char *buf, size_t len)
+
654{
+
655 int res;
+
656
+
657 res = read(fd, buf, len);
+
658 if (res == -1) {
+
659 fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+
660 return -EIO;
+
661 }
+
662 if (res != len) {
+
663 fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+
664 return -EIO;
+
665 }
+
666 return 0;
+
667}
+
668
+
669static int grow_pipe_to_max(int pipefd)
+
670{
+
671 int res;
+
672 long max;
+
673 long maxfd;
+
674 char buf[32];
+
675
+
676 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
677 if (maxfd < 0)
+
678 return -errno;
+
679
+
680 res = read(maxfd, buf, sizeof(buf) - 1);
+
681 if (res < 0) {
+
682 int saved_errno;
+
683
+
684 saved_errno = errno;
+
685 close(maxfd);
+
686 return -saved_errno;
+
687 }
+
688 close(maxfd);
+
689 buf[res] = '\0';
+
690
+
691 res = libfuse_strtol(buf, &max);
+
692 if (res)
+
693 return res;
+
694 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
695 if (res < 0)
+
696 return -errno;
+
697 return max;
+
698}
+
699
+
700static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
701 struct iovec *iov, int iov_count,
+
702 struct fuse_bufvec *buf, unsigned int flags)
+
703{
+
704 int res;
+
705 size_t len = fuse_buf_size(buf);
+
706 struct fuse_out_header *out = iov[0].iov_base;
+
707 struct fuse_ll_pipe *llp;
+
708 int splice_flags;
+
709 size_t pipesize;
+
710 size_t total_buf_size;
+
711 size_t idx;
+
712 size_t headerlen;
+
713 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
714
+
715 if (se->broken_splice_nonblock)
+
716 goto fallback;
+
717
+
718 if (flags & FUSE_BUF_NO_SPLICE)
+
719 goto fallback;
+
720
+
721 total_buf_size = 0;
+
722 for (idx = buf->idx; idx < buf->count; idx++) {
+
723 total_buf_size += buf->buf[idx].size;
+
724 if (idx == buf->idx)
+
725 total_buf_size -= buf->off;
+
726 }
+
727 if (total_buf_size < 2 * pagesize)
+
728 goto fallback;
+
729
+
730 if (se->conn.proto_minor < 14 ||
+
731 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
732 goto fallback;
+
733
+
734 llp = fuse_ll_get_pipe(se);
+
735 if (llp == NULL)
+
736 goto fallback;
+
737
+
738
+
739 headerlen = iov_length(iov, iov_count);
+
740
+
741 out->len = headerlen + len;
+
742
+
743 /*
+
744 * Heuristic for the required pipe size, does not work if the
+
745 * source contains less than page size fragments
+
746 */
+
747 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
748
+
749 if (llp->size < pipesize) {
+
750 if (llp->can_grow) {
+
751 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
752 if (res == -1) {
+
753 res = grow_pipe_to_max(llp->pipe[0]);
+
754 if (res > 0)
+
755 llp->size = res;
+
756 llp->can_grow = 0;
+
757 goto fallback;
+
758 }
+
759 llp->size = res;
+
760 }
+
761 if (llp->size < pipesize)
+
762 goto fallback;
+
763 }
+
764
+
765
+
766 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
767 if (res == -1)
+
768 goto fallback;
+
769
+
770 if (res != headerlen) {
+
771 res = -EIO;
+
772 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
773 headerlen);
+
774 goto clear_pipe;
+
775 }
+
776
+
777 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
778 pipe_buf.buf[0].fd = llp->pipe[1];
+
779
+
780 res = fuse_buf_copy(&pipe_buf, buf,
+ +
782 if (res < 0) {
+
783 if (res == -EAGAIN || res == -EINVAL) {
+
784 /*
+
785 * Should only get EAGAIN on kernels with
+
786 * broken SPLICE_F_NONBLOCK support (<=
+
787 * 2.6.35) where this error or a short read is
+
788 * returned even if the pipe itself is not
+
789 * full
+
790 *
+
791 * EINVAL might mean that splice can't handle
+
792 * this combination of input and output.
+
793 */
+
794 if (res == -EAGAIN)
+
795 se->broken_splice_nonblock = 1;
+
796
+
797 pthread_setspecific(se->pipe_key, NULL);
+
798 fuse_ll_pipe_free(llp);
+
799 goto fallback;
+
800 }
+
801 res = -res;
+
802 goto clear_pipe;
+
803 }
+
804
+
805 if (res != 0 && res < len) {
+
806 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
807 void *mbuf;
+
808 size_t now_len = res;
+
809 /*
+
810 * For regular files a short count is either
+
811 * 1) due to EOF, or
+
812 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
813 *
+
814 * For other inputs it's possible that we overflowed
+
815 * the pipe because of small buffer fragments.
+
816 */
+
817
+
818 res = posix_memalign(&mbuf, pagesize, len);
+
819 if (res != 0)
+
820 goto clear_pipe;
+
821
+
822 mem_buf.buf[0].mem = mbuf;
+
823 mem_buf.off = now_len;
+
824 res = fuse_buf_copy(&mem_buf, buf, 0);
+
825 if (res > 0) {
+
826 char *tmpbuf;
+
827 size_t extra_len = res;
+
828 /*
+
829 * Trickiest case: got more data. Need to get
+
830 * back the data from the pipe and then fall
+
831 * back to regular write.
+
832 */
+
833 tmpbuf = malloc(headerlen);
+
834 if (tmpbuf == NULL) {
+
835 free(mbuf);
+
836 res = ENOMEM;
+
837 goto clear_pipe;
+
838 }
+
839 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
840 free(tmpbuf);
+
841 if (res != 0) {
+
842 free(mbuf);
+
843 goto clear_pipe;
+
844 }
+
845 res = read_back(llp->pipe[0], mbuf, now_len);
+
846 if (res != 0) {
+
847 free(mbuf);
+
848 goto clear_pipe;
+
849 }
+
850 len = now_len + extra_len;
+
851 iov[iov_count].iov_base = mbuf;
+
852 iov[iov_count].iov_len = len;
+
853 iov_count++;
+
854 res = fuse_send_msg(se, ch, iov, iov_count);
+
855 free(mbuf);
+
856 return res;
+
857 }
+
858 free(mbuf);
+
859 res = now_len;
+
860 }
+
861 len = res;
+
862 out->len = headerlen + len;
+
863
+
864 if (se->debug) {
+
865 fuse_log(FUSE_LOG_DEBUG,
+
866 " unique: %llu, success, outsize: %i (splice)\n",
+
867 (unsigned long long) out->unique, out->len);
+
868 }
+
869
+
870 splice_flags = 0;
+
871 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
872 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
873 splice_flags |= SPLICE_F_MOVE;
+
874
+
875 if (se->io != NULL && se->io->splice_send != NULL) {
+
876 res = se->io->splice_send(llp->pipe[0], NULL,
+
877 ch ? ch->fd : se->fd, NULL, out->len,
+
878 splice_flags, se->userdata);
+
879 } else {
+
880 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
881 out->len, splice_flags);
+
882 }
+
883 if (res == -1) {
+
884 res = -errno;
+
885 perror("fuse: splice from pipe");
+
886 goto clear_pipe;
+
887 }
+
888 if (res != out->len) {
+
889 res = -EIO;
+
890 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
891 res, out->len);
+
892 goto clear_pipe;
+
893 }
+
894 return 0;
+
895
+
896clear_pipe:
+
897 fuse_ll_clear_pipe(se);
+
898 return res;
+
899
+
900fallback:
+
901 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
902}
+
903#else
+
904static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
905 struct iovec *iov, int iov_count,
+
906 struct fuse_bufvec *buf, unsigned int flags)
+
907{
+
908 size_t len = fuse_buf_size(buf);
+
909 (void) flags;
+
910
+
911 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
912}
+
913#endif
+
914
+
+ +
916 enum fuse_buf_copy_flags flags)
+
917{
+
918 struct iovec iov[2];
+
919 struct fuse_out_header out;
+
920 int res;
+
921
+
922 iov[0].iov_base = &out;
+
923 iov[0].iov_len = sizeof(struct fuse_out_header);
+
924
+
925 out.unique = req->unique;
+
926 out.error = 0;
+
927
+
928 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags);
+
929 if (res <= 0) {
+
930 fuse_free_req(req);
+
931 return res;
+
932 } else {
+
933 return fuse_reply_err(req, res);
+
934 }
+
935}
+
+
936
+
+
937int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
938{
+
939 struct fuse_statfs_out arg;
+
940 size_t size = req->se->conn.proto_minor < 4 ?
+
941 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
942
+
943 memset(&arg, 0, sizeof(arg));
+
944 convert_statfs(stbuf, &arg.st);
+
945
+
946 return send_reply_ok(req, &arg, size);
+
947}
+
+
948
+
+
949int fuse_reply_xattr(fuse_req_t req, size_t count)
+
950{
+
951 struct fuse_getxattr_out arg;
+
952
+
953 memset(&arg, 0, sizeof(arg));
+
954 arg.size = count;
+
955
+
956 return send_reply_ok(req, &arg, sizeof(arg));
+
957}
+
+
958
+
+
959int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
960{
+
961 struct fuse_lk_out arg;
+
962
+
963 memset(&arg, 0, sizeof(arg));
+
964 arg.lk.type = lock->l_type;
+
965 if (lock->l_type != F_UNLCK) {
+
966 arg.lk.start = lock->l_start;
+
967 if (lock->l_len == 0)
+
968 arg.lk.end = OFFSET_MAX;
+
969 else
+
970 arg.lk.end = lock->l_start + lock->l_len - 1;
+
971 }
+
972 arg.lk.pid = lock->l_pid;
+
973 return send_reply_ok(req, &arg, sizeof(arg));
+
974}
+
+
975
+
+
976int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
977{
+
978 struct fuse_bmap_out arg;
+
979
+
980 memset(&arg, 0, sizeof(arg));
+
981 arg.block = idx;
+
982
+
983 return send_reply_ok(req, &arg, sizeof(arg));
+
984}
+
+
985
+
986static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
987 size_t count)
+
988{
+
989 struct fuse_ioctl_iovec *fiov;
+
990 size_t i;
+
991
+
992 fiov = malloc(sizeof(fiov[0]) * count);
+
993 if (!fiov)
+
994 return NULL;
+
995
+
996 for (i = 0; i < count; i++) {
+
997 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
998 fiov[i].len = iov[i].iov_len;
+
999 }
+
1000
+
1001 return fiov;
+
1002}
+
1003
+
+ +
1005 const struct iovec *in_iov, size_t in_count,
+
1006 const struct iovec *out_iov, size_t out_count)
+
1007{
+
1008 struct fuse_ioctl_out arg;
+
1009 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1010 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1011 struct iovec iov[4];
+
1012 size_t count = 1;
+
1013 int res;
+
1014
+
1015 memset(&arg, 0, sizeof(arg));
+
1016 arg.flags |= FUSE_IOCTL_RETRY;
+
1017 arg.in_iovs = in_count;
+
1018 arg.out_iovs = out_count;
+
1019 iov[count].iov_base = &arg;
+
1020 iov[count].iov_len = sizeof(arg);
+
1021 count++;
+
1022
+
1023 if (req->se->conn.proto_minor < 16) {
+
1024 if (in_count) {
+
1025 iov[count].iov_base = (void *)in_iov;
+
1026 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1027 count++;
+
1028 }
+
1029
+
1030 if (out_count) {
+
1031 iov[count].iov_base = (void *)out_iov;
+
1032 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1033 count++;
+
1034 }
+
1035 } else {
+
1036 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1037 if (sizeof(void *) == 4 && req->ioctl_64bit) {
+
1038 res = fuse_reply_err(req, EINVAL);
+
1039 goto out;
+
1040 }
+
1041
+
1042 if (in_count) {
+
1043 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1044 if (!in_fiov)
+
1045 goto enomem;
+
1046
+
1047 iov[count].iov_base = (void *)in_fiov;
+
1048 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1049 count++;
+
1050 }
+
1051 if (out_count) {
+
1052 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1053 if (!out_fiov)
+
1054 goto enomem;
+
1055
+
1056 iov[count].iov_base = (void *)out_fiov;
+
1057 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1058 count++;
+
1059 }
+
1060 }
+
1061
+
1062 res = send_reply_iov(req, 0, iov, count);
+
1063out:
+
1064 free(in_fiov);
+
1065 free(out_fiov);
+
1066
+
1067 return res;
+
1068
+
1069enomem:
+
1070 res = fuse_reply_err(req, ENOMEM);
+
1071 goto out;
+
1072}
+
+
1073
+
+
1074int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1075{
+
1076 struct fuse_ioctl_out arg;
+
1077 struct iovec iov[3];
+
1078 size_t count = 1;
+
1079
+
1080 memset(&arg, 0, sizeof(arg));
+
1081 arg.result = result;
+
1082 iov[count].iov_base = &arg;
+
1083 iov[count].iov_len = sizeof(arg);
+
1084 count++;
+
1085
+
1086 if (size) {
+
1087 iov[count].iov_base = (char *) buf;
+
1088 iov[count].iov_len = size;
+
1089 count++;
+
1090 }
+
1091
+
1092 return send_reply_iov(req, 0, iov, count);
+
1093}
+
+
1094
+
+
1095int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1096 int count)
+
1097{
+
1098 struct iovec *padded_iov;
+
1099 struct fuse_ioctl_out arg;
+
1100 int res;
+
1101
+
1102 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1103 if (padded_iov == NULL)
+
1104 return fuse_reply_err(req, ENOMEM);
+
1105
+
1106 memset(&arg, 0, sizeof(arg));
+
1107 arg.result = result;
+
1108 padded_iov[1].iov_base = &arg;
+
1109 padded_iov[1].iov_len = sizeof(arg);
+
1110
+
1111 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1112
+
1113 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1114 free(padded_iov);
+
1115
+
1116 return res;
+
1117}
+
+
1118
+
+
1119int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1120{
+
1121 struct fuse_poll_out arg;
+
1122
+
1123 memset(&arg, 0, sizeof(arg));
+
1124 arg.revents = revents;
+
1125
+
1126 return send_reply_ok(req, &arg, sizeof(arg));
+
1127}
+
+
1128
+
+
1129int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1130{
+
1131 struct fuse_lseek_out arg;
+
1132
+
1133 memset(&arg, 0, sizeof(arg));
+
1134 arg.offset = off;
+
1135
+
1136 return send_reply_ok(req, &arg, sizeof(arg));
+
1137}
+
+
1138
+
1139static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1140{
+
1141 char *name = (char *) inarg;
+
1142
+
1143 if (req->se->op.lookup)
+
1144 req->se->op.lookup(req, nodeid, name);
+
1145 else
+
1146 fuse_reply_err(req, ENOSYS);
+
1147}
+
1148
+
1149static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1150{
+
1151 struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
1152
+
1153 if (req->se->op.forget)
+
1154 req->se->op.forget(req, nodeid, arg->nlookup);
+
1155 else
+
1156 fuse_reply_none(req);
+
1157}
+
1158
+
1159static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+
1160 const void *inarg)
+
1161{
+
1162 struct fuse_batch_forget_in *arg = (void *) inarg;
+
1163 struct fuse_forget_one *param = (void *) PARAM(arg);
+
1164 unsigned int i;
+
1165
+
1166 (void) nodeid;
+
1167
+
1168 if (req->se->op.forget_multi) {
+
1169 req->se->op.forget_multi(req, arg->count,
+
1170 (struct fuse_forget_data *) param);
+
1171 } else if (req->se->op.forget) {
+
1172 for (i = 0; i < arg->count; i++) {
+
1173 struct fuse_forget_one *forget = &param[i];
+
1174 struct fuse_req *dummy_req;
+
1175
+
1176 dummy_req = fuse_ll_alloc_req(req->se);
+
1177 if (dummy_req == NULL)
+
1178 break;
+
1179
+
1180 dummy_req->unique = req->unique;
+
1181 dummy_req->ctx = req->ctx;
+
1182 dummy_req->ch = NULL;
+
1183
+
1184 req->se->op.forget(dummy_req, forget->nodeid,
+
1185 forget->nlookup);
+
1186 }
+
1187 fuse_reply_none(req);
+
1188 } else {
+
1189 fuse_reply_none(req);
+
1190 }
+
1191}
+
1192
+
1193static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1194{
+
1195 struct fuse_file_info *fip = NULL;
+
1196 struct fuse_file_info fi;
+
1197
+
1198 if (req->se->conn.proto_minor >= 9) {
+
1199 struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
1200
+
1201 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1202 memset(&fi, 0, sizeof(fi));
+
1203 fi.fh = arg->fh;
+
1204 fip = &fi;
+
1205 }
+
1206 }
+
1207
+
1208 if (req->se->op.getattr)
+
1209 req->se->op.getattr(req, nodeid, fip);
+
1210 else
+
1211 fuse_reply_err(req, ENOSYS);
+
1212}
+
1213
+
1214static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1215{
+
1216 struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
1217
+
1218 if (req->se->op.setattr) {
+
1219 struct fuse_file_info *fi = NULL;
+
1220 struct fuse_file_info fi_store;
+
1221 struct stat stbuf;
+
1222 memset(&stbuf, 0, sizeof(stbuf));
+
1223 convert_attr(arg, &stbuf);
+
1224 if (arg->valid & FATTR_FH) {
+
1225 arg->valid &= ~FATTR_FH;
+
1226 memset(&fi_store, 0, sizeof(fi_store));
+
1227 fi = &fi_store;
+
1228 fi->fh = arg->fh;
+
1229 }
+
1230 arg->valid &=
+
1231 FUSE_SET_ATTR_MODE |
+
1232 FUSE_SET_ATTR_UID |
+
1233 FUSE_SET_ATTR_GID |
+
1234 FUSE_SET_ATTR_SIZE |
+
1235 FUSE_SET_ATTR_ATIME |
+
1236 FUSE_SET_ATTR_MTIME |
+
1237 FUSE_SET_ATTR_KILL_SUID |
+
1238 FUSE_SET_ATTR_KILL_SGID |
+
1239 FUSE_SET_ATTR_ATIME_NOW |
+
1240 FUSE_SET_ATTR_MTIME_NOW |
+
1241 FUSE_SET_ATTR_CTIME;
+
1242
+
1243 req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+
1244 } else
+
1245 fuse_reply_err(req, ENOSYS);
+
1246}
+
1247
+
1248static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1249{
+
1250 struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
1251
+
1252 if (req->se->op.access)
+
1253 req->se->op.access(req, nodeid, arg->mask);
+
1254 else
+
1255 fuse_reply_err(req, ENOSYS);
+
1256}
+
1257
+
1258static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1259{
+
1260 (void) inarg;
+
1261
+
1262 if (req->se->op.readlink)
+
1263 req->se->op.readlink(req, nodeid);
+
1264 else
+
1265 fuse_reply_err(req, ENOSYS);
+
1266}
+
1267
+
1268static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1269{
+
1270 struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+
1271 char *name = PARAM(arg);
+
1272
+
1273 if (req->se->conn.proto_minor >= 12)
+
1274 req->ctx.umask = arg->umask;
+
1275 else
+
1276 name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1277
+
1278 if (req->se->op.mknod)
+
1279 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1280 else
+
1281 fuse_reply_err(req, ENOSYS);
+
1282}
+
1283
+
1284static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1285{
+
1286 struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
1287
+
1288 if (req->se->conn.proto_minor >= 12)
+
1289 req->ctx.umask = arg->umask;
+
1290
+
1291 if (req->se->op.mkdir)
+
1292 req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+
1293 else
+
1294 fuse_reply_err(req, ENOSYS);
+
1295}
+
1296
+
1297static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1298{
+
1299 char *name = (char *) inarg;
+
1300
+
1301 if (req->se->op.unlink)
+
1302 req->se->op.unlink(req, nodeid, name);
+
1303 else
+
1304 fuse_reply_err(req, ENOSYS);
+
1305}
+
1306
+
1307static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1308{
+
1309 char *name = (char *) inarg;
+
1310
+
1311 if (req->se->op.rmdir)
+
1312 req->se->op.rmdir(req, nodeid, name);
+
1313 else
+
1314 fuse_reply_err(req, ENOSYS);
+
1315}
+
1316
+
1317static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1318{
+
1319 char *name = (char *) inarg;
+
1320 char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
1321
+
1322 if (req->se->op.symlink)
+
1323 req->se->op.symlink(req, linkname, nodeid, name);
+
1324 else
+
1325 fuse_reply_err(req, ENOSYS);
+
1326}
+
1327
+
1328static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1329{
+
1330 struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+
1331 char *oldname = PARAM(arg);
+
1332 char *newname = oldname + strlen(oldname) + 1;
+
1333
+
1334 if (req->se->op.rename)
+
1335 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1336 0);
+
1337 else
+
1338 fuse_reply_err(req, ENOSYS);
+
1339}
+
1340
+
1341static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1342{
+
1343 struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg;
+
1344 char *oldname = PARAM(arg);
+
1345 char *newname = oldname + strlen(oldname) + 1;
+
1346
+
1347 if (req->se->op.rename)
+
1348 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1349 arg->flags);
+
1350 else
+
1351 fuse_reply_err(req, ENOSYS);
+
1352}
+
1353
+
1354static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1355{
+
1356 struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
1357
+
1358 if (req->se->op.link)
+
1359 req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+
1360 else
+
1361 fuse_reply_err(req, ENOSYS);
+
1362}
+
1363
+
1364static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1365{
+
1366 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1367
+
1368 if (req->se->op.tmpfile) {
+
1369 struct fuse_file_info fi;
+
1370
+
1371 memset(&fi, 0, sizeof(fi));
+
1372 fi.flags = arg->flags;
+
1373
+
1374 if (req->se->conn.proto_minor >= 12)
+
1375 req->ctx.umask = arg->umask;
+
1376
+
1377 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1378 } else
+
1379 fuse_reply_err(req, ENOSYS);
+
1380}
+
1381
+
1382static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1383{
+
1384 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1385
+
1386 if (req->se->op.create) {
+
1387 struct fuse_file_info fi;
+
1388 char *name = PARAM(arg);
+
1389
+
1390 memset(&fi, 0, sizeof(fi));
+
1391 fi.flags = arg->flags;
+
1392
+
1393 if (req->se->conn.proto_minor >= 12)
+
1394 req->ctx.umask = arg->umask;
+
1395 else
+
1396 name = (char *) inarg + sizeof(struct fuse_open_in);
+
1397
+
1398 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1399 } else
+
1400 fuse_reply_err(req, ENOSYS);
+
1401}
+
1402
+
1403static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1404{
+
1405 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1406 struct fuse_file_info fi;
+
1407
+
1408 memset(&fi, 0, sizeof(fi));
+
1409 fi.flags = arg->flags;
+
1410
+
1411 if (req->se->op.open)
+
1412 req->se->op.open(req, nodeid, &fi);
+
1413 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1414 fuse_reply_err(req, ENOSYS);
+
1415 else
+
1416 fuse_reply_open(req, &fi);
+
1417}
+
1418
+
1419static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1420{
+
1421 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1422
+
1423 if (req->se->op.read) {
+
1424 struct fuse_file_info fi;
+
1425
+
1426 memset(&fi, 0, sizeof(fi));
+
1427 fi.fh = arg->fh;
+
1428 if (req->se->conn.proto_minor >= 9) {
+
1429 fi.lock_owner = arg->lock_owner;
+
1430 fi.flags = arg->flags;
+
1431 }
+
1432 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1433 } else
+
1434 fuse_reply_err(req, ENOSYS);
+
1435}
+
1436
+
1437static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1438{
+
1439 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1440 struct fuse_file_info fi;
+
1441 char *param;
+
1442
+
1443 memset(&fi, 0, sizeof(fi));
+
1444 fi.fh = arg->fh;
+
1445 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1446
+
1447 if (req->se->conn.proto_minor < 9) {
+
1448 param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1449 } else {
+
1450 fi.lock_owner = arg->lock_owner;
+
1451 fi.flags = arg->flags;
+
1452 param = PARAM(arg);
+
1453 }
+
1454
+
1455 if (req->se->op.write)
+
1456 req->se->op.write(req, nodeid, param, arg->size,
+
1457 arg->offset, &fi);
+
1458 else
+
1459 fuse_reply_err(req, ENOSYS);
+
1460}
+
1461
+
1462static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+
1463 const struct fuse_buf *ibuf)
+
1464{
+
1465 struct fuse_session *se = req->se;
+
1466 struct fuse_bufvec bufv = {
+
1467 .buf[0] = *ibuf,
+
1468 .count = 1,
+
1469 };
+
1470 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1471 struct fuse_file_info fi;
+
1472
+
1473 memset(&fi, 0, sizeof(fi));
+
1474 fi.fh = arg->fh;
+
1475 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1476
+
1477 if (se->conn.proto_minor < 9) {
+
1478 bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1479 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1480 FUSE_COMPAT_WRITE_IN_SIZE;
+
1481 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1482 } else {
+
1483 fi.lock_owner = arg->lock_owner;
+
1484 fi.flags = arg->flags;
+
1485 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1486 bufv.buf[0].mem = PARAM(arg);
+
1487
+
1488 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1489 sizeof(struct fuse_write_in);
+
1490 }
+
1491 if (bufv.buf[0].size < arg->size) {
+
1492 fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n");
+
1493 fuse_reply_err(req, EIO);
+
1494 goto out;
+
1495 }
+
1496 bufv.buf[0].size = arg->size;
+
1497
+
1498 se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
1499
+
1500out:
+
1501 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1502 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1503 fuse_ll_clear_pipe(se);
+
1504}
+
1505
+
1506static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1507{
+
1508 struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+
1509 struct fuse_file_info fi;
+
1510
+
1511 memset(&fi, 0, sizeof(fi));
+
1512 fi.fh = arg->fh;
+
1513 fi.flush = 1;
+
1514 if (req->se->conn.proto_minor >= 7)
+
1515 fi.lock_owner = arg->lock_owner;
+
1516
+
1517 if (req->se->op.flush)
+
1518 req->se->op.flush(req, nodeid, &fi);
+
1519 else
+
1520 fuse_reply_err(req, ENOSYS);
+
1521}
+
1522
+
1523static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1524{
+
1525 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1526 struct fuse_file_info fi;
+
1527
+
1528 memset(&fi, 0, sizeof(fi));
+
1529 fi.flags = arg->flags;
+
1530 fi.fh = arg->fh;
+
1531 if (req->se->conn.proto_minor >= 8) {
+
1532 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1533 fi.lock_owner = arg->lock_owner;
+
1534 }
+
1535 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1536 fi.flock_release = 1;
+
1537 fi.lock_owner = arg->lock_owner;
+
1538 }
+
1539
+
1540 if (req->se->op.release)
+
1541 req->se->op.release(req, nodeid, &fi);
+
1542 else
+
1543 fuse_reply_err(req, 0);
+
1544}
+
1545
+
1546static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1547{
+
1548 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1549 struct fuse_file_info fi;
+
1550 int datasync = arg->fsync_flags & 1;
+
1551
+
1552 memset(&fi, 0, sizeof(fi));
+
1553 fi.fh = arg->fh;
+
1554
+
1555 if (req->se->op.fsync)
+
1556 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1557 else
+
1558 fuse_reply_err(req, ENOSYS);
+
1559}
+
1560
+
1561static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1562{
+
1563 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1564 struct fuse_file_info fi;
+
1565
+
1566 memset(&fi, 0, sizeof(fi));
+
1567 fi.flags = arg->flags;
+
1568
+
1569 if (req->se->op.opendir)
+
1570 req->se->op.opendir(req, nodeid, &fi);
+
1571 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1572 fuse_reply_err(req, ENOSYS);
+
1573 else
+
1574 fuse_reply_open(req, &fi);
+
1575}
+
1576
+
1577static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1578{
+
1579 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1580 struct fuse_file_info fi;
+
1581
+
1582 memset(&fi, 0, sizeof(fi));
+
1583 fi.fh = arg->fh;
+
1584
+
1585 if (req->se->op.readdir)
+
1586 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1587 else
+
1588 fuse_reply_err(req, ENOSYS);
+
1589}
+
1590
+
1591static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1592{
+
1593 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1594 struct fuse_file_info fi;
+
1595
+
1596 memset(&fi, 0, sizeof(fi));
+
1597 fi.fh = arg->fh;
+
1598
+
1599 if (req->se->op.readdirplus)
+
1600 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1601 else
+
1602 fuse_reply_err(req, ENOSYS);
+
1603}
+
1604
+
1605static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1606{
+
1607 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1608 struct fuse_file_info fi;
+
1609
+
1610 memset(&fi, 0, sizeof(fi));
+
1611 fi.flags = arg->flags;
+
1612 fi.fh = arg->fh;
+
1613
+
1614 if (req->se->op.releasedir)
+
1615 req->se->op.releasedir(req, nodeid, &fi);
+
1616 else
+
1617 fuse_reply_err(req, 0);
+
1618}
+
1619
+
1620static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1621{
+
1622 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1623 struct fuse_file_info fi;
+
1624 int datasync = arg->fsync_flags & 1;
+
1625
+
1626 memset(&fi, 0, sizeof(fi));
+
1627 fi.fh = arg->fh;
+
1628
+
1629 if (req->se->op.fsyncdir)
+
1630 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
1631 else
+
1632 fuse_reply_err(req, ENOSYS);
+
1633}
+
1634
+
1635static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1636{
+
1637 (void) nodeid;
+
1638 (void) inarg;
+
1639
+
1640 if (req->se->op.statfs)
+
1641 req->se->op.statfs(req, nodeid);
+
1642 else {
+
1643 struct statvfs buf = {
+
1644 .f_namemax = 255,
+
1645 .f_bsize = 512,
+
1646 };
+
1647 fuse_reply_statfs(req, &buf);
+
1648 }
+
1649}
+
1650
+
1651static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1652{
+
1653 struct fuse_session *se = req->se;
+
1654 unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT);
+
1655 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+
1656 char *name = xattr_ext ? PARAM(arg) :
+
1657 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
1658 char *value = name + strlen(name) + 1;
+
1659
+
1660 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
1661 if (req->se->op.setxattr)
+
1662 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
1663 arg->flags);
+
1664 else
+
1665 fuse_reply_err(req, ENOSYS);
+
1666}
+
1667
+
1668static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1669{
+
1670 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1671
+
1672 if (req->se->op.getxattr)
+
1673 req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+
1674 else
+
1675 fuse_reply_err(req, ENOSYS);
+
1676}
+
1677
+
1678static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1679{
+
1680 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1681
+
1682 if (req->se->op.listxattr)
+
1683 req->se->op.listxattr(req, nodeid, arg->size);
+
1684 else
+
1685 fuse_reply_err(req, ENOSYS);
+
1686}
+
1687
+
1688static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1689{
+
1690 char *name = (char *) inarg;
+
1691
+
1692 if (req->se->op.removexattr)
+
1693 req->se->op.removexattr(req, nodeid, name);
+
1694 else
+
1695 fuse_reply_err(req, ENOSYS);
+
1696}
+
1697
+
1698static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+
1699 struct flock *flock)
+
1700{
+
1701 memset(flock, 0, sizeof(struct flock));
+
1702 flock->l_type = fl->type;
+
1703 flock->l_whence = SEEK_SET;
+
1704 flock->l_start = fl->start;
+
1705 if (fl->end == OFFSET_MAX)
+
1706 flock->l_len = 0;
+
1707 else
+
1708 flock->l_len = fl->end - fl->start + 1;
+
1709 flock->l_pid = fl->pid;
+
1710}
+
1711
+
1712static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1713{
+
1714 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1715 struct fuse_file_info fi;
+
1716 struct flock flock;
+
1717
+
1718 memset(&fi, 0, sizeof(fi));
+
1719 fi.fh = arg->fh;
+
1720 fi.lock_owner = arg->owner;
+
1721
+
1722 convert_fuse_file_lock(&arg->lk, &flock);
+
1723 if (req->se->op.getlk)
+
1724 req->se->op.getlk(req, nodeid, &fi, &flock);
+
1725 else
+
1726 fuse_reply_err(req, ENOSYS);
+
1727}
+
1728
+
1729static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+
1730 const void *inarg, int sleep)
+
1731{
+
1732 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1733 struct fuse_file_info fi;
+
1734 struct flock flock;
+
1735
+
1736 memset(&fi, 0, sizeof(fi));
+
1737 fi.fh = arg->fh;
+
1738 fi.lock_owner = arg->owner;
+
1739
+
1740 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
1741 int op = 0;
+
1742
+
1743 switch (arg->lk.type) {
+
1744 case F_RDLCK:
+
1745 op = LOCK_SH;
+
1746 break;
+
1747 case F_WRLCK:
+
1748 op = LOCK_EX;
+
1749 break;
+
1750 case F_UNLCK:
+
1751 op = LOCK_UN;
+
1752 break;
+
1753 }
+
1754 if (!sleep)
+
1755 op |= LOCK_NB;
+
1756
+
1757 if (req->se->op.flock)
+
1758 req->se->op.flock(req, nodeid, &fi, op);
+
1759 else
+
1760 fuse_reply_err(req, ENOSYS);
+
1761 } else {
+
1762 convert_fuse_file_lock(&arg->lk, &flock);
+
1763 if (req->se->op.setlk)
+
1764 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
1765 else
+
1766 fuse_reply_err(req, ENOSYS);
+
1767 }
+
1768}
+
1769
+
1770static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1771{
+
1772 do_setlk_common(req, nodeid, inarg, 0);
+
1773}
+
1774
+
1775static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1776{
+
1777 do_setlk_common(req, nodeid, inarg, 1);
+
1778}
+
1779
+
1780static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
1781{
+
1782 struct fuse_req *curr;
+
1783
+
1784 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
1785 if (curr->unique == req->u.i.unique) {
+ +
1787 void *data;
+
1788
+
1789 curr->ref_cnt++;
+
1790 pthread_mutex_unlock(&se->lock);
+
1791
+
1792 /* Ugh, ugly locking */
+
1793 pthread_mutex_lock(&curr->lock);
+
1794 pthread_mutex_lock(&se->lock);
+
1795 curr->interrupted = 1;
+
1796 func = curr->u.ni.func;
+
1797 data = curr->u.ni.data;
+
1798 pthread_mutex_unlock(&se->lock);
+
1799 if (func)
+
1800 func(curr, data);
+
1801 pthread_mutex_unlock(&curr->lock);
+
1802
+
1803 pthread_mutex_lock(&se->lock);
+
1804 curr->ref_cnt--;
+
1805 if (!curr->ref_cnt) {
+
1806 destroy_req(curr);
+
1807 }
+
1808
+
1809 return 1;
+
1810 }
+
1811 }
+
1812 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1813 curr = curr->next) {
+
1814 if (curr->u.i.unique == req->u.i.unique)
+
1815 return 1;
+
1816 }
+
1817 return 0;
+
1818}
+
1819
+
1820static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1821{
+
1822 struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+
1823 struct fuse_session *se = req->se;
+
1824
+
1825 (void) nodeid;
+
1826 if (se->debug)
+
1827 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
1828 (unsigned long long) arg->unique);
+
1829
+
1830 req->u.i.unique = arg->unique;
+
1831
+
1832 pthread_mutex_lock(&se->lock);
+
1833 if (find_interrupted(se, req)) {
+
1834 fuse_chan_put(req->ch);
+
1835 req->ch = NULL;
+
1836 destroy_req(req);
+
1837 } else
+
1838 list_add_req(req, &se->interrupts);
+
1839 pthread_mutex_unlock(&se->lock);
+
1840}
+
1841
+
1842static struct fuse_req *check_interrupt(struct fuse_session *se,
+
1843 struct fuse_req *req)
+
1844{
+
1845 struct fuse_req *curr;
+
1846
+
1847 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1848 curr = curr->next) {
+
1849 if (curr->u.i.unique == req->unique) {
+
1850 req->interrupted = 1;
+
1851 list_del_req(curr);
+
1852 fuse_chan_put(curr->ch);
+
1853 curr->ch = NULL;
+
1854 destroy_req(curr);
+
1855 return NULL;
+
1856 }
+
1857 }
+
1858 curr = se->interrupts.next;
+
1859 if (curr != &se->interrupts) {
+
1860 list_del_req(curr);
+
1861 list_init_req(curr);
+
1862 return curr;
+
1863 } else
+
1864 return NULL;
+
1865}
+
1866
+
1867static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1868{
+
1869 struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
1870
+
1871 if (req->se->op.bmap)
+
1872 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
1873 else
+
1874 fuse_reply_err(req, ENOSYS);
+
1875}
+
1876
+
1877static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1878{
+
1879 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+
1880 unsigned int flags = arg->flags;
+
1881 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
1882 struct fuse_file_info fi;
+
1883
+
1884 if (flags & FUSE_IOCTL_DIR &&
+
1885 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
1886 fuse_reply_err(req, ENOTTY);
+
1887 return;
+
1888 }
+
1889
+
1890 memset(&fi, 0, sizeof(fi));
+
1891 fi.fh = arg->fh;
+
1892
+
1893 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
1894 !(flags & FUSE_IOCTL_32BIT)) {
+
1895 req->ioctl_64bit = 1;
+
1896 }
+
1897
+
1898 if (req->se->op.ioctl)
+
1899 req->se->op.ioctl(req, nodeid, arg->cmd,
+
1900 (void *)(uintptr_t)arg->arg, &fi, flags,
+
1901 in_buf, arg->in_size, arg->out_size);
+
1902 else
+
1903 fuse_reply_err(req, ENOSYS);
+
1904}
+
1905
+
+
1906void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
1907{
+
1908 free(ph);
+
1909}
+
+
1910
+
1911static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1912{
+
1913 struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+
1914 struct fuse_file_info fi;
+
1915
+
1916 memset(&fi, 0, sizeof(fi));
+
1917 fi.fh = arg->fh;
+
1918 fi.poll_events = arg->events;
+
1919
+
1920 if (req->se->op.poll) {
+
1921 struct fuse_pollhandle *ph = NULL;
+
1922
+
1923 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
1924 ph = malloc(sizeof(struct fuse_pollhandle));
+
1925 if (ph == NULL) {
+
1926 fuse_reply_err(req, ENOMEM);
+
1927 return;
+
1928 }
+
1929 ph->kh = arg->kh;
+
1930 ph->se = req->se;
+
1931 }
+
1932
+
1933 req->se->op.poll(req, nodeid, &fi, ph);
+
1934 } else {
+
1935 fuse_reply_err(req, ENOSYS);
+
1936 }
+
1937}
+
1938
+
1939static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1940{
+
1941 struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+
1942 struct fuse_file_info fi;
+
1943
+
1944 memset(&fi, 0, sizeof(fi));
+
1945 fi.fh = arg->fh;
+
1946
+
1947 if (req->se->op.fallocate)
+
1948 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+
1949 else
+
1950 fuse_reply_err(req, ENOSYS);
+
1951}
+
1952
+
1953static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
+
1954{
+
1955 struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
+
1956 struct fuse_file_info fi_in, fi_out;
+
1957
+
1958 memset(&fi_in, 0, sizeof(fi_in));
+
1959 fi_in.fh = arg->fh_in;
+
1960
+
1961 memset(&fi_out, 0, sizeof(fi_out));
+
1962 fi_out.fh = arg->fh_out;
+
1963
+
1964
+
1965 if (req->se->op.copy_file_range)
+
1966 req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
+
1967 &fi_in, arg->nodeid_out,
+
1968 arg->off_out, &fi_out, arg->len,
+
1969 arg->flags);
+
1970 else
+
1971 fuse_reply_err(req, ENOSYS);
+
1972}
+
1973
+
1974static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1975{
+
1976 struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg;
+
1977 struct fuse_file_info fi;
+
1978
+
1979 memset(&fi, 0, sizeof(fi));
+
1980 fi.fh = arg->fh;
+
1981
+
1982 if (req->se->op.lseek)
+
1983 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
1984 else
+
1985 fuse_reply_err(req, ENOSYS);
+
1986}
+
1987
+
1988static bool want_flags_valid(uint64_t capable, uint64_t want)
+
1989{
+
1990 uint64_t unknown_flags = want & (~capable);
+
1991 if (unknown_flags != 0) {
+
1992 fuse_log(FUSE_LOG_ERR,
+
1993 "fuse: unknown connection 'want' flags: 0x%08lx\n",
+
1994 unknown_flags);
+
1995 return false;
+
1996 }
+
1997 return true;
+
1998}
+
1999
+
2003static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn,
+
2004 uint64_t want_ext_default)
+
2005{
+
2006 /* Convert want to want_ext if necessary */
+
2007 if (conn->want != 0) {
+
2008 if (conn->want_ext != want_ext_default) {
+
2009 fuse_log(FUSE_LOG_ERR,
+
2010 "fuse: both 'want' and 'want_ext' are set\n");
+
2011 return -EINVAL;
+
2012 }
+
2013 conn->want_ext |= conn->want;
+
2014 }
+
2015
+
2016 return 0;
+
2017}
+
2018
+
2019/* Prevent bogus data races (bogus since "init" is called before
+
2020 * multi-threading becomes relevant */
+
2021static __attribute__((no_sanitize("thread")))
+
2022void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2023{
+
2024 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
2025 struct fuse_init_out outarg;
+
2026 struct fuse_session *se = req->se;
+
2027 size_t bufsize = se->bufsize;
+
2028 size_t outargsize = sizeof(outarg);
+
2029 uint64_t inargflags = 0;
+
2030 uint64_t outargflags = 0;
+
2031 bool buf_reallocable = se->buf_reallocable;
+
2032 (void) nodeid;
+
2033 if (se->debug) {
+
2034 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2035 if (arg->major == 7 && arg->minor >= 6) {
+
2036 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2037 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2038 arg->max_readahead);
+
2039 }
+
2040 }
+
2041 se->conn.proto_major = arg->major;
+
2042 se->conn.proto_minor = arg->minor;
+
2043 se->conn.capable_ext = 0;
+
2044 se->conn.want_ext = 0;
+
2045
+
2046 memset(&outarg, 0, sizeof(outarg));
+
2047 outarg.major = FUSE_KERNEL_VERSION;
+
2048 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2049
+
2050 if (arg->major < 7) {
+
2051 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2052 arg->major, arg->minor);
+
2053 fuse_reply_err(req, EPROTO);
+
2054 return;
+
2055 }
+
2056
+
2057 if (arg->major > 7) {
+
2058 /* Wait for a second INIT request with a 7.X version */
+
2059 send_reply_ok(req, &outarg, sizeof(outarg));
+
2060 return;
+
2061 }
+
2062
+
2063 if (arg->minor >= 6) {
+
2064 if (arg->max_readahead < se->conn.max_readahead)
+
2065 se->conn.max_readahead = arg->max_readahead;
+
2066 inargflags = arg->flags;
+
2067 if (inargflags & FUSE_INIT_EXT)
+
2068 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2069 if (inargflags & FUSE_ASYNC_READ)
+
2070 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2071 if (inargflags & FUSE_POSIX_LOCKS)
+
2072 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2073 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2074 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2075 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2076 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2077 if (inargflags & FUSE_DONT_MASK)
+
2078 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2079 if (inargflags & FUSE_FLOCK_LOCKS)
+
2080 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2081 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2082 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2083 if (inargflags & FUSE_DO_READDIRPLUS)
+
2084 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2085 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2086 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2087 if (inargflags & FUSE_ASYNC_DIO)
+
2088 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2089 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2090 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2091 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2092 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2093 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2094 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2095 if (inargflags & FUSE_POSIX_ACL)
+
2096 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2097 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2098 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2099 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2100 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2101 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2102 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2103 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2104 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2105 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2106 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2107 if (inargflags & FUSE_SETXATTR_EXT)
+
2108 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2109 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2110 size_t max_bufsize =
+
2111 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2112 + FUSE_BUFFER_HEADER_SIZE;
+
2113 if (bufsize > max_bufsize) {
+
2114 bufsize = max_bufsize;
+
2115 }
+
2116 buf_reallocable = false;
+
2117 }
+
2118 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2119 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2120 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2121 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2122 if (inargflags & FUSE_PASSTHROUGH)
+
2123 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2124 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2125 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2126 } else {
+
2127 se->conn.max_readahead = 0;
+
2128 }
+
2129
+
2130 if (se->conn.proto_minor >= 14) {
+
2131#ifdef HAVE_SPLICE
+
2132#ifdef HAVE_VMSPLICE
+
2133 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2134 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2136 }
+
2137#endif
+
2138 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2139 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2140 }
+
2141#endif
+
2142 }
+
2143 if (se->conn.proto_minor >= 18)
+
2144 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2145
+
2146 /* Default settings for modern filesystems.
+
2147 *
+
2148 * Most of these capabilities were disabled by default in
+
2149 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2150 * we can finally enable them by default (as long as they're
+
2151 * supported by the kernel).
+
2152 */
+
2153#define LL_SET_DEFAULT(cond, cap) \
+
2154 if ((cond)) \
+
2155 fuse_set_feature_flag(&se->conn, cap)
+
2156
+
2157 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2158 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2159 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2160 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2161 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2162 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2163 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2165 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2166 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2167 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2169
+
2170 /* This could safely become default, but libfuse needs an API extension
+
2171 * to support it
+
2172 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2173 */
+
2174
+
2175 se->conn.time_gran = 1;
+
2176
+
2177 se->got_init = 1;
+
2178 if (se->op.init) {
+
2179 uint64_t want_ext_default = se->conn.want_ext;
+
2180 int rc;
+
2181
+
2182 // Apply the first 32 bits of capable_ext to capable
+
2183 se->conn.capable =
+
2184 (uint32_t)(se->conn.capable_ext & 0xFFFFFFFF);
+
2185
+
2186 se->op.init(se->userdata, &se->conn);
+
2187
+
2188 /*
+
2189 * se->conn.want is 32-bit value and deprecated in favour of
+
2190 * se->conn.want_ext
+
2191 * Userspace might still use conn.want - we need to convert it
+
2192 */
+
2193 rc = convert_to_conn_want_ext(&se->conn, want_ext_default);
+
2194 if (rc != 0) {
+
2195 fuse_reply_err(req, EPROTO);
+
2196 se->error = -EPROTO;
+ +
2198 return;
+
2199 }
+
2200 }
+
2201
+
2202 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2203 fuse_reply_err(req, EPROTO);
+
2204 se->error = -EPROTO;
+ +
2206 return;
+
2207 }
+
2208
+
2209 unsigned max_read_mo = get_max_read(se->mo);
+
2210 if (se->conn.max_read != max_read_mo) {
+
2211 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2212 "requested different maximum read size (%u vs %u)\n",
+
2213 se->conn.max_read, max_read_mo);
+
2214 fuse_reply_err(req, EPROTO);
+
2215 se->error = -EPROTO;
+ +
2217 return;
+
2218 }
+
2219
+
2220 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2221 fuse_log(FUSE_LOG_ERR,
+
2222 "fuse: warning: buffer size too small: %zu\n",
+
2223 bufsize);
+
2224 bufsize = FUSE_MIN_READ_BUFFER;
+
2225 }
+
2226
+
2227 if (buf_reallocable)
+
2228 bufsize = UINT_MAX;
+
2229 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2230 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2231
+
2232 if (arg->flags & FUSE_MAX_PAGES) {
+
2233 outarg.flags |= FUSE_MAX_PAGES;
+
2234 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2235 }
+
2236 outargflags = outarg.flags;
+
2237 /* Always enable big writes, this is superseded
+
2238 by the max_write option */
+
2239 outargflags |= FUSE_BIG_WRITES;
+
2240
+
2241 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2242 outargflags |= FUSE_ASYNC_READ;
+
2243 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2244 outargflags |= FUSE_POSIX_LOCKS;
+
2245 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2246 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2247 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2248 outargflags |= FUSE_EXPORT_SUPPORT;
+
2249 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2250 outargflags |= FUSE_DONT_MASK;
+
2251 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2252 outargflags |= FUSE_FLOCK_LOCKS;
+
2253 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2254 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2255 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2256 outargflags |= FUSE_DO_READDIRPLUS;
+
2257 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2258 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2259 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2260 outargflags |= FUSE_ASYNC_DIO;
+
2261 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2262 outargflags |= FUSE_WRITEBACK_CACHE;
+
2263 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2264 outargflags |= FUSE_PARALLEL_DIROPS;
+
2265 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2266 outargflags |= FUSE_POSIX_ACL;
+
2267 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2268 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2269 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2270 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2271 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2272 outargflags |= FUSE_CACHE_SYMLINKS;
+
2273 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2274 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2275 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2276 outargflags |= FUSE_SETXATTR_EXT;
+
2277 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2278 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2279 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2280 outargflags |= FUSE_PASSTHROUGH;
+
2281 /*
+
2282 * outarg.max_stack_depth includes the fuse stack layer,
+
2283 * so it is one more than max_backing_stack_depth.
+
2284 */
+
2285 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2286 }
+
2287 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2288 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2289
+
2290 if (inargflags & FUSE_INIT_EXT) {
+
2291 outargflags |= FUSE_INIT_EXT;
+
2292 outarg.flags2 = outargflags >> 32;
+
2293 }
+
2294
+
2295 outarg.flags = outargflags;
+
2296
+
2297 outarg.max_readahead = se->conn.max_readahead;
+
2298 outarg.max_write = se->conn.max_write;
+
2299 if (se->conn.proto_minor >= 13) {
+
2300 if (se->conn.max_background >= (1 << 16))
+
2301 se->conn.max_background = (1 << 16) - 1;
+
2302 if (se->conn.congestion_threshold > se->conn.max_background)
+
2303 se->conn.congestion_threshold = se->conn.max_background;
+
2304 if (!se->conn.congestion_threshold) {
+
2305 se->conn.congestion_threshold =
+
2306 se->conn.max_background * 3 / 4;
+
2307 }
+
2308
+
2309 outarg.max_background = se->conn.max_background;
+
2310 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2311 }
+
2312 if (se->conn.proto_minor >= 23)
+
2313 outarg.time_gran = se->conn.time_gran;
+
2314
+
2315 if (se->debug) {
+
2316 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2317 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2318 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2319 outarg.max_readahead);
+
2320 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2321 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2322 outarg.max_background);
+
2323 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2324 outarg.congestion_threshold);
+
2325 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2326 outarg.time_gran);
+
2327 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2328 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2329 outarg.max_stack_depth);
+
2330 }
+
2331 if (arg->minor < 5)
+
2332 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2333 else if (arg->minor < 23)
+
2334 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2335
+
2336 send_reply_ok(req, &outarg, outargsize);
+
2337}
+
2338
+
2339static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2340{
+
2341 struct fuse_session *se = req->se;
+
2342
+
2343 (void) nodeid;
+
2344 (void) inarg;
+
2345
+
2346 se->got_destroy = 1;
+
2347 se->got_init = 0;
+
2348 if (se->op.destroy)
+
2349 se->op.destroy(se->userdata);
+
2350
+
2351 send_reply_ok(req, NULL, 0);
+
2352}
+
2353
+
2354static void list_del_nreq(struct fuse_notify_req *nreq)
+
2355{
+
2356 struct fuse_notify_req *prev = nreq->prev;
+
2357 struct fuse_notify_req *next = nreq->next;
+
2358 prev->next = next;
+
2359 next->prev = prev;
+
2360}
+
2361
+
2362static void list_add_nreq(struct fuse_notify_req *nreq,
+
2363 struct fuse_notify_req *next)
+
2364{
+
2365 struct fuse_notify_req *prev = next->prev;
+
2366 nreq->next = next;
+
2367 nreq->prev = prev;
+
2368 prev->next = nreq;
+
2369 next->prev = nreq;
+
2370}
+
2371
+
2372static void list_init_nreq(struct fuse_notify_req *nreq)
+
2373{
+
2374 nreq->next = nreq;
+
2375 nreq->prev = nreq;
+
2376}
+
2377
+
2378static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
2379 const void *inarg, const struct fuse_buf *buf)
+
2380{
+
2381 struct fuse_session *se = req->se;
+
2382 struct fuse_notify_req *nreq;
+
2383 struct fuse_notify_req *head;
+
2384
+
2385 pthread_mutex_lock(&se->lock);
+
2386 head = &se->notify_list;
+
2387 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
2388 if (nreq->unique == req->unique) {
+
2389 list_del_nreq(nreq);
+
2390 break;
+
2391 }
+
2392 }
+
2393 pthread_mutex_unlock(&se->lock);
+
2394
+
2395 if (nreq != head)
+
2396 nreq->reply(nreq, req, nodeid, inarg, buf);
+
2397}
+
2398
+
2399static int send_notify_iov(struct fuse_session *se, int notify_code,
+
2400 struct iovec *iov, int count)
+
2401{
+
2402 struct fuse_out_header out;
+
2403
+
2404 if (!se->got_init)
+
2405 return -ENOTCONN;
+
2406
+
2407 out.unique = 0;
+
2408 out.error = notify_code;
+
2409 iov[0].iov_base = &out;
+
2410 iov[0].iov_len = sizeof(struct fuse_out_header);
+
2411
+
2412 return fuse_send_msg(se, NULL, iov, count);
+
2413}
+
2414
+
+
2415int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
2416{
+
2417 if (ph != NULL) {
+
2418 struct fuse_notify_poll_wakeup_out outarg;
+
2419 struct iovec iov[2];
+
2420
+
2421 outarg.kh = ph->kh;
+
2422
+
2423 iov[1].iov_base = &outarg;
+
2424 iov[1].iov_len = sizeof(outarg);
+
2425
+
2426 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
2427 } else {
+
2428 return 0;
+
2429 }
+
2430}
+
+
2431
+
+
2432int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
2433 off_t off, off_t len)
+
2434{
+
2435 struct fuse_notify_inval_inode_out outarg;
+
2436 struct iovec iov[2];
+
2437
+
2438 if (!se)
+
2439 return -EINVAL;
+
2440
+
2441 if (se->conn.proto_minor < 12)
+
2442 return -ENOSYS;
+
2443
+
2444 outarg.ino = ino;
+
2445 outarg.off = off;
+
2446 outarg.len = len;
+
2447
+
2448 iov[1].iov_base = &outarg;
+
2449 iov[1].iov_len = sizeof(outarg);
+
2450
+
2451 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
2452}
+
+
2453
+
2473static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
2474 const char *name, size_t namelen,
+
2475 enum fuse_notify_entry_flags flags)
+
2476{
+
2477 struct fuse_notify_inval_entry_out outarg;
+
2478 struct iovec iov[3];
+
2479
+
2480 if (!se)
+
2481 return -EINVAL;
+
2482
+
2483 if (se->conn.proto_minor < 12)
+
2484 return -ENOSYS;
+
2485
+
2486 outarg.parent = parent;
+
2487 outarg.namelen = namelen;
+
2488 outarg.flags = 0;
+
2489 if (flags & FUSE_LL_EXPIRE_ONLY)
+
2490 outarg.flags |= FUSE_EXPIRE_ONLY;
+
2491
+
2492 iov[1].iov_base = &outarg;
+
2493 iov[1].iov_len = sizeof(outarg);
+
2494 iov[2].iov_base = (void *)name;
+
2495 iov[2].iov_len = namelen + 1;
+
2496
+
2497 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
2498}
+
2499
+
+
2500int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
2501 const char *name, size_t namelen)
+
2502{
+
2503 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
2504}
+
+
2505
+
+
2506int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
2507 const char *name, size_t namelen)
+
2508{
+
2509 if (!se)
+
2510 return -EINVAL;
+
2511
+
2512 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
2513 return -ENOSYS;
+
2514
+
2515 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
2516}
+
+
2517
+
2518
+
+
2519int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
2520 fuse_ino_t parent, fuse_ino_t child,
+
2521 const char *name, size_t namelen)
+
2522{
+
2523 struct fuse_notify_delete_out outarg;
+
2524 struct iovec iov[3];
+
2525
+
2526 if (!se)
+
2527 return -EINVAL;
+
2528
+
2529 if (se->conn.proto_minor < 18)
+
2530 return -ENOSYS;
+
2531
+
2532 outarg.parent = parent;
+
2533 outarg.child = child;
+
2534 outarg.namelen = namelen;
+
2535 outarg.padding = 0;
+
2536
+
2537 iov[1].iov_base = &outarg;
+
2538 iov[1].iov_len = sizeof(outarg);
+
2539 iov[2].iov_base = (void *)name;
+
2540 iov[2].iov_len = namelen + 1;
+
2541
+
2542 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
2543}
+
+
2544
+
+
2545int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
2546 off_t offset, struct fuse_bufvec *bufv,
+
2547 enum fuse_buf_copy_flags flags)
+
2548{
+
2549 struct fuse_out_header out;
+
2550 struct fuse_notify_store_out outarg;
+
2551 struct iovec iov[3];
+
2552 size_t size = fuse_buf_size(bufv);
+
2553 int res;
+
2554
+
2555 if (!se)
+
2556 return -EINVAL;
+
2557
+
2558 if (se->conn.proto_minor < 15)
+
2559 return -ENOSYS;
+
2560
+
2561 out.unique = 0;
+
2562 out.error = FUSE_NOTIFY_STORE;
+
2563
+
2564 outarg.nodeid = ino;
+
2565 outarg.offset = offset;
+
2566 outarg.size = size;
+
2567 outarg.padding = 0;
+
2568
+
2569 iov[0].iov_base = &out;
+
2570 iov[0].iov_len = sizeof(out);
+
2571 iov[1].iov_base = &outarg;
+
2572 iov[1].iov_len = sizeof(outarg);
+
2573
+
2574 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags);
+
2575 if (res > 0)
+
2576 res = -res;
+
2577
+
2578 return res;
+
2579}
+
+
2580
+
2581struct fuse_retrieve_req {
+
2582 struct fuse_notify_req nreq;
+
2583 void *cookie;
+
2584};
+
2585
+
2586static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
2587 fuse_req_t req, fuse_ino_t ino,
+
2588 const void *inarg,
+
2589 const struct fuse_buf *ibuf)
+
2590{
+
2591 struct fuse_session *se = req->se;
+
2592 struct fuse_retrieve_req *rreq =
+
2593 container_of(nreq, struct fuse_retrieve_req, nreq);
+
2594 const struct fuse_notify_retrieve_in *arg = inarg;
+
2595 struct fuse_bufvec bufv = {
+
2596 .buf[0] = *ibuf,
+
2597 .count = 1,
+
2598 };
+
2599
+
2600 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
2601 bufv.buf[0].mem = PARAM(arg);
+
2602
+
2603 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
2604 sizeof(struct fuse_notify_retrieve_in);
+
2605
+
2606 if (bufv.buf[0].size < arg->size) {
+
2607 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
2608 fuse_reply_none(req);
+
2609 goto out;
+
2610 }
+
2611 bufv.buf[0].size = arg->size;
+
2612
+
2613 if (se->op.retrieve_reply) {
+
2614 se->op.retrieve_reply(req, rreq->cookie, ino,
+
2615 arg->offset, &bufv);
+
2616 } else {
+
2617 fuse_reply_none(req);
+
2618 }
+
2619out:
+
2620 free(rreq);
+
2621 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
2622 fuse_ll_clear_pipe(se);
+
2623}
+
2624
+
+
2625int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
2626 size_t size, off_t offset, void *cookie)
+
2627{
+
2628 struct fuse_notify_retrieve_out outarg;
+
2629 struct iovec iov[2];
+
2630 struct fuse_retrieve_req *rreq;
+
2631 int err;
+
2632
+
2633 if (!se)
+
2634 return -EINVAL;
+
2635
+
2636 if (se->conn.proto_minor < 15)
+
2637 return -ENOSYS;
+
2638
+
2639 rreq = malloc(sizeof(*rreq));
+
2640 if (rreq == NULL)
+
2641 return -ENOMEM;
+
2642
+
2643 pthread_mutex_lock(&se->lock);
+
2644 rreq->cookie = cookie;
+
2645 rreq->nreq.unique = se->notify_ctr++;
+
2646 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
2647 list_add_nreq(&rreq->nreq, &se->notify_list);
+
2648 pthread_mutex_unlock(&se->lock);
+
2649
+
2650 outarg.notify_unique = rreq->nreq.unique;
+
2651 outarg.nodeid = ino;
+
2652 outarg.offset = offset;
+
2653 outarg.size = size;
+
2654 outarg.padding = 0;
+
2655
+
2656 iov[1].iov_base = &outarg;
+
2657 iov[1].iov_len = sizeof(outarg);
+
2658
+
2659 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
2660 if (err) {
+
2661 pthread_mutex_lock(&se->lock);
+
2662 list_del_nreq(&rreq->nreq);
+
2663 pthread_mutex_unlock(&se->lock);
+
2664 free(rreq);
+
2665 }
+
2666
+
2667 return err;
+
2668}
+
+
2669
+
+ +
2671{
+
2672 return req->se->userdata;
+
2673}
+
+
2674
+
+ +
2676{
+
2677 return &req->ctx;
+
2678}
+
+
2679
+
+ +
2681 void *data)
+
2682{
+
2683 pthread_mutex_lock(&req->lock);
+
2684 pthread_mutex_lock(&req->se->lock);
+
2685 req->u.ni.func = func;
+
2686 req->u.ni.data = data;
+
2687 pthread_mutex_unlock(&req->se->lock);
+
2688 if (req->interrupted && func)
+
2689 func(req, data);
+
2690 pthread_mutex_unlock(&req->lock);
+
2691}
+
+
2692
+
+ +
2694{
+
2695 int interrupted;
+
2696
+
2697 pthread_mutex_lock(&req->se->lock);
+
2698 interrupted = req->interrupted;
+
2699 pthread_mutex_unlock(&req->se->lock);
+
2700
+
2701 return interrupted;
+
2702}
+
+
2703
+
2704static struct {
+
2705 void (*func)(fuse_req_t, fuse_ino_t, const void *);
+
2706 const char *name;
+
2707} fuse_ll_ops[] = {
+
2708 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
2709 [FUSE_FORGET] = { do_forget, "FORGET" },
+
2710 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
2711 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
2712 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
2713 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
2714 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
2715 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
2716 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
2717 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
2718 [FUSE_RENAME] = { do_rename, "RENAME" },
+
2719 [FUSE_LINK] = { do_link, "LINK" },
+
2720 [FUSE_OPEN] = { do_open, "OPEN" },
+
2721 [FUSE_READ] = { do_read, "READ" },
+
2722 [FUSE_WRITE] = { do_write, "WRITE" },
+
2723 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
2724 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
2725 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
2726 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
2727 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
2728 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
2729 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
2730 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
2731 [FUSE_INIT] = { do_init, "INIT" },
+
2732 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
2733 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
2734 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
2735 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
2736 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
2737 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
2738 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
2739 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
2740 [FUSE_CREATE] = { do_create, "CREATE" },
+
2741 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
2742 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
2743 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
2744 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
2745 [FUSE_POLL] = { do_poll, "POLL" },
+
2746 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
2747 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
2748 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
2749 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
2750 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
2751 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
2752 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
2753 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
2754 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
2755};
+
2756
+
2757/*
+
2758 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
2759 * Without ABI compatibility we could use the size of the array.
+
2760 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
2761 */
+
2762#define FUSE_MAXOP (CUSE_INIT + 1)
+
2763
+
2764static const char *opname(enum fuse_opcode opcode)
+
2765{
+
2766 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
2767 return "???";
+
2768 else
+
2769 return fuse_ll_ops[opcode].name;
+
2770}
+
2771
+
2772static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
2773 struct fuse_bufvec *src)
+
2774{
+
2775 ssize_t res = fuse_buf_copy(dst, src, 0);
+
2776 if (res < 0) {
+
2777 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
2778 return res;
+
2779 }
+
2780 if ((size_t)res < fuse_buf_size(dst)) {
+
2781 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
2782 return -1;
+
2783 }
+
2784 return 0;
+
2785}
+
2786
+
+
2787void fuse_session_process_buf(struct fuse_session *se,
+
2788 const struct fuse_buf *buf)
+
2789{
+
2790 fuse_session_process_buf_internal(se, buf, NULL);
+
2791}
+
+
2792
+
2793/* libfuse internal handler */
+
2794void fuse_session_process_buf_internal(struct fuse_session *se,
+
2795 const struct fuse_buf *buf, struct fuse_chan *ch)
+
2796{
+
2797 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
2798 sizeof(struct fuse_write_in);
+
2799 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
2800 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
2801 struct fuse_in_header *in;
+
2802 const void *inarg;
+
2803 struct fuse_req *req;
+
2804 void *mbuf = NULL;
+
2805 int err;
+
2806 int res;
+
2807
+
2808 if (buf->flags & FUSE_BUF_IS_FD) {
+
2809 if (buf->size < tmpbuf.buf[0].size)
+
2810 tmpbuf.buf[0].size = buf->size;
+
2811
+
2812 mbuf = malloc(tmpbuf.buf[0].size);
+
2813 if (mbuf == NULL) {
+
2814 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
2815 goto clear_pipe;
+
2816 }
+
2817 tmpbuf.buf[0].mem = mbuf;
+
2818
+
2819 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2820 if (res < 0)
+
2821 goto clear_pipe;
+
2822
+
2823 in = mbuf;
+
2824 } else {
+
2825 in = buf->mem;
+
2826 }
+
2827
+
2828 if (se->debug) {
+
2829 fuse_log(FUSE_LOG_DEBUG,
+
2830 "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
2831 (unsigned long long) in->unique,
+
2832 opname((enum fuse_opcode) in->opcode), in->opcode,
+
2833 (unsigned long long) in->nodeid, buf->size, in->pid);
+
2834 }
+
2835
+
2836 req = fuse_ll_alloc_req(se);
+
2837 if (req == NULL) {
+
2838 struct fuse_out_header out = {
+
2839 .unique = in->unique,
+
2840 .error = -ENOMEM,
+
2841 };
+
2842 struct iovec iov = {
+
2843 .iov_base = &out,
+
2844 .iov_len = sizeof(struct fuse_out_header),
+
2845 };
+
2846
+
2847 fuse_send_msg(se, ch, &iov, 1);
+
2848 goto clear_pipe;
+
2849 }
+
2850
+
2851 req->unique = in->unique;
+
2852 req->ctx.uid = in->uid;
+
2853 req->ctx.gid = in->gid;
+
2854 req->ctx.pid = in->pid;
+
2855 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
2856
+
2857 err = EIO;
+
2858 if (!se->got_init) {
+
2859 enum fuse_opcode expected;
+
2860
+
2861 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
2862 if (in->opcode != expected)
+
2863 goto reply_err;
+
2864 } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+
2865 goto reply_err;
+
2866
+
2867 err = EACCES;
+
2868 /* Implement -o allow_root */
+
2869 if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
+
2870 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+
2871 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+
2872 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+
2873 in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+
2874 in->opcode != FUSE_NOTIFY_REPLY &&
+
2875 in->opcode != FUSE_READDIRPLUS)
+
2876 goto reply_err;
+
2877
+
2878 err = ENOSYS;
+
2879 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
2880 goto reply_err;
+
2881 /* Do not process interrupt request */
+
2882 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
2883 if (se->debug)
+
2884 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
2885 goto reply_err;
+
2886 }
+
2887 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
2888 struct fuse_req *intr;
+
2889 pthread_mutex_lock(&se->lock);
+
2890 intr = check_interrupt(se, req);
+
2891 list_add_req(req, &se->list);
+
2892 pthread_mutex_unlock(&se->lock);
+
2893 if (intr)
+
2894 fuse_reply_err(intr, EAGAIN);
+
2895 }
+
2896
+
2897 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
2898 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
2899 in->opcode != FUSE_NOTIFY_REPLY) {
+
2900 void *newmbuf;
+
2901
+
2902 err = ENOMEM;
+
2903 newmbuf = realloc(mbuf, buf->size);
+
2904 if (newmbuf == NULL)
+
2905 goto reply_err;
+
2906 mbuf = newmbuf;
+
2907
+
2908 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
2909 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
2910
+
2911 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2912 err = -res;
+
2913 if (res < 0)
+
2914 goto reply_err;
+
2915
+
2916 in = mbuf;
+
2917 }
+
2918
+
2919 inarg = (void *) &in[1];
+
2920 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
2921 do_write_buf(req, in->nodeid, inarg, buf);
+
2922 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
2923 do_notify_reply(req, in->nodeid, inarg, buf);
+
2924 else
+
2925 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
2926
+
2927out_free:
+
2928 free(mbuf);
+
2929 return;
+
2930
+
2931reply_err:
+
2932 fuse_reply_err(req, err);
+
2933clear_pipe:
+
2934 if (buf->flags & FUSE_BUF_IS_FD)
+
2935 fuse_ll_clear_pipe(se);
+
2936 goto out_free;
+
2937}
+
2938
+
2939#define LL_OPTION(n,o,v) \
+
2940 { n, offsetof(struct fuse_session, o), v }
+
2941
+
2942static const struct fuse_opt fuse_ll_opts[] = {
+
2943 LL_OPTION("debug", debug, 1),
+
2944 LL_OPTION("-d", debug, 1),
+
2945 LL_OPTION("--debug", debug, 1),
+
2946 LL_OPTION("allow_root", deny_others, 1),
+ +
2948};
+
2949
+
+ +
2951{
+
2952 printf("using FUSE kernel interface version %i.%i\n",
+
2953 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
2954 fuse_mount_version();
+
2955}
+
+
2956
+
+ +
2958{
+
2959 /* These are not all options, but the ones that are
+
2960 potentially of interest to an end-user */
+
2961 printf(
+
2962" -o allow_other allow access by all users\n"
+
2963" -o allow_root allow access by root\n"
+
2964" -o auto_unmount auto unmount on process termination\n");
+
2965}
+
+
2966
+
+
2967void fuse_session_destroy(struct fuse_session *se)
+
2968{
+
2969 struct fuse_ll_pipe *llp;
+
2970
+
2971 if (se->got_init && !se->got_destroy) {
+
2972 if (se->op.destroy)
+
2973 se->op.destroy(se->userdata);
+
2974 }
+
2975 llp = pthread_getspecific(se->pipe_key);
+
2976 if (llp != NULL)
+
2977 fuse_ll_pipe_free(llp);
+
2978 pthread_key_delete(se->pipe_key);
+
2979 pthread_mutex_destroy(&se->lock);
+
2980 free(se->cuse_data);
+
2981 if (se->fd != -1)
+
2982 close(se->fd);
+
2983 if (se->io != NULL)
+
2984 free(se->io);
+
2985 destroy_mount_opts(se->mo);
+
2986 free(se);
+
2987}
+
+
2988
+
2989
+
2990static void fuse_ll_pipe_destructor(void *data)
+
2991{
+
2992 struct fuse_ll_pipe *llp = data;
+
2993 fuse_ll_pipe_free(llp);
+
2994}
+
2995
+
2996void fuse_buf_free(struct fuse_buf *buf)
+
2997{
+
2998 if (buf->mem == NULL)
+
2999 return;
+
3000
+
3001 size_t write_header_sz =
+
3002 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
3003
+
3004 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
3005 free(ptr);
+
3006 buf->mem = NULL;
+
3007}
+
3008
+
3009/*
+
3010 * This is used to allocate buffers that hold fuse requests
+
3011 */
+
3012static void *buf_alloc(size_t size, bool internal)
+
3013{
+
3014 /*
+
3015 * For libfuse internal caller add in alignment. That cannot be done
+
3016 * for an external caller, as it is not guaranteed that the external
+
3017 * caller frees the raw pointer.
+
3018 */
+
3019 if (internal) {
+
3020 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3021 sizeof(struct fuse_write_in);
+
3022 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3023
+
3024 char *buf = aligned_alloc(pagesize, new_size);
+
3025 if (buf == NULL)
+
3026 return NULL;
+
3027
+
3028 buf += pagesize - write_header_sz;
+
3029
+
3030 return buf;
+
3031 } else {
+
3032 return malloc(size);
+
3033 }
+
3034}
+
3035
+
3036/*
+
3037 *@param internal true if called from libfuse internal code
+
3038 */
+
3039static int _fuse_session_receive_buf(struct fuse_session *se,
+
3040 struct fuse_buf *buf, struct fuse_chan *ch,
+
3041 bool internal)
+
3042{
+
3043 int err;
+
3044 ssize_t res;
+
3045 size_t bufsize = se->bufsize;
+
3046#ifdef HAVE_SPLICE
+
3047 struct fuse_ll_pipe *llp;
+
3048 struct fuse_buf tmpbuf;
+
3049
+
3050 if (se->conn.proto_minor < 14 ||
+
3051 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3052 goto fallback;
+
3053
+
3054 llp = fuse_ll_get_pipe(se);
+
3055 if (llp == NULL)
+
3056 goto fallback;
+
3057
+
3058 if (llp->size < bufsize) {
+
3059 if (llp->can_grow) {
+
3060 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3061 if (res == -1) {
+
3062 llp->can_grow = 0;
+
3063 res = grow_pipe_to_max(llp->pipe[0]);
+
3064 if (res > 0)
+
3065 llp->size = res;
+
3066 goto fallback;
+
3067 }
+
3068 llp->size = res;
+
3069 }
+
3070 if (llp->size < bufsize)
+
3071 goto fallback;
+
3072 }
+
3073
+
3074 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3075 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3076 llp->pipe[1], NULL, bufsize, 0,
+
3077 se->userdata);
+
3078 } else {
+
3079 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3080 bufsize, 0);
+
3081 }
+
3082 err = errno;
+
3083
+
3084 if (fuse_session_exited(se))
+
3085 return 0;
+
3086
+
3087 if (res == -1) {
+
3088 if (err == ENODEV) {
+
3089 /* Filesystem was unmounted, or connection was aborted
+
3090 via /sys/fs/fuse/connections */
+ +
3092 return 0;
+
3093 }
+
3094 if (err != EINTR && err != EAGAIN)
+
3095 perror("fuse: splice from device");
+
3096 return -err;
+
3097 }
+
3098
+
3099 if (res < sizeof(struct fuse_in_header)) {
+
3100 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3101 return -EIO;
+
3102 }
+
3103
+
3104 tmpbuf = (struct fuse_buf){
+
3105 .size = res,
+
3106 .flags = FUSE_BUF_IS_FD,
+
3107 .fd = llp->pipe[0],
+
3108 };
+
3109
+
3110 /*
+
3111 * Don't bother with zero copy for small requests.
+
3112 * fuse_loop_mt() needs to check for FORGET so this more than
+
3113 * just an optimization.
+
3114 */
+
3115 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3116 pagesize) {
+
3117 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3118 struct fuse_bufvec dst = { .count = 1 };
+
3119
+
3120 if (!buf->mem) {
+
3121 buf->mem = buf_alloc(se->bufsize, internal);
+
3122 if (!buf->mem) {
+
3123 fuse_log(
+
3124 FUSE_LOG_ERR,
+
3125 "fuse: failed to allocate read buffer\n");
+
3126 return -ENOMEM;
+
3127 }
+
3128 buf->mem_size = se->bufsize;
+
3129 if (internal)
+
3130 se->buf_reallocable = true;
+
3131 }
+
3132 buf->size = se->bufsize;
+
3133 buf->flags = 0;
+
3134 dst.buf[0] = *buf;
+
3135
+
3136 res = fuse_buf_copy(&dst, &src, 0);
+
3137 if (res < 0) {
+
3138 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3139 strerror(-res));
+
3140 fuse_ll_clear_pipe(se);
+
3141 return res;
+
3142 }
+
3143 if (res < tmpbuf.size) {
+
3144 fuse_log(FUSE_LOG_ERR,
+
3145 "fuse: copy from pipe: short read\n");
+
3146 fuse_ll_clear_pipe(se);
+
3147 return -EIO;
+
3148 }
+
3149 assert(res == tmpbuf.size);
+
3150
+
3151 } else {
+
3152 /* Don't overwrite buf->mem, as that would cause a leak */
+
3153 buf->fd = tmpbuf.fd;
+
3154 buf->flags = tmpbuf.flags;
+
3155 }
+
3156 buf->size = tmpbuf.size;
+
3157
+
3158 return res;
+
3159
+
3160fallback:
+
3161#endif
+
3162 if (!buf->mem) {
+
3163 buf->mem = buf_alloc(se->bufsize, internal);
+
3164 if (!buf->mem) {
+
3165 fuse_log(FUSE_LOG_ERR,
+
3166 "fuse: failed to allocate read buffer\n");
+
3167 return -ENOMEM;
+
3168 }
+
3169 buf->mem_size = se->bufsize;
+
3170 if (internal)
+
3171 se->buf_reallocable = true;
+
3172 }
+
3173
+
3174restart:
+
3175 if (se->buf_reallocable)
+
3176 bufsize = buf->mem_size;
+
3177 if (se->io != NULL) {
+
3178 /* se->io->read is never NULL if se->io is not NULL as
+
3179 specified by fuse_session_custom_io()*/
+
3180 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
3181 se->userdata);
+
3182 } else {
+
3183 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
3184 }
+
3185 err = errno;
+
3186
+
3187 if (fuse_session_exited(se))
+
3188 return 0;
+
3189 if (res == -1) {
+
3190 if (err == EINVAL && se->buf_reallocable &&
+
3191 se->bufsize > buf->mem_size) {
+
3192 void *newbuf = buf_alloc(se->bufsize, internal);
+
3193 if (!newbuf) {
+
3194 fuse_log(
+
3195 FUSE_LOG_ERR,
+
3196 "fuse: failed to (re)allocate read buffer\n");
+
3197 return -ENOMEM;
+
3198 }
+
3199 fuse_buf_free(buf);
+
3200 buf->mem = newbuf;
+
3201 buf->mem_size = se->bufsize;
+
3202 se->buf_reallocable = true;
+
3203 goto restart;
+
3204 }
+
3205
+
3206 /* ENOENT means the operation was interrupted, it's safe
+
3207 to restart */
+
3208 if (err == ENOENT)
+
3209 goto restart;
+
3210
+
3211 if (err == ENODEV) {
+
3212 /* Filesystem was unmounted, or connection was aborted
+
3213 via /sys/fs/fuse/connections */
+ +
3215 return 0;
+
3216 }
+
3217 /* Errors occurring during normal operation: EINTR (read
+
3218 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
3219 umounted) */
+
3220 if (err != EINTR && err != EAGAIN)
+
3221 perror("fuse: reading device");
+
3222 return -err;
+
3223 }
+
3224 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
3225 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
3226 return -EIO;
+
3227 }
+
3228
+
3229 buf->size = res;
+
3230
+
3231 return res;
+
3232}
+
3233
+
+
3234int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
3235{
+
3236 return _fuse_session_receive_buf(se, buf, NULL, false);
+
3237}
+
+
3238
+
3239/* libfuse internal handler */
+
3240int fuse_session_receive_buf_internal(struct fuse_session *se,
+
3241 struct fuse_buf *buf,
+
3242 struct fuse_chan *ch)
+
3243{
+
3244 return _fuse_session_receive_buf(se, buf, ch, true);
+
3245}
+
3246
+
3247struct fuse_session *
+
3248fuse_session_new_versioned(struct fuse_args *args,
+
3249 const struct fuse_lowlevel_ops *op, size_t op_size,
+
3250 struct libfuse_version *version, void *userdata);
+
3251struct fuse_session *
+
3252fuse_session_new_versioned(struct fuse_args *args,
+
3253 const struct fuse_lowlevel_ops *op, size_t op_size,
+
3254 struct libfuse_version *version, void *userdata)
+
3255{
+
3256 int err;
+
3257 struct fuse_session *se;
+
3258 struct mount_opts *mo;
+
3259
+
3260 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
3261 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3262 op_size = sizeof(struct fuse_lowlevel_ops);
+
3263 }
+
3264
+
3265 if (args->argc == 0) {
+
3266 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
3267 return NULL;
+
3268 }
+
3269
+
3270 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
3271 if (se == NULL) {
+
3272 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
3273 goto out1;
+
3274 }
+
3275 se->fd = -1;
+
3276 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
3277 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
3278 se->conn.max_readahead = UINT_MAX;
+
3279
+
3280 /* Parse options */
+
3281 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
3282 goto out2;
+
3283 if(se->deny_others) {
+
3284 /* Allowing access only by root is done by instructing
+
3285 * kernel to allow access by everyone, and then restricting
+
3286 * access to root and mountpoint owner in libfuse.
+
3287 */
+
3288 // We may be adding the option a second time, but
+
3289 // that doesn't hurt.
+
3290 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
3291 goto out2;
+
3292 }
+
3293 mo = parse_mount_opts(args);
+
3294 if (mo == NULL)
+
3295 goto out3;
+
3296
+
3297 if(args->argc == 1 &&
+
3298 args->argv[0][0] == '-') {
+
3299 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
3300 "will be ignored\n");
+
3301 } else if (args->argc != 1) {
+
3302 int i;
+
3303 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
3304 for(i = 1; i < args->argc-1; i++)
+
3305 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
3306 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
3307 goto out4;
+
3308 }
+
3309
+
3310 if (se->debug)
+
3311 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
3312
+
3313 list_init_req(&se->list);
+
3314 list_init_req(&se->interrupts);
+
3315 list_init_nreq(&se->notify_list);
+
3316 se->notify_ctr = 1;
+
3317 pthread_mutex_init(&se->lock, NULL);
+
3318
+
3319 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
3320 if (err) {
+
3321 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
3322 strerror(err));
+
3323 goto out5;
+
3324 }
+
3325
+
3326 memcpy(&se->op, op, op_size);
+
3327 se->owner = getuid();
+
3328 se->userdata = userdata;
+
3329
+
3330 se->mo = mo;
+
3331
+
3332 /* Fuse server application should pass the version it was compiled
+
3333 * against and pass it. If a libfuse version accidentally introduces an
+
3334 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
3335 * by checking the version numbers.
+
3336 */
+
3337 se->version = *version;
+
3338
+
3339 return se;
+
3340
+
3341out5:
+
3342 pthread_mutex_destroy(&se->lock);
+
3343out4:
+
3344 fuse_opt_free_args(args);
+
3345out3:
+
3346 if (mo != NULL)
+
3347 destroy_mount_opts(mo);
+
3348out2:
+
3349 free(se);
+
3350out1:
+
3351 return NULL;
+
3352}
+
3353
+
3354struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3355 const struct fuse_lowlevel_ops *op,
+
3356 size_t op_size, void *userdata);
+
3357struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3358 const struct fuse_lowlevel_ops *op,
+
3359 size_t op_size,
+
3360 void *userdata)
+
3361{
+
3362 /* unknown version */
+
3363 struct libfuse_version version = { 0 };
+
3364
+
3365 return fuse_session_new_versioned(args, op, op_size, &version,
+
3366 userdata);
+
3367}
+
3368
+
3369FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
3370int fuse_session_custom_io_317(struct fuse_session *se,
+
3371 const struct fuse_custom_io *io, size_t op_size, int fd)
+
3372{
+
3373 if (sizeof(struct fuse_custom_io) < op_size) {
+
3374 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3375 op_size = sizeof(struct fuse_custom_io);
+
3376 }
+
3377
+
3378 if (fd < 0) {
+
3379 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
3380 "fuse_session_custom_io()\n", fd);
+
3381 return -EBADF;
+
3382 }
+
3383 if (io == NULL) {
+
3384 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
3385 "fuse_session_custom_io()\n");
+
3386 return -EINVAL;
+
3387 } else if (io->read == NULL || io->writev == NULL) {
+
3388 /* If the user provides their own file descriptor, we can't
+
3389 guarantee that the default behavior of the io operations made
+
3390 in libfuse will function properly. Therefore, we enforce the
+
3391 user to implement these io operations when using custom io. */
+
3392 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
3393 "implement both io->read() and io->writev\n");
+
3394 return -EINVAL;
+
3395 }
+
3396
+
3397 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
3398 if (se->io == NULL) {
+
3399 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
3400 "Error: %s\n", strerror(errno));
+
3401 return -errno;
+
3402 }
+
3403
+
3404 se->fd = fd;
+
3405 memcpy(se->io, io, op_size);
+
3406 return 0;
+
3407}
+
3408
+
3409int fuse_session_custom_io_30(struct fuse_session *se,
+
3410 const struct fuse_custom_io *io, int fd);
+
3411FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
3412int fuse_session_custom_io_30(struct fuse_session *se,
+
3413 const struct fuse_custom_io *io, int fd)
+
3414{
+
3415 return fuse_session_custom_io_317(se, io,
+
3416 offsetof(struct fuse_custom_io, clone_fd), fd);
+
3417}
+
3418
+
+
3419int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
3420{
+
3421 int fd;
+
3422
+
3423 if (mountpoint == NULL) {
+
3424 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
3425 return -1;
+
3426 }
+
3427
+
3428 /*
+
3429 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
3430 * would ensue.
+
3431 */
+
3432 do {
+
3433 fd = open("/dev/null", O_RDWR);
+
3434 if (fd > 2)
+
3435 close(fd);
+
3436 } while (fd >= 0 && fd <= 2);
+
3437
+
3438 /*
+
3439 * To allow FUSE daemons to run without privileges, the caller may open
+
3440 * /dev/fuse before launching the file system and pass on the file
+
3441 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
3442 * parent process takes care of performing the mount in this case.
+
3443 */
+
3444 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
3445 if (fd != -1) {
+
3446 if (fcntl(fd, F_GETFD) == -1) {
+
3447 fuse_log(FUSE_LOG_ERR,
+
3448 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
3449 fd);
+
3450 return -1;
+
3451 }
+
3452 se->fd = fd;
+
3453 return 0;
+
3454 }
+
3455
+
3456 /* Open channel */
+
3457 fd = fuse_kern_mount(mountpoint, se->mo);
+
3458 if (fd == -1)
+
3459 return -1;
+
3460 se->fd = fd;
+
3461
+
3462 /* Save mountpoint */
+
3463 se->mountpoint = strdup(mountpoint);
+
3464 if (se->mountpoint == NULL)
+
3465 goto error_out;
+
3466
+
3467 return 0;
+
3468
+
3469error_out:
+
3470 fuse_kern_unmount(mountpoint, fd);
+
3471 return -1;
+
3472}
+
+
3473
+
+
3474int fuse_session_fd(struct fuse_session *se)
+
3475{
+
3476 return se->fd;
+
3477}
+
+
3478
+
+
3479void fuse_session_unmount(struct fuse_session *se)
+
3480{
+
3481 if (se->mountpoint != NULL) {
+
3482 fuse_kern_unmount(se->mountpoint, se->fd);
+
3483 se->fd = -1;
+
3484 free(se->mountpoint);
+
3485 se->mountpoint = NULL;
+
3486 }
+
3487}
+
+
3488
+
3489#ifdef linux
+
3490int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3491{
+
3492 char *buf;
+
3493 size_t bufsize = 1024;
+
3494 char path[128];
+
3495 int ret;
+
3496 int fd;
+
3497 unsigned long pid = req->ctx.pid;
+
3498 char *s;
+
3499
+
3500 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
3501
+
3502retry:
+
3503 buf = malloc(bufsize);
+
3504 if (buf == NULL)
+
3505 return -ENOMEM;
+
3506
+
3507 ret = -EIO;
+
3508 fd = open(path, O_RDONLY);
+
3509 if (fd == -1)
+
3510 goto out_free;
+
3511
+
3512 ret = read(fd, buf, bufsize);
+
3513 close(fd);
+
3514 if (ret < 0) {
+
3515 ret = -EIO;
+
3516 goto out_free;
+
3517 }
+
3518
+
3519 if ((size_t)ret == bufsize) {
+
3520 free(buf);
+
3521 bufsize *= 4;
+
3522 goto retry;
+
3523 }
+
3524
+
3525 buf[ret] = '\0';
+
3526 ret = -EIO;
+
3527 s = strstr(buf, "\nGroups:");
+
3528 if (s == NULL)
+
3529 goto out_free;
+
3530
+
3531 s += 8;
+
3532 ret = 0;
+
3533 while (1) {
+
3534 char *end;
+
3535 unsigned long val = strtoul(s, &end, 0);
+
3536 if (end == s)
+
3537 break;
+
3538
+
3539 s = end;
+
3540 if (ret < size)
+
3541 list[ret] = val;
+
3542 ret++;
+
3543 }
+
3544
+
3545out_free:
+
3546 free(buf);
+
3547 return ret;
+
3548}
+
3549#else /* linux */
+
3550/*
+
3551 * This is currently not implemented on other than Linux...
+
3552 */
+
+
3553int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3554{
+
3555 (void) req; (void) size; (void) list;
+
3556 return -ENOSYS;
+
3557}
+
+
3558#endif
+
3559
+
3560/* Prevent spurious data race warning - we don't care
+
3561 * about races for this flag */
+
3562__attribute__((no_sanitize_thread))
+
3563void fuse_session_exit(struct fuse_session *se)
+
3564{
+
3565 se->exited = 1;
+
3566}
+
3567
+
3568__attribute__((no_sanitize_thread))
+
3569void fuse_session_reset(struct fuse_session *se)
+
3570{
+
3571 se->exited = 0;
+
3572 se->error = 0;
+
3573}
+
3574
+
3575__attribute__((no_sanitize_thread))
+
3576int fuse_session_exited(struct fuse_session *se)
+
3577{
+
3578 return se->exited;
+
3579}
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
struct fuse_req * fuse_req_t
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
uint64_t fuse_ino_t
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + + +
uint64_t want_ext
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__misc_8h_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..bbfef57 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__opt_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..09fcb74 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__opt_8c_source.html @@ -0,0 +1,516 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
+
143
+
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2fuse__signals_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..919dc75 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2fuse__signals_8c_source.html @@ -0,0 +1,269 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
26static int ignore_sigs[] = { SIGPIPE};
+
27static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
28static struct fuse_session *fuse_instance;
+
29
+
30#define BT_STACK_SZ (1024 * 1024)
+
31static void *backtrace_buffer[BT_STACK_SZ];
+
32
+
33static void dump_stack(void)
+
34{
+
35#ifdef HAVE_BACKTRACE
+
36 char **strings;
+
37
+
38 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
39 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
40
+
41 if (strings == NULL) {
+
42 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
43 strerror(errno));
+
44 return;
+
45 }
+
46
+
47 for (int idx = 0; idx < nptrs; idx++)
+
48 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
49
+
50 free(strings);
+
51#endif
+
52}
+
53
+
54static void exit_handler(int sig)
+
55{
+
56 if (fuse_instance == NULL)
+
57 return;
+
58
+
59 fuse_session_exit(fuse_instance);
+
60
+
61 if (sig < 0) {
+
62 fuse_log(FUSE_LOG_ERR,
+
63 "assertion error: signal value <= 0\n");
+
64 dump_stack();
+
65 abort();
+
66 fuse_instance->error = sig;
+
67 }
+
68
+
69 fuse_instance->error = sig;
+
70}
+
71
+
72static void exit_backtrace(int sig)
+
73{
+
74 if (fuse_instance == NULL)
+
75 return;
+
76
+
77 fuse_session_exit(fuse_instance);
+
78
+
79 fuse_remove_signal_handlers(fuse_instance);
+
80 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
81 dump_stack();
+
82 abort();
+
83}
+
84
+
85
+
86static void do_nothing(int sig)
+
87{
+
88 (void) sig;
+
89}
+
90
+
91static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
92{
+
93 struct sigaction sa;
+
94 struct sigaction old_sa;
+
95
+
96 memset(&sa, 0, sizeof(struct sigaction));
+
97 sa.sa_handler = remove ? SIG_DFL : handler;
+
98 sigemptyset(&(sa.sa_mask));
+
99 sa.sa_flags = 0;
+
100
+
101 if (sigaction(sig, NULL, &old_sa) == -1) {
+
102 perror("fuse: cannot get old signal handler");
+
103 return -1;
+
104 }
+
105
+
106 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
107 sigaction(sig, &sa, NULL) == -1) {
+
108 perror("fuse: cannot set signal handler");
+
109 return -1;
+
110 }
+
111 return 0;
+
112}
+
113
+
114static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
115 void (*handler)(int))
+
116{
+
117 for (int idx = 0; idx < nr_signals; idx++) {
+
118 int signal = signals[idx];
+
119
+
120 /*
+
121 * If we used SIG_IGN instead of the do_nothing function,
+
122 * then we would be unable to tell if we set SIG_IGN (and
+
123 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
124 * or if it was already set to SIG_IGN (and should be left
+
125 * untouched.
+
126 */
+
127 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
128 fuse_log(FUSE_LOG_ERR,
+
129 "Failed to install signal handler for sig %d\n",
+
130 signal);
+
131 return -1;
+
132 }
+
133 }
+
134
+
135 return 0;
+
136}
+
137
+
+
138int fuse_set_signal_handlers(struct fuse_session *se)
+
139{
+
140 size_t nr_signals;
+
141 int rc;
+
142
+
143 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
144 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
145 if (rc < 0)
+
146 return rc;
+
147
+
148 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
149 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
150 if (rc < 0)
+
151 return rc;
+
152
+
153 if (fuse_instance == NULL)
+
154 fuse_instance = se;
+
155 return 0;
+
156}
+
+
157
+
+
158int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
159{
+
160 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
161
+
162 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
163 exit_backtrace);
+
164 if (rc < 0)
+
165 return rc;
+
166
+
167 if (fuse_instance == NULL)
+
168 fuse_instance = se;
+
169
+
170 return 0;
+
171}
+
+
172
+
173static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
174 void (*handler)(int))
+
175{
+
176 for (int idx = 0; idx < nr_signals; idx++)
+
177 set_one_signal_handler(signals[idx], handler, 1);
+
178}
+
179
+
+
180void fuse_remove_signal_handlers(struct fuse_session *se)
+
181{
+
182 size_t nr_signals;
+
183
+
184 if (fuse_instance != se)
+
185 fuse_log(FUSE_LOG_ERR,
+
186 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
187 else
+
188 fuse_instance = NULL;
+
189
+
190 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
191 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
192
+
193 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
194 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
195
+
196 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
197 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
198}
+
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2helper_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2helper_8c_source.html new file mode 100644 index 0000000..a449fdd --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2helper_8c_source.html @@ -0,0 +1,616 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
+ +
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
+ +
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
+
252
+
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
+
306
+
307/* Not symboled, as not part of the official API */
+
308int fuse_main_real_versioned(int argc, char *argv[],
+
309 const struct fuse_operations *op, size_t op_size,
+
310 struct libfuse_version *version, void *user_data);
+
+
311int fuse_main_real_versioned(int argc, char *argv[],
+
312 const struct fuse_operations *op, size_t op_size,
+
313 struct libfuse_version *version, void *user_data)
+
314{
+
315 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
316 struct fuse *fuse;
+
317 struct fuse_cmdline_opts opts;
+
318 int res;
+
319 struct fuse_loop_config *loop_config = NULL;
+
320
+
321 if (fuse_parse_cmdline(&args, &opts) != 0)
+
322 return 1;
+
323
+
324 if (opts.show_version) {
+
325 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
327 res = 0;
+
328 goto out1;
+
329 }
+
330
+
331 if (opts.show_help) {
+
332 if(args.argv[0][0] != '\0')
+
333 printf("usage: %s [options] <mountpoint>\n\n",
+
334 args.argv[0]);
+
335 printf("FUSE options:\n");
+ +
337 fuse_lib_help(&args);
+
338 res = 0;
+
339 goto out1;
+
340 }
+
341
+
342 if (!opts.show_help &&
+
343 !opts.mountpoint) {
+
344 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
345 res = 2;
+
346 goto out1;
+
347 }
+
348
+
349 struct fuse *_fuse_new_31(struct fuse_args *args,
+
350 const struct fuse_operations *op, size_t op_size,
+
351 struct libfuse_version *version,
+
352 void *user_data);
+
353 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
354 if (fuse == NULL) {
+
355 res = 3;
+
356 goto out1;
+
357 }
+
358
+
359 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
360 res = 4;
+
361 goto out2;
+
362 }
+
363
+
364 if (fuse_daemonize(opts.foreground) != 0) {
+
365 res = 5;
+
366 goto out3;
+
367 }
+
368
+
369 struct fuse_session *se = fuse_get_session(fuse);
+
370 if (fuse_set_signal_handlers(se) != 0) {
+
371 res = 6;
+
372 goto out3;
+
373 }
+
374
+
375 if (opts.singlethread)
+
376 res = fuse_loop(fuse);
+
377 else {
+
378 loop_config = fuse_loop_cfg_create();
+
379 if (loop_config == NULL) {
+
380 res = 7;
+
381 goto out3;
+
382 }
+
383
+
384 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
385
+
386 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
387 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
388 res = fuse_loop_mt(fuse, loop_config);
+
389 }
+
390 if (res)
+
391 res = 8;
+
392
+ +
394out3:
+
395 fuse_unmount(fuse);
+
396out2:
+
397 fuse_destroy(fuse);
+
398out1:
+
399 fuse_loop_cfg_destroy(loop_config);
+
400 free(opts.mountpoint);
+
401 fuse_opt_free_args(&args);
+
402 return res;
+
403}
+
+
404
+
405/* Not symboled, as not part of the official API */
+
406int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
407 size_t op_size, void *user_data);
+
408int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
409 size_t op_size, void *user_data)
+
410{
+
411 struct libfuse_version version = { 0 };
+
412 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
413 user_data);
+
414}
+
415
+
+
416void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
417 struct fuse_conn_info *conn)
+
418{
+
419 if(opts->set_max_write)
+
420 conn->max_write = opts->max_write;
+
421 if(opts->set_max_background)
+
422 conn->max_background = opts->max_background;
+
423 if(opts->set_congestion_threshold)
+
424 conn->congestion_threshold = opts->congestion_threshold;
+
425 if(opts->set_time_gran)
+
426 conn->time_gran = opts->time_gran;
+
427 if(opts->set_max_readahead)
+
428 conn->max_readahead = opts->max_readahead;
+
429
+
430#define LL_ENABLE(cond,cap) \
+
431 if (cond) conn->want |= (cap)
+
432#define LL_DISABLE(cond,cap) \
+
433 if (cond) conn->want &= ~(cap)
+
434
+
435 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
436 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
437
+
438 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
439 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
440
+
441 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
442 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
443
+
444 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
445 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
446
+
447 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
448 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
449
+
450 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
451 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
452
+
453 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
454 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
455
+
456 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
457 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
458
+
459 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
460 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
461}
+
+
462
+
+
463struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
464{
+
465 struct fuse_conn_info_opts *opts;
+
466
+
467 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
468 if(opts == NULL) {
+
469 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
470 return NULL;
+
471 }
+
472 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
473 free(opts);
+
474 return NULL;
+
475 }
+
476 return opts;
+
477}
+
+
478
+
+
479int fuse_open_channel(const char *mountpoint, const char* options)
+
480{
+
481 struct mount_opts *opts = NULL;
+
482 int fd = -1;
+
483 const char *argv[] = { "", "-o", options };
+
484 int argc = sizeof(argv) / sizeof(argv[0]);
+
485 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
486
+
487 opts = parse_mount_opts(&args);
+
488 if (opts == NULL)
+
489 return -1;
+
490
+
491 fd = fuse_kern_mount(mountpoint, opts);
+
492 destroy_mount_opts(opts);
+
493
+
494 return fd;
+
495}
+
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:479
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:463
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:416
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2modules_2iconv_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..3c720b3 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2modules_2iconv_8c_source.html @@ -0,0 +1,816 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571static void *iconv_init(struct fuse_conn_info *conn,
+
572 struct fuse_config *cfg)
+
573{
+
574 struct iconv *ic = iconv_get();
+
575 fuse_fs_init(ic->next, conn, cfg);
+
576 /* Don't touch cfg->nullpath_ok, we can work with
+
577 either */
+
578 return ic;
+
579}
+
580
+
581static void iconv_destroy(void *data)
+
582{
+
583 struct iconv *ic = data;
+
584 fuse_fs_destroy(ic->next);
+
585 iconv_close(ic->tofs);
+
586 iconv_close(ic->fromfs);
+
587 pthread_mutex_destroy(&ic->lock);
+
588 free(ic->from_code);
+
589 free(ic->to_code);
+
590 free(ic);
+
591}
+
592
+
593static const struct fuse_operations iconv_oper = {
+
594 .destroy = iconv_destroy,
+
595 .init = iconv_init,
+
596 .getattr = iconv_getattr,
+
597 .access = iconv_access,
+
598 .readlink = iconv_readlink,
+
599 .opendir = iconv_opendir,
+
600 .readdir = iconv_readdir,
+
601 .releasedir = iconv_releasedir,
+
602 .mknod = iconv_mknod,
+
603 .mkdir = iconv_mkdir,
+
604 .symlink = iconv_symlink,
+
605 .unlink = iconv_unlink,
+
606 .rmdir = iconv_rmdir,
+
607 .rename = iconv_rename,
+
608 .link = iconv_link,
+
609 .chmod = iconv_chmod,
+
610 .chown = iconv_chown,
+
611 .truncate = iconv_truncate,
+
612 .utimens = iconv_utimens,
+
613 .create = iconv_create,
+
614 .open = iconv_open_file,
+
615 .read_buf = iconv_read_buf,
+
616 .write_buf = iconv_write_buf,
+
617 .statfs = iconv_statfs,
+
618 .flush = iconv_flush,
+
619 .release = iconv_release,
+
620 .fsync = iconv_fsync,
+
621 .fsyncdir = iconv_fsyncdir,
+
622 .setxattr = iconv_setxattr,
+
623 .getxattr = iconv_getxattr,
+
624 .listxattr = iconv_listxattr,
+
625 .removexattr = iconv_removexattr,
+
626 .lock = iconv_lock,
+
627 .flock = iconv_flock,
+
628 .bmap = iconv_bmap,
+
629 .lseek = iconv_lseek,
+
630};
+
631
+
632static const struct fuse_opt iconv_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
636 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
638};
+
639
+
640static void iconv_help(void)
+
641{
+
642 char *charmap;
+
643 const char *old = setlocale(LC_CTYPE, "");
+
644
+
645 charmap = strdup(nl_langinfo(CODESET));
+
646 if (old)
+
647 setlocale(LC_CTYPE, old);
+
648 else
+
649 perror("setlocale");
+
650
+
651 printf(
+
652" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
653" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
654 charmap);
+
655 free(charmap);
+
656}
+
657
+
658static int iconv_opt_proc(void *data, const char *arg, int key,
+
659 struct fuse_args *outargs)
+
660{
+
661 (void) data; (void) arg; (void) outargs;
+
662
+
663 if (!key) {
+
664 iconv_help();
+
665 return -1;
+
666 }
+
667
+
668 return 1;
+
669}
+
670
+
671static struct fuse_fs *iconv_new(struct fuse_args *args,
+
672 struct fuse_fs *next[])
+
673{
+
674 struct fuse_fs *fs;
+
675 struct iconv *ic;
+
676 const char *old = NULL;
+
677 const char *from;
+
678 const char *to;
+
679
+
680 ic = calloc(1, sizeof(struct iconv));
+
681 if (ic == NULL) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
683 return NULL;
+
684 }
+
685
+
686 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
687 goto out_free;
+
688
+
689 if (!next[0] || next[1]) {
+
690 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
691 goto out_free;
+
692 }
+
693
+
694 from = ic->from_code ? ic->from_code : "UTF-8";
+
695 to = ic->to_code ? ic->to_code : "";
+
696 /* FIXME: detect charset equivalence? */
+
697 if (!to[0])
+
698 old = setlocale(LC_CTYPE, "");
+
699 ic->tofs = iconv_open(from, to);
+
700 if (ic->tofs == (iconv_t) -1) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
702 to, from);
+
703 goto out_free;
+
704 }
+
705 ic->fromfs = iconv_open(to, from);
+
706 if (ic->tofs == (iconv_t) -1) {
+
707 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
708 from, to);
+
709 goto out_iconv_close_to;
+
710 }
+
711 if (old) {
+
712 setlocale(LC_CTYPE, old);
+
713 old = NULL;
+
714 }
+
715
+
716 ic->next = next[0];
+
717 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
718 if (!fs)
+
719 goto out_iconv_close_from;
+
720
+
721 return fs;
+
722
+
723out_iconv_close_from:
+
724 iconv_close(ic->fromfs);
+
725out_iconv_close_to:
+
726 iconv_close(ic->tofs);
+
727out_free:
+
728 free(ic->from_code);
+
729 free(ic->to_code);
+
730 free(ic);
+
731 if (old) {
+
732 setlocale(LC_CTYPE, old);
+
733 }
+
734 return NULL;
+
735}
+
736
+
737FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1415
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2modules_2subdir_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..1465556 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2modules_2subdir_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556static void *subdir_init(struct fuse_conn_info *conn,
+
557 struct fuse_config *cfg)
+
558{
+
559 struct subdir *d = subdir_get();
+
560 fuse_fs_init(d->next, conn, cfg);
+
561 /* Don't touch cfg->nullpath_ok, we can work with
+
562 either */
+
563 return d;
+
564}
+
565
+
566static void subdir_destroy(void *data)
+
567{
+
568 struct subdir *d = data;
+
569 fuse_fs_destroy(d->next);
+
570 free(d->base);
+
571 free(d);
+
572}
+
573
+
574static const struct fuse_operations subdir_oper = {
+
575 .destroy = subdir_destroy,
+
576 .init = subdir_init,
+
577 .getattr = subdir_getattr,
+
578 .access = subdir_access,
+
579 .readlink = subdir_readlink,
+
580 .opendir = subdir_opendir,
+
581 .readdir = subdir_readdir,
+
582 .releasedir = subdir_releasedir,
+
583 .mknod = subdir_mknod,
+
584 .mkdir = subdir_mkdir,
+
585 .symlink = subdir_symlink,
+
586 .unlink = subdir_unlink,
+
587 .rmdir = subdir_rmdir,
+
588 .rename = subdir_rename,
+
589 .link = subdir_link,
+
590 .chmod = subdir_chmod,
+
591 .chown = subdir_chown,
+
592 .truncate = subdir_truncate,
+
593 .utimens = subdir_utimens,
+
594 .create = subdir_create,
+
595 .open = subdir_open,
+
596 .read_buf = subdir_read_buf,
+
597 .write_buf = subdir_write_buf,
+
598 .statfs = subdir_statfs,
+
599 .flush = subdir_flush,
+
600 .release = subdir_release,
+
601 .fsync = subdir_fsync,
+
602 .fsyncdir = subdir_fsyncdir,
+
603 .setxattr = subdir_setxattr,
+
604 .getxattr = subdir_getxattr,
+
605 .listxattr = subdir_listxattr,
+
606 .removexattr = subdir_removexattr,
+
607 .lock = subdir_lock,
+
608 .flock = subdir_flock,
+
609 .bmap = subdir_bmap,
+
610 .lseek = subdir_lseek,
+
611};
+
612
+
613static const struct fuse_opt subdir_opts[] = {
+
614 FUSE_OPT_KEY("-h", 0),
+
615 FUSE_OPT_KEY("--help", 0),
+
616 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
617 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
618 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
620};
+
621
+
622static void subdir_help(void)
+
623{
+
624 printf(
+
625" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
626" -o [no]rellinks transform absolute symlinks to relative\n");
+
627}
+
628
+
629static int subdir_opt_proc(void *data, const char *arg, int key,
+
630 struct fuse_args *outargs)
+
631{
+
632 (void) data; (void) arg; (void) outargs;
+
633
+
634 if (!key) {
+
635 subdir_help();
+
636 return -1;
+
637 }
+
638
+
639 return 1;
+
640}
+
641
+
642static struct fuse_fs *subdir_new(struct fuse_args *args,
+
643 struct fuse_fs *next[])
+
644{
+
645 struct fuse_fs *fs;
+
646 struct subdir *d;
+
647
+
648 d = calloc(1, sizeof(struct subdir));
+
649 if (d == NULL) {
+
650 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
651 return NULL;
+
652 }
+
653
+
654 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
655 goto out_free;
+
656
+
657 if (!next[0] || next[1]) {
+
658 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
659 goto out_free;
+
660 }
+
661
+
662 if (!d->base) {
+
663 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
664 goto out_free;
+
665 }
+
666
+
667 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
668 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
669 if (!tmp) {
+
670 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
671 goto out_free;
+
672 }
+
673 d->base = tmp;
+
674 strcat(d->base, "/");
+
675 }
+
676 d->baselen = strlen(d->base);
+
677 d->next = next[0];
+
678 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
679 if (!fs)
+
680 goto out_free;
+
681 return fs;
+
682
+
683out_free:
+
684 free(d->base);
+
685 free(d);
+
686 return NULL;
+
687}
+
688
+
689FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1415
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2mount_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2mount_8c_source.html new file mode 100644 index 0000000..149957e --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2mount_8c_source.html @@ -0,0 +1,791 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52
+
53#ifndef MS_DIRSYNC
+
54#define MS_DIRSYNC 128
+
55#endif
+
56
+
57enum {
+
58 KEY_KERN_FLAG,
+
59 KEY_KERN_OPT,
+
60 KEY_FUSERMOUNT_OPT,
+
61 KEY_SUBTYPE_OPT,
+
62 KEY_MTAB_OPT,
+
63 KEY_ALLOW_OTHER,
+
64 KEY_RO,
+
65};
+
66
+
67struct mount_opts {
+
68 int allow_other;
+
69 int flags;
+
70 int auto_unmount;
+
71 int blkdev;
+
72 char *fsname;
+
73 char *subtype;
+
74 char *subtype_opt;
+
75 char *mtab_opts;
+
76 char *fusermount_opts;
+
77 char *kernel_opts;
+
78 unsigned max_read;
+
79};
+
80
+
81#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
82
+
83static const struct fuse_opt fuse_mount_opts[] = {
+
84 FUSE_MOUNT_OPT("allow_other", allow_other),
+
85 FUSE_MOUNT_OPT("blkdev", blkdev),
+
86 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
87 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
88 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
89 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
90 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
91 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
92 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
95 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
96 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
103 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-r", KEY_RO),
+
105 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
106 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
122};
+
123
+
124/*
+
125 * Running fusermount by calling 'posix_spawn'
+
126 *
+
127 * @param out_pid might be NULL
+
128 */
+
129static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
130 char const * const argv[], pid_t *out_pid)
+
131{
+
132 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
133 pid_t pid;
+
134
+
135 /* See man 7 environ for the global environ pointer */
+
136
+
137 /* first try the install path */
+
138 int status = posix_spawn(&pid, full_path, action, NULL,
+
139 (char * const *) argv, environ);
+
140 if (status != 0) {
+
141 /* if that fails, try a system install */
+
142 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
143 (char * const *) argv, environ);
+
144 }
+
145
+
146 if (status != 0) {
+
147 fuse_log(FUSE_LOG_ERR,
+
148 "On calling fusermount posix_spawn failed: %s\n",
+
149 strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0);
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawaning %s to unumount failed",
+
345 FUSERMOUNT_PROG);
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addclose(&action, 1);
+
394 posix_spawn_file_actions_addclose(&action, 2);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed");
+
410 return -1;
+
411 }
+
412 // passed to child now, so can close here.
+
413 close(fds[0]);
+
414
+
415 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
416 // process exits.
+
417 return 0;
+
418 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
419}
+
420
+
421static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
422 const char *opts, int quiet)
+
423{
+
424 int fds[2];
+
425 pid_t pid;
+
426 int res;
+
427
+
428 if (!mountpoint) {
+
429 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
430 return -1;
+
431 }
+
432
+
433 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
434 if(res == -1) {
+
435 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
436 FUSERMOUNT_PROG, strerror(errno));
+
437 return -1;
+
438 }
+
439
+
440 char arg_fd_entry[30];
+
441 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
442 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
443 /*
+
444 * This helps to identify the FD hold by parent process.
+
445 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
446 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
447 * One potential use case is to satisfy FD-Leak checks.
+
448 */
+
449 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
450 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
451
+
452 char const *const argv[] = {
+
453 FUSERMOUNT_PROG,
+
454 "-o", opts ? opts : "",
+
455 "--",
+
456 mountpoint,
+
457 NULL,
+
458 };
+
459
+
460
+
461 posix_spawn_file_actions_t action;
+
462 posix_spawn_file_actions_init(&action);
+
463
+
464 if (quiet) {
+
465 posix_spawn_file_actions_addclose(&action, 1);
+
466 posix_spawn_file_actions_addclose(&action, 2);
+
467 }
+
468 posix_spawn_file_actions_addclose(&action, fds[1]);
+
469
+
470 int status = fusermount_posix_spawn(&action, argv, &pid);
+
471
+
472 posix_spawn_file_actions_destroy(&action);
+
473
+
474 if(status != 0) {
+
475 close(fds[0]);
+
476 close(fds[1]);
+
477 fuse_log(FUSE_LOG_ERR, "posix_spawnp() for %s failed",
+
478 FUSERMOUNT_PROG, strerror(errno));
+
479 return -1;
+
480 }
+
481
+
482 // passed to child now, so can close here.
+
483 close(fds[0]);
+
484
+
485 int fd = receive_fd(fds[1]);
+
486
+
487 if (!mo->auto_unmount) {
+
488 /* with auto_unmount option fusermount3 will not exit until
+
489 this socket is closed */
+
490 close(fds[1]);
+
491 waitpid(pid, NULL, 0); /* bury zombie */
+
492 }
+
493
+
494 if (fd >= 0)
+
495 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
496
+
497 return fd;
+
498}
+
499
+
500#ifndef O_CLOEXEC
+
501#define O_CLOEXEC 0
+
502#endif
+
503
+
504static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
505 const char *mnt_opts)
+
506{
+
507 char tmp[128];
+
508 const char *devname = "/dev/fuse";
+
509 char *source = NULL;
+
510 char *type = NULL;
+
511 struct stat stbuf;
+
512 int fd;
+
513 int res;
+
514
+
515 if (!mnt) {
+
516 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
517 return -1;
+
518 }
+
519
+
520 res = stat(mnt, &stbuf);
+
521 if (res == -1) {
+
522 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
523 mnt, strerror(errno));
+
524 return -1;
+
525 }
+
526
+
527 fd = open(devname, O_RDWR | O_CLOEXEC);
+
528 if (fd == -1) {
+
529 if (errno == ENODEV || errno == ENOENT)
+
530 fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n");
+
531 else
+
532 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
533 devname, strerror(errno));
+
534 return -1;
+
535 }
+
536 if (!O_CLOEXEC)
+
537 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
538
+
539 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
540 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
541
+
542 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
543 if (res == -1)
+
544 goto out_close;
+
545
+
546 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
547 (mo->subtype ? strlen(mo->subtype) : 0) +
+
548 strlen(devname) + 32);
+
549
+
550 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
551 if (!type || !source) {
+
552 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
553 goto out_close;
+
554 }
+
555
+
556 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
557 if (mo->subtype) {
+
558 strcat(type, ".");
+
559 strcat(type, mo->subtype);
+
560 }
+
561 strcpy(source,
+
562 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
563
+
564 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
565 if (res == -1 && errno == ENODEV && mo->subtype) {
+
566 /* Probably missing subtype support */
+
567 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
568 if (mo->fsname) {
+
569 if (!mo->blkdev)
+
570 sprintf(source, "%s#%s", mo->subtype,
+
571 mo->fsname);
+
572 } else {
+
573 strcpy(source, type);
+
574 }
+
575 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
576 }
+
577 if (res == -1) {
+
578 /*
+
579 * Maybe kernel doesn't support unprivileged mounts, in this
+
580 * case try falling back to fusermount3
+
581 */
+
582 if (errno == EPERM) {
+
583 res = -2;
+
584 } else {
+
585 int errno_save = errno;
+
586 if (mo->blkdev && errno == ENODEV &&
+
587 !fuse_mnt_check_fuseblk())
+
588 fuse_log(FUSE_LOG_ERR,
+
589 "fuse: 'fuseblk' support missing\n");
+
590 else
+
591 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
592 strerror(errno_save));
+
593 }
+
594
+
595 goto out_close;
+
596 }
+
597
+
598#ifndef IGNORE_MTAB
+
599 if (geteuid() == 0) {
+
600 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
601 res = -1;
+
602 if (!newmnt)
+
603 goto out_umount;
+
604
+
605 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
606 mnt_opts);
+
607 free(newmnt);
+
608 if (res == -1)
+
609 goto out_umount;
+
610 }
+
611#endif /* IGNORE_MTAB */
+
612 free(type);
+
613 free(source);
+
614
+
615 return fd;
+
616
+
617out_umount:
+
618 umount2(mnt, 2); /* lazy umount */
+
619out_close:
+
620 free(type);
+
621 free(source);
+
622 close(fd);
+
623 return res;
+
624}
+
625
+
626static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
627{
+
628 int i;
+
629
+
630 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
631 return -1;
+
632
+
633 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
634 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
635 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
636 return -1;
+
637 }
+
638 return 0;
+
639}
+
640
+
641struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
642{
+
643 struct mount_opts *mo;
+
644
+
645 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
646 if (mo == NULL)
+
647 return NULL;
+
648
+
649 memset(mo, 0, sizeof(struct mount_opts));
+
650 mo->flags = MS_NOSUID | MS_NODEV;
+
651
+
652 if (args &&
+
653 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
654 goto err_out;
+
655
+
656 return mo;
+
657
+
658err_out:
+
659 destroy_mount_opts(mo);
+
660 return NULL;
+
661}
+
662
+
663void destroy_mount_opts(struct mount_opts *mo)
+
664{
+
665 free(mo->fsname);
+
666 free(mo->subtype);
+
667 free(mo->fusermount_opts);
+
668 free(mo->subtype_opt);
+
669 free(mo->kernel_opts);
+
670 free(mo->mtab_opts);
+
671 free(mo);
+
672}
+
673
+
674
+
675int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
676{
+
677 int res = -1;
+
678 char *mnt_opts = NULL;
+
679
+
680 res = -1;
+
681 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
682 goto out;
+
683 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
684 goto out;
+
685 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
686 goto out;
+
687
+
688 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
689 if (res >= 0 && mo->auto_unmount) {
+
690 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
691 // Something went wrong, let's umount like in fuse_mount_sys.
+
692 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
693 res = -1;
+
694 }
+
695 } else if (res == -2) {
+
696 if (mo->fusermount_opts &&
+
697 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
698 goto out;
+
699
+
700 if (mo->subtype) {
+
701 char *tmp_opts = NULL;
+
702
+
703 res = -1;
+
704 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
705 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
706 free(tmp_opts);
+
707 goto out;
+
708 }
+
709
+
710 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
711 free(tmp_opts);
+
712 if (res == -1)
+
713 res = fuse_mount_fusermount(mountpoint, mo,
+
714 mnt_opts, 0);
+
715 } else {
+
716 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
717 }
+
718 }
+
719out:
+
720 free(mnt_opts);
+
721 return res;
+
722}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2mount__bsd_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..90ffb40 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2mount__bsd_8c_source.html @@ -0,0 +1,357 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <sys/param.h>
+
17#include "fuse_mount_compat.h"
+
18
+
19#include <sys/stat.h>
+
20#include <sys/wait.h>
+
21#include <sys/sysctl.h>
+
22#include <sys/user.h>
+
23#include <stdio.h>
+
24#include <stdlib.h>
+
25#include <unistd.h>
+
26#include <stddef.h>
+
27#include <fcntl.h>
+
28#include <errno.h>
+
29#include <string.h>
+
30#include <paths.h>
+
31#include <limits.h>
+
32
+
33#define FUSERMOUNT_PROG "mount_fusefs"
+
34#define FUSE_DEV_TRUNK "/dev/fuse"
+
35
+
36enum {
+
37 KEY_RO,
+
38 KEY_KERN
+
39};
+
40
+
41struct mount_opts {
+
42 int allow_other;
+
43 char *kernel_opts;
+
44 unsigned max_read;
+
45};
+
46
+
47#define FUSE_DUAL_OPT_KEY(templ, key) \
+
48 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
49
+
50static const struct fuse_opt fuse_mount_opts[] = {
+
51 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
52 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
53 FUSE_OPT_KEY("-r", KEY_RO),
+
54 /* standard FreeBSD mount options */
+
55 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
75 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
76 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
78 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
79 /* options supported under both Linux and FBSD */
+
80 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
81 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
82 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
83 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
84 /* FBSD FUSE specific mount options */
+
85 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
86 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
87 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
88 FUSE_OPT_KEY("nosync_unmount", KEY_KERN),
+
89#if __FreeBSD_version >= 1200519
+
90 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
91#endif
+
92 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
93 /*
+
94 * Linux specific mount options, but let just the mount util
+
95 * handle them
+
96 */
+
97 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
99};
+
100
+
101void fuse_mount_version(void)
+
102{
+
103 system(FUSERMOUNT_PROG " --version");
+
104}
+
105
+
106unsigned get_max_read(struct mount_opts *o)
+
107{
+
108 return o->max_read;
+
109}
+
110
+
111static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
112 struct fuse_args *outargs)
+
113{
+
114 (void) outargs;
+
115 struct mount_opts *mo = data;
+
116
+
117 switch (key) {
+
118 case KEY_RO:
+
119 arg = "ro";
+
120 /* fall through */
+
121
+
122 case KEY_KERN:
+
123 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
124 }
+
125
+
126 /* Pass through unknown options */
+
127 return 1;
+
128}
+
129
+
130void fuse_kern_unmount(const char *mountpoint, int fd)
+
131{
+
132 close(fd);
+
133 unmount(mountpoint, MNT_FORCE);
+
134}
+
135
+
136/* Check if kernel is doing init in background */
+
137static int init_backgrounded(void)
+
138{
+
139 unsigned ibg;
+
140 size_t len;
+
141
+
142 len = sizeof(ibg);
+
143
+
144 if (sysctlbyname("vfs.fuse.init_backgrounded", &ibg, &len, NULL, 0))
+
145 return 0;
+
146
+
147 return ibg;
+
148}
+
149
+
150
+
151static int fuse_mount_core(const char *mountpoint, const char *opts)
+
152{
+
153 const char *mountprog = FUSERMOUNT_PROG;
+
154 int fd;
+
155 char *fdnam, *dev;
+
156 pid_t pid, cpid;
+
157 int status;
+
158 int err;
+
159
+
160 fdnam = getenv("FUSE_DEV_FD");
+
161
+
162 if (fdnam) {
+
163 err = libfuse_strtol(fdnam, &fd);
+
164 if (err) {
+
165 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
166 return -1;
+
167 }
+
168
+
169 goto mount;
+
170 }
+
171
+
172 dev = getenv("FUSE_DEV_NAME");
+
173
+
174 if (! dev)
+
175 dev = (char *)FUSE_DEV_TRUNK;
+
176
+
177 if ((fd = open(dev, O_RDWR)) < 0) {
+
178 perror("fuse: failed to open fuse device");
+
179 return -1;
+
180 }
+
181
+
182mount:
+
183 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
184 goto out;
+
185
+
186 pid = fork();
+
187 cpid = pid;
+
188
+
189 if (pid == -1) {
+
190 perror("fuse: fork() failed");
+
191 close(fd);
+
192 return -1;
+
193 }
+
194
+
195 if (pid == 0) {
+
196 if (! init_backgrounded()) {
+
197 /*
+
198 * If init is not backgrounded, we have to
+
199 * call the mount util backgrounded, to avoid
+
200 * deadlock.
+
201 */
+
202
+
203 pid = fork();
+
204
+
205 if (pid == -1) {
+
206 perror("fuse: fork() failed");
+
207 close(fd);
+
208 exit(1);
+
209 }
+
210 }
+
211
+
212 if (pid == 0) {
+
213 const char *argv[32];
+
214 int a = 0;
+
215 int ret = -1;
+
216
+
217 if (! fdnam)
+
218 {
+
219 ret = asprintf(&fdnam, "%d", fd);
+
220 if(ret == -1)
+
221 {
+
222 perror("fuse: failed to assemble mount arguments");
+
223 close(fd);
+
224 exit(1);
+
225 }
+
226 }
+
227
+
228 argv[a++] = mountprog;
+
229 if (opts) {
+
230 argv[a++] = "-o";
+
231 argv[a++] = opts;
+
232 }
+
233 argv[a++] = fdnam;
+
234 argv[a++] = mountpoint;
+
235 argv[a++] = NULL;
+
236 execvp(mountprog, (char **) argv);
+
237 perror("fuse: failed to exec mount program");
+
238 free(fdnam);
+
239 exit(1);
+
240 }
+
241
+
242 exit(0);
+
243 }
+
244
+
245 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
246 perror("fuse: failed to mount file system");
+
247 close(fd);
+
248 return -1;
+
249 }
+
250
+
251out:
+
252 return fd;
+
253}
+
254
+
255struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
256{
+
257 struct mount_opts *mo;
+
258
+
259 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
260 if (mo == NULL)
+
261 return NULL;
+
262
+
263 memset(mo, 0, sizeof(struct mount_opts));
+
264
+
265 if (args &&
+
266 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
267 goto err_out;
+
268
+
269 return mo;
+
270
+
271err_out:
+
272 destroy_mount_opts(mo);
+
273 return NULL;
+
274}
+
275
+
276void destroy_mount_opts(struct mount_opts *mo)
+
277{
+
278 free(mo->kernel_opts);
+
279 free(mo);
+
280}
+
281
+
282int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
283{
+
284 /* mount util should not try to spawn the daemon */
+
285 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
286 /* to notify the mount util it's called from lib */
+
287 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
288
+
289 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
290}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8c_source.html new file mode 100644 index 0000000..27f0a02 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8c_source.html @@ -0,0 +1,433 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78 }
+
79
+
80 return 1;
+
81}
+
82#endif /* IGNORE_MTAB */
+
83
+
84static int add_mount(const char *progname, const char *fsname,
+
85 const char *mnt, const char *type, const char *opts)
+
86{
+
87 int res;
+
88 int status;
+
89 sigset_t blockmask;
+
90 sigset_t oldmask;
+
91
+
92 sigemptyset(&blockmask);
+
93 sigaddset(&blockmask, SIGCHLD);
+
94 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
95 if (res == -1) {
+
96 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
97 return -1;
+
98 }
+
99
+
100 res = fork();
+
101 if (res == -1) {
+
102 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
103 goto out_restore;
+
104 }
+
105 if (res == 0) {
+
106 char *env = NULL;
+
107
+
108 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
109
+
110 if(setuid(geteuid()) == -1) {
+
111 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
112 res = -1;
+
113 goto out_restore;
+
114 }
+
115
+
116 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
117 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
118 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
119 progname, strerror(errno));
+
120 exit(1);
+
121 }
+
122 res = waitpid(res, &status, 0);
+
123 if (res == -1)
+
124 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
125
+
126 if (status != 0)
+
127 res = -1;
+
128
+
129 out_restore:
+
130 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
131
+
132 return res;
+
133}
+
134
+
135int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
136 const char *mnt, const char *type, const char *opts)
+
137{
+
138 if (!mtab_needs_update(mnt))
+
139 return 0;
+
140
+
141 return add_mount(progname, fsname, mnt, type, opts);
+
142}
+
143
+
144static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
145{
+
146 int res;
+
147 int status;
+
148 sigset_t blockmask;
+
149 sigset_t oldmask;
+
150
+
151 sigemptyset(&blockmask);
+
152 sigaddset(&blockmask, SIGCHLD);
+
153 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
154 if (res == -1) {
+
155 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
156 return -1;
+
157 }
+
158
+
159 res = fork();
+
160 if (res == -1) {
+
161 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
162 goto out_restore;
+
163 }
+
164 if (res == 0) {
+
165 char *env = NULL;
+
166
+
167 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
168
+
169 if(setuid(geteuid()) == -1) {
+
170 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
171 res = -1;
+
172 goto out_restore;
+
173 }
+
174
+
175 if (lazy) {
+
176 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
177 "-l", NULL, &env);
+
178 } else {
+
179 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
180 NULL, &env);
+
181 }
+
182 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
183 progname, strerror(errno));
+
184 exit(1);
+
185 }
+
186 res = waitpid(res, &status, 0);
+
187 if (res == -1)
+
188 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
189
+
190 if (status != 0) {
+
191 res = -1;
+
192 }
+
193
+
194 out_restore:
+
195 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
196 return res;
+
197
+
198}
+
199
+
200int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
201 const char *rel_mnt, int lazy)
+
202{
+
203 int res;
+
204
+
205 if (!mtab_needs_update(abs_mnt)) {
+
206 res = umount2(rel_mnt, lazy ? 2 : 0);
+
207 if (res == -1)
+
208 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
209 progname, abs_mnt, strerror(errno));
+
210 return res;
+
211 }
+
212
+
213 return exec_umount(progname, rel_mnt, lazy);
+
214}
+
215
+
216static int remove_mount(const char *progname, const char *mnt)
+
217{
+
218 int res;
+
219 int status;
+
220 sigset_t blockmask;
+
221 sigset_t oldmask;
+
222
+
223 sigemptyset(&blockmask);
+
224 sigaddset(&blockmask, SIGCHLD);
+
225 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
226 if (res == -1) {
+
227 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
228 return -1;
+
229 }
+
230
+
231 res = fork();
+
232 if (res == -1) {
+
233 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
234 goto out_restore;
+
235 }
+
236 if (res == 0) {
+
237 char *env = NULL;
+
238
+
239 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
240
+
241 if(setuid(geteuid()) == -1) {
+
242 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
243 res = -1;
+
244 goto out_restore;
+
245 }
+
246
+
247 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
248 "--fake", mnt, NULL, &env);
+
249 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
250 progname, strerror(errno));
+
251 exit(1);
+
252 }
+
253 res = waitpid(res, &status, 0);
+
254 if (res == -1)
+
255 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
256
+
257 if (status != 0)
+
258 res = -1;
+
259
+
260 out_restore:
+
261 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
262 return res;
+
263}
+
264
+
265int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
266{
+
267 if (!mtab_needs_update(mnt))
+
268 return 0;
+
269
+
270 return remove_mount(progname, mnt);
+
271}
+
272
+
273char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
274{
+
275 char buf[PATH_MAX];
+
276 char *copy;
+
277 char *dst;
+
278 char *end;
+
279 char *lastcomp;
+
280 const char *toresolv;
+
281
+
282 if (!orig[0]) {
+
283 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
284 orig);
+
285 return NULL;
+
286 }
+
287
+
288 copy = strdup(orig);
+
289 if (copy == NULL) {
+
290 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
291 return NULL;
+
292 }
+
293
+
294 toresolv = copy;
+
295 lastcomp = NULL;
+
296 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
297 if (end[0] != '/') {
+
298 char *tmp;
+
299 end[1] = '\0';
+
300 tmp = strrchr(copy, '/');
+
301 if (tmp == NULL) {
+
302 lastcomp = copy;
+
303 toresolv = ".";
+
304 } else {
+
305 lastcomp = tmp + 1;
+
306 if (tmp == copy)
+
307 toresolv = "/";
+
308 }
+
309 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
310 lastcomp = NULL;
+
311 toresolv = copy;
+
312 }
+
313 else if (tmp)
+
314 tmp[0] = '\0';
+
315 }
+
316 if (realpath(toresolv, buf) == NULL) {
+
317 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
318 strerror(errno));
+
319 free(copy);
+
320 return NULL;
+
321 }
+
322 if (lastcomp == NULL)
+
323 dst = strdup(buf);
+
324 else {
+
325 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
326 if (dst) {
+
327 unsigned buflen = strlen(buf);
+
328 if (buflen && buf[buflen-1] == '/')
+
329 sprintf(dst, "%s%s", buf, lastcomp);
+
330 else
+
331 sprintf(dst, "%s/%s", buf, lastcomp);
+
332 }
+
333 }
+
334 free(copy);
+
335 if (dst == NULL)
+
336 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
337 return dst;
+
338}
+
339
+
340int fuse_mnt_check_fuseblk(void)
+
341{
+
342 char buf[256];
+
343 FILE *f = fopen("/proc/filesystems", "r");
+
344 if (!f)
+
345 return 1;
+
346
+
347 while (fgets(buf, sizeof(buf), f))
+
348 if (strstr(buf, "fuseblk\n")) {
+
349 fclose(f);
+
350 return 1;
+
351 }
+
352
+
353 fclose(f);
+
354 return 0;
+
355}
+
356
+
357int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
358{
+
359 int fd = -1;
+
360 int len = 0;
+
361
+
362 if (mountpoint == NULL) {
+
363 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
364 return -1;
+
365 }
+
366
+
367 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
368 len == strlen(mountpoint)) {
+
369 return fd;
+
370 }
+
371
+
372 return -1;
+
373}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8h_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8h_source.html new file mode 100644 index 0000000..2661c02 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2util_8c_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2util_8c_source.html new file mode 100644 index 0000000..557fe08 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2util_8c_source.html @@ -0,0 +1,87 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1#include <stdlib.h>
+
2#include <errno.h>
+
3
+
4#include "util.h"
+
5
+
6int libfuse_strtol(const char *str, long *res)
+
7{
+
8 char *endptr;
+
9 int base = 10;
+
10 long val;
+
11
+
12 errno = 0;
+
13
+
14 if (!str)
+
15 return -EINVAL;
+
16
+
17 val = strtol(str, &endptr, base);
+
18
+
19 if (errno)
+
20 return -errno;
+
21
+
22 if (endptr == str || *endptr != '\0')
+
23 return -EINVAL;
+
24
+
25 *res = val;
+
26 return 0;
+
27}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2lib_2util_8h_source.html b/doc/html/fuse-3_817_81-rc0_2lib_2util_8h_source.html new file mode 100644 index 0000000..4db6dc0 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2lib_2util_8h_source.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
2
+
3int libfuse_strtol(const char *str, long *res);
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2test_2readdir__inode_8c_source.html b/doc/html/fuse-3_817_81-rc0_2test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..09a3efe --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test/readdir_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
readdir_inode.c
+
+
+
1/*
+
2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
+
3 * Skips '.' and '..' because readdir is not required to return them and
+
4 * some of our examples don't. However if they are returned, their d_type
+
5 * should be valid.
+
6 */
+
7
+
8#include <stdio.h>
+
9#include <string.h>
+
10#include <sys/types.h>
+
11#include <dirent.h>
+
12#include <errno.h>
+
13
+
14int main(int argc, char* argv[])
+
15{
+
16 DIR* dirp;
+
17 struct dirent* dent;
+
18
+
19 if (argc != 2) {
+
20 fprintf(stderr, "Usage: readdir_inode dir\n");
+
21 return 1;
+
22 }
+
23
+
24 dirp = opendir(argv[1]);
+
25 if (dirp == NULL) {
+
26 perror("failed to open directory");
+
27 return 2;
+
28 }
+
29
+
30 errno = 0;
+
31 dent = readdir(dirp);
+
32 while (dent != NULL) {
+
33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
+
34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
+
35 (int)dent->d_type, dent->d_name);
+
36 if ((long long)dent->d_ino < 0)
+
37 fprintf(stderr,"%s : bad d_ino %llu\n",
+
38 dent->d_name, (unsigned long long)dent->d_ino);
+
39 if ((dent->d_type < 1) || (dent->d_type > 15))
+
40 fprintf(stderr,"%s : bad d_type %d\n",
+
41 dent->d_name, (int)dent->d_type);
+
42 } else {
+
43 if (dent->d_type != DT_DIR)
+
44 fprintf(stderr,"%s : bad d_type %d\n",
+
45 dent->d_name, (int)dent->d_type);
+
46 }
+
47 dent = readdir(dirp);
+
48 }
+
49 if (errno != 0) {
+
50 perror("failed to read directory entry");
+
51 return 3;
+
52 }
+
53
+
54 closedir(dirp);
+
55
+
56 return 0;
+
57}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2test_2release__unlink__race_8c_source.html b/doc/html/fuse-3_817_81-rc0_2test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..11b4494 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test/release_unlink_race.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
release_unlink_race.c
+
+
+
1/*
+
2 This program can be distributed under the terms of the GNU GPLv2.
+
3 See the file COPYING.
+
4*/
+
5
+
6#define FUSE_USE_VERSION 31
+
7
+
8#define _GNU_SOURCE
+
9
+
10#include <fuse.h>
+
11
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16
+
17static void *xmp_init(struct fuse_conn_info *conn,
+
18 struct fuse_config *cfg)
+
19{
+
20 (void) conn;
+
21
+
22 cfg->use_ino = 1;
+
23 cfg->nullpath_ok = 1;
+
24 cfg->entry_timeout = 0;
+
25 cfg->attr_timeout = 0;
+
26 cfg->negative_timeout = 0;
+
27
+
28 return NULL;
+
29}
+
30
+
31static int xmp_getattr(const char *path, struct stat *stbuf,
+
32 struct fuse_file_info *fi)
+
33{
+
34 int res;
+
35
+
36 (void) path;
+
37
+
38 if(fi)
+
39 res = fstat(fi->fh, stbuf);
+
40 else
+
41 res = lstat(path, stbuf);
+
42 if (res == -1)
+
43 return -errno;
+
44
+
45 return 0;
+
46}
+
47
+
48static int xmp_unlink(const char *path)
+
49{
+
50 int res;
+
51
+
52 res = unlink(path);
+
53 if (res == -1)
+
54 return -errno;
+
55
+
56 return 0;
+
57}
+
58
+
59static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
60{
+
61 int res;
+
62
+
63 if (flags)
+
64 return -EINVAL;
+
65
+
66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
67
+
68 res = rename(from, to);
+
69 if (res == -1)
+
70 return -errno;
+
71
+
72 return 0;
+
73}
+
74
+
75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
76{
+
77 int fd;
+
78
+
79 fd = open(path, fi->flags, mode);
+
80 if (fd == -1)
+
81 return -errno;
+
82
+
83 fi->fh = fd;
+
84 return 0;
+
85}
+
86
+
87static int xmp_release(const char *path, struct fuse_file_info *fi)
+
88{
+
89 (void) path;
+
90
+
91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
92
+
93 close(fi->fh);
+
94
+
95 return 0;
+
96}
+
97
+
98static const struct fuse_operations xmp_oper = {
+
99 .init = xmp_init,
+
100 .getattr = xmp_getattr,
+
101 .unlink = xmp_unlink,
+
102 .rename = xmp_rename,
+
103 .create = xmp_create,
+
104 .release = xmp_release,
+
105};
+
106
+
107int main(int argc, char *argv[])
+
108{
+
109 umask(0);
+
110 return fuse_main(argc, argv, &xmp_oper, NULL);
+
111}
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2test_2stracedecode_8c_source.html b/doc/html/fuse-3_817_81-rc0_2test_2stracedecode_8c_source.html new file mode 100644 index 0000000..468e2c8 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test/stracedecode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
stracedecode.c
+
+
+
1#include <stdio.h>
+
2#include <string.h>
+
3#include "fuse_kernel.h"
+
4
+
5static struct {
+
6 const char *name;
+
7} fuse_ll_ops[] = {
+
8 [FUSE_LOOKUP] = { "LOOKUP" },
+
9 [FUSE_FORGET] = { "FORGET" },
+
10 [FUSE_GETATTR] = { "GETATTR" },
+
11 [FUSE_SETATTR] = { "SETATTR" },
+
12 [FUSE_READLINK] = { "READLINK" },
+
13 [FUSE_SYMLINK] = { "SYMLINK" },
+
14 [FUSE_MKNOD] = { "MKNOD" },
+
15 [FUSE_MKDIR] = { "MKDIR" },
+
16 [FUSE_UNLINK] = { "UNLINK" },
+
17 [FUSE_RMDIR] = { "RMDIR" },
+
18 [FUSE_RENAME] = { "RENAME" },
+
19 [FUSE_LINK] = { "LINK" },
+
20 [FUSE_OPEN] = { "OPEN" },
+
21 [FUSE_READ] = { "READ" },
+
22 [FUSE_WRITE] = { "WRITE" },
+
23 [FUSE_STATFS] = { "STATFS" },
+
24 [FUSE_RELEASE] = { "RELEASE" },
+
25 [FUSE_FSYNC] = { "FSYNC" },
+
26 [FUSE_SETXATTR] = { "SETXATTR" },
+
27 [FUSE_GETXATTR] = { "GETXATTR" },
+
28 [FUSE_LISTXATTR] = { "LISTXATTR" },
+
29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
+
30 [FUSE_FLUSH] = { "FLUSH" },
+
31 [FUSE_INIT] = { "INIT" },
+
32 [FUSE_OPENDIR] = { "OPENDIR" },
+
33 [FUSE_READDIR] = { "READDIR" },
+
34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
+
35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
+
36 [FUSE_GETLK] = { "GETLK" },
+
37 [FUSE_SETLK] = { "SETLK" },
+
38 [FUSE_SETLKW] = { "SETLKW" },
+
39 [FUSE_ACCESS] = { "ACCESS" },
+
40 [FUSE_CREATE] = { "CREATE" },
+
41 [FUSE_TMPFILE] = { "TMPFILE" },
+
42 [FUSE_INTERRUPT] = { "INTERRUPT" },
+
43 [FUSE_BMAP] = { "BMAP" },
+
44 [FUSE_DESTROY] = { "DESTROY" },
+
45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
+
46};
+
47
+
48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
49
+
50static const char *opname(enum fuse_opcode opcode)
+
51{
+
52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
53 return "???";
+
54 else
+
55 return fuse_ll_ops[opcode].name;
+
56}
+
57
+
58
+
59static void process_buf(int dir, char *buf, int len)
+
60{
+
61 static unsigned long long prevuniq = -1;
+
62 static int prevopcode;
+
63
+
64 if (!dir) {
+
65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
+
66 buf += sizeof(struct fuse_in_header);
+
67
+
68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
+
69 (unsigned long long) in->unique,
+
70 opname((enum fuse_opcode) in->opcode), in->opcode,
+
71 (unsigned long) in->nodeid, in->len, len);
+
72
+
73 switch (in->opcode) {
+
74 case FUSE_READ: {
+
75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
+
76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
+
77 arg->fh, arg->offset, arg->size, arg->read_flags,
+
78 arg->lock_owner, arg->flags);
+
79 break;
+
80 }
+
81 case FUSE_WRITE: {
+
82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
+
83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
+
84 arg->fh, arg->offset, arg->size, arg->write_flags,
+
85 arg->lock_owner, arg->flags);
+
86 break;
+
87 }
+
88 }
+
89 prevuniq = in->unique;
+
90 prevopcode = in->opcode;
+
91 } else {
+
92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
+
93 buf += sizeof(struct fuse_out_header);
+
94
+
95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
+
96 (unsigned long long) out->unique, out->error,
+
97 strerror(-out->error), out->len, len);
+
98
+
99 if (out->unique == prevuniq) {
+
100 switch (prevopcode) {
+
101 case FUSE_GETATTR: {
+
102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
+
103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
104 arg->attr_valid, arg->attr_valid_nsec,
+
105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
106 break;
+
107 }
+
108 case FUSE_LOOKUP: {
+
109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
+
110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
+
112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
113 break;
+
114 }
+
115 }
+
116 }
+
117 }
+
118
+
119}
+
120
+
121int main(void)
+
122{
+
123 FILE *in = stdin;
+
124 while (1) {
+
125 int dir;
+
126 int res;
+
127 char buf[1048576];
+
128 unsigned len = 0;
+
129
+
130 memset(buf, 0, sizeof(buf));
+
131 while (1) {
+
132 char str[32];
+
133
+
134 res = fscanf(in, "%30s", str);
+
135 if (res != 1 && feof(in))
+
136 return 0;
+
137
+
138 if (res == 0)
+
139 continue;
+
140
+
141 if (strncmp(str, "read(", 5) == 0) {
+
142 dir = 0;
+
143 break;
+
144 } else if (strncmp(str, "writev(", 7) == 0) {
+
145 dir = 1;
+
146 break;
+
147 }
+
148 }
+
149
+
150 while (1) {
+
151 int c = getc(in);
+
152 if (c == '"') {
+
153 while (1) {
+
154 int val;
+
155
+
156 c = getc(in);
+
157 if (c == EOF) {
+
158 fprintf(stderr, "eof in string\n");
+
159 break;
+
160 }
+
161 if (c == '\n') {
+
162 fprintf(stderr, "eol in string\n");
+
163 break;
+
164 }
+
165 if (c == '"')
+
166 break;
+
167 if (c != '\\') {
+
168 val = c;
+
169 } else {
+
170 c = getc(in);
+
171 switch (c) {
+
172 case 'n': val = '\n'; break;
+
173 case 'r': val = '\r'; break;
+
174 case 't': val = '\t'; break;
+
175 case '"': val = '"'; break;
+
176 case '\\': val = '\\'; break;
+
177 case 'x':
+
178 res = scanf("%x", &val);
+
179 if (res != 1) {
+
180 fprintf(stderr, "parse error\n");
+
181 continue;
+
182 }
+
183 break;
+
184 default:
+
185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
+
186 continue;
+
187 }
+
188 }
+
189 buf[len++] = val;
+
190 }
+
191 }
+
192 if (c == '\n')
+
193 break;
+
194 }
+
195 process_buf(dir, buf, len);
+
196 memset(buf, 0, len);
+
197 len = 0;
+
198 }
+
199}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2test_2test__setattr_8c_source.html b/doc/html/fuse-3_817_81-rc0_2test_2test__setattr_8c_source.html new file mode 100644 index 0000000..d61e829 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test/test_setattr.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_setattr.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <pthread.h>
+
26
+
27#ifndef __linux__
+
28#include <limits.h>
+
29#else
+
30#include <linux/limits.h>
+
31#endif
+
32
+
33#define FILE_INO 2
+
34#define FILE_NAME "truncate_me"
+
35
+
36static int got_fh;
+
37static mode_t file_mode = S_IFREG | 0644;
+
38
+
39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
40 stbuf->st_ino = ino;
+
41 if (ino == FUSE_ROOT_ID) {
+
42 stbuf->st_mode = S_IFDIR | 0755;
+
43 stbuf->st_nlink = 1;
+
44 }
+
45
+
46 else if (ino == FILE_INO) {
+
47 stbuf->st_mode = file_mode;
+
48 stbuf->st_nlink = 1;
+
49 stbuf->st_size = 0;
+
50 }
+
51
+
52 else
+
53 return -1;
+
54
+
55 return 0;
+
56}
+
57
+
58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
59 const char *name) {
+
60 struct fuse_entry_param e;
+
61 memset(&e, 0, sizeof(e));
+
62
+
63 if (parent != FUSE_ROOT_ID)
+
64 goto err_out;
+
65 else if (strcmp(name, FILE_NAME) == 0)
+
66 e.ino = FILE_INO;
+
67 else
+
68 goto err_out;
+
69
+
70 if (tfs_stat(e.ino, &e.attr) != 0)
+
71 goto err_out;
+
72 fuse_reply_entry(req, &e);
+
73 return;
+
74
+
75err_out:
+
76 fuse_reply_err(req, ENOENT);
+
77}
+
78
+
79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
80 struct fuse_file_info *fi) {
+
81 struct stat stbuf;
+
82
+
83 (void) fi;
+
84
+
85 memset(&stbuf, 0, sizeof(stbuf));
+
86 if (tfs_stat(ino, &stbuf) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else
+
89 fuse_reply_attr(req, &stbuf, 5);
+
90}
+
91
+
92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
93 struct fuse_file_info *fi) {
+
94 if (ino == FUSE_ROOT_ID)
+
95 fuse_reply_err(req, EISDIR);
+
96 else {
+
97 assert(ino == FILE_INO);
+
98 fi->fh = FILE_INO;
+
99 fuse_reply_open(req, fi);
+
100 }
+
101}
+
102
+
103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
104 int to_set, struct fuse_file_info *fi) {
+
105 if(ino != FILE_INO ||
+
106 !(to_set & FUSE_SET_ATTR_MODE)) {
+
107 fuse_reply_err(req, EINVAL);
+
108 return;
+
109 }
+
110
+
111 if(fi == NULL)
+
112 fprintf(stderr, "setattr with fi == NULL\n");
+
113 else if (fi->fh != FILE_INO)
+
114 fprintf(stderr, "setattr with wrong fi->fh\n");
+
115 else {
+
116 fprintf(stderr, "setattr ok\n");
+
117 got_fh = 1;
+
118 file_mode = attr->st_mode;
+
119 }
+
120
+
121 tfs_getattr(req, ino, fi);
+
122}
+
123
+
124static struct fuse_lowlevel_ops tfs_oper = {
+
125 .lookup = tfs_lookup,
+
126 .getattr = tfs_getattr,
+
127 .open = tfs_open,
+
128 .setattr = tfs_setattr,
+
129};
+
130
+
131static void* run_fs(void *data) {
+
132 struct fuse_session *se = (struct fuse_session*) data;
+
133 assert(fuse_session_loop(se) == 0);
+
134 return NULL;
+
135}
+
136
+
137static void test_fs(char *mountpoint) {
+
138 char fname[PATH_MAX];
+
139 int fd;
+
140
+
141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
142 mountpoint) > 0);
+
143 fd = open(fname, O_WRONLY);
+
144 if (fd == -1) {
+
145 perror(fname);
+
146 assert(0);
+
147 }
+
148
+
149 assert(fchmod(fd, 0600) == 0);
+
150 close(fd);
+
151}
+
152
+
153int main(int argc, char *argv[]) {
+
154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
155 struct fuse_session *se;
+
156 struct fuse_cmdline_opts fuse_opts;
+
157 pthread_t fs_thread;
+
158
+
159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
160#ifndef __FreeBSD__
+
161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
162#endif
+
163 se = fuse_session_new(&args, &tfs_oper,
+
164 sizeof(tfs_oper), NULL);
+
165 assert (se != NULL);
+
166 assert(fuse_set_signal_handlers(se) == 0);
+
167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
168
+
169 /* Start file-system thread */
+
170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
171
+
172 /* Do test */
+
173 test_fs(fuse_opts.mountpoint);
+
174
+
175 /* Stop file system */
+
176 assert(pthread_cancel(fs_thread) == 0);
+
177
+ +
179 assert(got_fh == 1);
+ + +
182
+
183 printf("Test completed successfully.\n");
+
184 return 0;
+
185}
+
186
+
187
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
+
fuse_ino_t ino
+ + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2test_2test__syscalls_8c_source.html b/doc/html/fuse-3_817_81-rc0_2test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..205893e --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2test_2test__syscalls_8c_source.html @@ -0,0 +1,2260 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test/test_syscalls.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_syscalls.c
+
+
+
1#define _GNU_SOURCE
+
2#include "fuse_config.h"
+
3
+
4#include <stdio.h>
+
5#include <stdlib.h>
+
6#include <stdarg.h>
+
7#include <string.h>
+
8#include <unistd.h>
+
9#include <fcntl.h>
+
10#include <dirent.h>
+
11#include <utime.h>
+
12#include <errno.h>
+
13#include <assert.h>
+
14#include <sys/socket.h>
+
15#include <sys/types.h>
+
16#include <sys/stat.h>
+
17#include <sys/un.h>
+
18
+
19#ifndef ALLPERMS
+
20# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
+
21#endif
+
22
+
23
+
24static const char *basepath;
+
25static const char *basepath_r;
+
26static char testfile[1024];
+
27static char testfile2[1024];
+
28static char testdir[1024];
+
29static char testdir2[1024];
+
30static char testsock[1024];
+
31static char subfile[1280];
+
32
+
33static char testfile_r[1024];
+
34static char testfile2_r[1024];
+
35static char testdir_r[1024];
+
36static char testdir2_r[1024];
+
37static char subfile_r[1280];
+
38
+
39static char testname[256];
+
40static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
+
41static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
+
42static const char *testdir_files[] = { "f1", "f2", NULL};
+
43static long seekdir_offsets[4];
+
44static char zerodata[4096];
+
45static int testdatalen = sizeof(testdata) - 1;
+
46static int testdata2len = sizeof(testdata2) - 1;
+
47static unsigned int testnum = 0;
+
48static unsigned int select_test = 0;
+
49static unsigned int skip_test = 0;
+
50static unsigned int unlinked_test = 0;
+
51
+
52#define MAX_ENTRIES 1024
+
53#define MAX_TESTS 100
+
54
+
55static struct test {
+
56 int fd;
+
57 struct stat stat;
+
58} tests[MAX_TESTS];
+
59
+
60static void test_perror(const char *func, const char *msg)
+
61{
+
62 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
+
63 strerror(errno));
+
64}
+
65
+
66static void test_error(const char *func, const char *msg, ...)
+
67 __attribute__ ((format (printf, 2, 3)));
+
68
+
69static void __start_test(const char *fmt, ...)
+
70 __attribute__ ((format (printf, 1, 2)));
+
71
+
72static void test_error(const char *func, const char *msg, ...)
+
73{
+
74 va_list ap;
+
75 fprintf(stderr, "%s %s() - ", testname, func);
+
76 va_start(ap, msg);
+
77 vfprintf(stderr, msg, ap);
+
78 va_end(ap);
+
79 fprintf(stderr, "\n");
+
80}
+
81
+
82static int is_dot_or_dotdot(const char *name) {
+
83 return name[0] == '.' &&
+
84 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+
85}
+
86
+
87static void success(void)
+
88{
+
89 fprintf(stderr, "%s OK\n", testname);
+
90}
+
91
+
92#define this_test (&tests[testnum-1])
+
93#define next_test (&tests[testnum])
+
94
+
95static void __start_test(const char *fmt, ...)
+
96{
+
97 unsigned int n;
+
98 va_list ap;
+
99 n = sprintf(testname, "%3i [", testnum);
+
100 va_start(ap, fmt);
+
101 n += vsprintf(testname + n, fmt, ap);
+
102 va_end(ap);
+
103 sprintf(testname + n, "]");
+
104 // Use dedicated testfile per test
+
105 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
+
106 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
+
107 if (testnum > MAX_TESTS) {
+
108 fprintf(stderr, "%s - too many tests\n", testname);
+
109 exit(1);
+
110 }
+
111 this_test->fd = -1;
+
112}
+
113
+
114#define start_test(msg, args...) { \
+
115 testnum++; \
+
116 if ((select_test && testnum != select_test) || \
+
117 (testnum == skip_test)) { \
+
118 return 0; \
+
119 } \
+
120 __start_test(msg, ##args); \
+
121}
+
122
+
123#define PERROR(msg) test_perror(__FUNCTION__, msg)
+
124#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
+
125
+
126#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
127
+
128static int st_check_size(struct stat *st, int len)
+
129{
+
130 if (st->st_size != len) {
+
131 ERROR("length %u instead of %u", (int) st->st_size,
+
132 (int) len);
+
133 return -1;
+
134 }
+
135 return 0;
+
136}
+
137
+
138static int check_size(const char *path, int len)
+
139{
+
140 struct stat stbuf;
+
141 int res = stat(path, &stbuf);
+
142 if (res == -1) {
+
143 PERROR("stat");
+
144 return -1;
+
145 }
+
146 return st_check_size(&stbuf, len);
+
147}
+
148
+
149static int check_testfile_size(const char *path, int len)
+
150{
+
151 this_test->stat.st_size = len;
+
152 return check_size(path, len);
+
153}
+
154
+
155static int st_check_type(struct stat *st, mode_t type)
+
156{
+
157 if ((st->st_mode & S_IFMT) != type) {
+
158 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
+
159 return -1;
+
160 }
+
161 return 0;
+
162}
+
163
+
164static int check_type(const char *path, mode_t type)
+
165{
+
166 struct stat stbuf;
+
167 int res = lstat(path, &stbuf);
+
168 if (res == -1) {
+
169 PERROR("lstat");
+
170 return -1;
+
171 }
+
172 return st_check_type(&stbuf, type);
+
173}
+
174
+
175static int st_check_mode(struct stat *st, mode_t mode)
+
176{
+
177 if ((st->st_mode & ALLPERMS) != mode) {
+
178 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
+
179 mode);
+
180 return -1;
+
181 }
+
182 return 0;
+
183}
+
184
+
185static int check_mode(const char *path, mode_t mode)
+
186{
+
187 struct stat stbuf;
+
188 int res = lstat(path, &stbuf);
+
189 if (res == -1) {
+
190 PERROR("lstat");
+
191 return -1;
+
192 }
+
193 return st_check_mode(&stbuf, mode);
+
194}
+
195
+
196static int check_testfile_mode(const char *path, mode_t mode)
+
197{
+
198 this_test->stat.st_mode &= ~ALLPERMS;
+
199 this_test->stat.st_mode |= mode;
+
200 return check_mode(path, mode);
+
201}
+
202
+
203static int check_times(const char *path, time_t atime, time_t mtime)
+
204{
+
205 int err = 0;
+
206 struct stat stbuf;
+
207 int res = lstat(path, &stbuf);
+
208 if (res == -1) {
+
209 PERROR("lstat");
+
210 return -1;
+
211 }
+
212 if (stbuf.st_atime != atime) {
+
213 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
214 err--;
+
215 }
+
216 if (stbuf.st_mtime != mtime) {
+
217 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
218 err--;
+
219 }
+
220 if (err)
+
221 return -1;
+
222
+
223 return 0;
+
224}
+
225
+
226#if 0
+
227static int fcheck_times(int fd, time_t atime, time_t mtime)
+
228{
+
229 int err = 0;
+
230 struct stat stbuf;
+
231 int res = fstat(fd, &stbuf);
+
232 if (res == -1) {
+
233 PERROR("fstat");
+
234 return -1;
+
235 }
+
236 if (stbuf.st_atime != atime) {
+
237 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
238 err--;
+
239 }
+
240 if (stbuf.st_mtime != mtime) {
+
241 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
242 err--;
+
243 }
+
244 if (err)
+
245 return -1;
+
246
+
247 return 0;
+
248}
+
249#endif
+
250
+
251static int st_check_nlink(struct stat *st, nlink_t nlink)
+
252{
+
253 if (st->st_nlink != nlink) {
+
254 ERROR("nlink %li instead of %li", (long) st->st_nlink,
+
255 (long) nlink);
+
256 return -1;
+
257 }
+
258 return 0;
+
259}
+
260
+
261static int check_nlink(const char *path, nlink_t nlink)
+
262{
+
263 struct stat stbuf;
+
264 int res = lstat(path, &stbuf);
+
265 if (res == -1) {
+
266 PERROR("lstat");
+
267 return -1;
+
268 }
+
269 return st_check_nlink(&stbuf, nlink);
+
270}
+
271
+
272static int fcheck_stat(int fd, int flags, struct stat *st)
+
273{
+
274 struct stat stbuf;
+
275 int res = fstat(fd, &stbuf);
+
276 if (res == -1) {
+
277 if (flags & O_PATH) {
+
278 // With O_PATH fd, the server does not have to keep
+
279 // the inode alive so FUSE inode may be stale or bad
+
280 if (errno == ESTALE || errno == EIO ||
+
281 errno == ENOENT || errno == EBADF)
+
282 return 0;
+
283 }
+
284 PERROR("fstat");
+
285 return -1;
+
286 }
+
287
+
288 int err = 0;
+
289 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
+
290 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
+
291 err += st_check_size(&stbuf, st->st_size);
+
292 err += st_check_nlink(&stbuf, st->st_nlink);
+
293
+
294 return err;
+
295}
+
296
+
297static int check_nonexist(const char *path)
+
298{
+
299 struct stat stbuf;
+
300 int res = lstat(path, &stbuf);
+
301 if (res == 0) {
+
302 ERROR("file should not exist");
+
303 return -1;
+
304 }
+
305 if (errno != ENOENT) {
+
306 ERROR("file should not exist: %s", strerror(errno));
+
307 return -1;
+
308 }
+
309 return 0;
+
310}
+
311
+
312static int check_buffer(const char *buf, const char *data, unsigned len)
+
313{
+
314 if (memcmp(buf, data, len) != 0) {
+
315 ERROR("data mismatch");
+
316 return -1;
+
317 }
+
318 return 0;
+
319}
+
320
+
321static int check_data(const char *path, const char *data, int offset,
+
322 unsigned len)
+
323{
+
324 char buf[4096];
+
325 int res;
+
326 int fd = open(path, O_RDONLY);
+
327 if (fd == -1) {
+
328 PERROR("open");
+
329 return -1;
+
330 }
+
331 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
332 PERROR("lseek");
+
333 close(fd);
+
334 return -1;
+
335 }
+
336 while (len) {
+
337 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
338 res = read(fd, buf, rdlen);
+
339 if (res == -1) {
+
340 PERROR("read");
+
341 close(fd);
+
342 return -1;
+
343 }
+
344 if (res != rdlen) {
+
345 ERROR("short read: %u instead of %u", res, rdlen);
+
346 close(fd);
+
347 return -1;
+
348 }
+
349 if (check_buffer(buf, data, rdlen) != 0) {
+
350 close(fd);
+
351 return -1;
+
352 }
+
353 data += rdlen;
+
354 len -= rdlen;
+
355 }
+
356 res = close(fd);
+
357 if (res == -1) {
+
358 PERROR("close");
+
359 return -1;
+
360 }
+
361 return 0;
+
362}
+
363
+
364static int fcheck_data(int fd, const char *data, int offset,
+
365 unsigned len)
+
366{
+
367 char buf[4096];
+
368 int res;
+
369 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
370 PERROR("lseek");
+
371 return -1;
+
372 }
+
373 while (len) {
+
374 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
375 res = read(fd, buf, rdlen);
+
376 if (res == -1) {
+
377 PERROR("read");
+
378 return -1;
+
379 }
+
380 if (res != rdlen) {
+
381 ERROR("short read: %u instead of %u", res, rdlen);
+
382 return -1;
+
383 }
+
384 if (check_buffer(buf, data, rdlen) != 0) {
+
385 return -1;
+
386 }
+
387 data += rdlen;
+
388 len -= rdlen;
+
389 }
+
390 return 0;
+
391}
+
392
+
393static int check_dir_contents(const char *path, const char **contents)
+
394{
+
395 int i;
+
396 int res;
+
397 int err = 0;
+
398 int found[MAX_ENTRIES];
+
399 const char *cont[MAX_ENTRIES];
+
400 DIR *dp;
+
401
+
402 for (i = 0; contents[i]; i++) {
+
403 assert(i < MAX_ENTRIES - 3);
+
404 found[i] = 0;
+
405 cont[i] = contents[i];
+
406 }
+
407 cont[i] = NULL;
+
408
+
409 dp = opendir(path);
+
410 if (dp == NULL) {
+
411 PERROR("opendir");
+
412 return -1;
+
413 }
+
414 memset(found, 0, sizeof(found));
+
415 while(1) {
+
416 struct dirent *de;
+
417 errno = 0;
+
418 de = readdir(dp);
+
419 if (de == NULL) {
+
420 if (errno) {
+
421 PERROR("readdir");
+
422 closedir(dp);
+
423 return -1;
+
424 }
+
425 break;
+
426 }
+
427 if (is_dot_or_dotdot(de->d_name))
+
428 continue;
+
429 for (i = 0; cont[i] != NULL; i++) {
+
430 assert(i < MAX_ENTRIES);
+
431 if (strcmp(cont[i], de->d_name) == 0) {
+
432 if (found[i]) {
+
433 ERROR("duplicate entry <%s>",
+
434 de->d_name);
+
435 err--;
+
436 } else
+
437 found[i] = 1;
+
438 break;
+
439 }
+
440 }
+
441 if (!cont[i]) {
+
442 ERROR("unexpected entry <%s>", de->d_name);
+
443 err --;
+
444 }
+
445 }
+
446 for (i = 0; cont[i] != NULL; i++) {
+
447 if (!found[i]) {
+
448 ERROR("missing entry <%s>", cont[i]);
+
449 err--;
+
450 }
+
451 }
+
452 res = closedir(dp);
+
453 if (res == -1) {
+
454 PERROR("closedir");
+
455 return -1;
+
456 }
+
457 if (err)
+
458 return -1;
+
459
+
460 return 0;
+
461}
+
462
+
463static int create_file(const char *path, const char *data, int len)
+
464{
+
465 int res;
+
466 int fd;
+
467
+
468 unlink(path);
+
469 fd = creat(path, 0644);
+
470 if (fd == -1) {
+
471 PERROR("creat");
+
472 return -1;
+
473 }
+
474 if (len) {
+
475 res = write(fd, data, len);
+
476 if (res == -1) {
+
477 PERROR("write");
+
478 close(fd);
+
479 return -1;
+
480 }
+
481 if (res != len) {
+
482 ERROR("write is short: %u instead of %u", res, len);
+
483 close(fd);
+
484 return -1;
+
485 }
+
486 }
+
487 res = close(fd);
+
488 if (res == -1) {
+
489 PERROR("close");
+
490 return -1;
+
491 }
+
492 res = check_type(path, S_IFREG);
+
493 if (res == -1)
+
494 return -1;
+
495 res = check_mode(path, 0644);
+
496 if (res == -1)
+
497 return -1;
+
498 res = check_nlink(path, 1);
+
499 if (res == -1)
+
500 return -1;
+
501 res = check_size(path, len);
+
502 if (res == -1)
+
503 return -1;
+
504
+
505 if (len) {
+
506 res = check_data(path, data, 0, len);
+
507 if (res == -1)
+
508 return -1;
+
509 }
+
510
+
511 return 0;
+
512}
+
513
+
514static int create_path_fd(const char *path, const char *data, int len)
+
515{
+
516 int path_fd;
+
517 int res;
+
518
+
519 res = create_file(path, data, len);
+
520 if (res == -1)
+
521 return -1;
+
522
+
523 path_fd = open(path, O_PATH);
+
524 if (path_fd == -1)
+
525 PERROR("open(O_PATH)");
+
526
+
527 return path_fd;
+
528}
+
529
+
530// Can be called once per test
+
531static int create_testfile(const char *path, const char *data, int len)
+
532{
+
533 struct test *t = this_test;
+
534 struct stat *st = &t->stat;
+
535 int res, fd;
+
536
+
537 if (t->fd > 0) {
+
538 ERROR("testfile already created");
+
539 return -1;
+
540 }
+
541
+
542 fd = create_path_fd(path, data, len);
+
543 if (fd == -1)
+
544 return -1;
+
545
+
546 t->fd = fd;
+
547
+
548 res = fstat(fd, st);
+
549 if (res == -1) {
+
550 PERROR("fstat");
+
551 return -1;
+
552 }
+
553
+
554 return 0;
+
555}
+
556
+
557static int check_unlinked_testfile(int fd)
+
558{
+
559 struct stat *st = &this_test->stat;
+
560
+
561 st->st_nlink = 0;
+
562 return fcheck_stat(fd, O_PATH, st);
+
563}
+
564
+
565// Check recorded testfiles after all tests completed
+
566static int check_unlinked_testfiles(void)
+
567{
+
568 int fd;
+
569 int res, err = 0;
+
570 int num = testnum;
+
571
+
572 if (!unlinked_test)
+
573 return 0;
+
574
+
575 testnum = 0;
+
576 while (testnum < num) {
+
577 fd = next_test->fd;
+
578 start_test("check_unlinked_testfile");
+
579 if (fd == -1)
+
580 continue;
+
581
+
582 err += check_unlinked_testfile(fd);
+
583 res = close(fd);
+
584 if (res == -1) {
+
585 PERROR("close(test_fd)");
+
586 err--;
+
587 }
+
588 }
+
589
+
590 if (err) {
+
591 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
+
592 return 1;
+
593 }
+
594
+
595 return err;
+
596}
+
597
+
598static int cleanup_dir(const char *path, const char **dir_files, int quiet)
+
599{
+
600 int i;
+
601 int err = 0;
+
602
+
603 for (i = 0; dir_files[i]; i++) {
+
604 int res;
+
605 char fpath[1280];
+
606 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
607 res = unlink(fpath);
+
608 if (res == -1 && !quiet) {
+
609 PERROR("unlink");
+
610 err --;
+
611 }
+
612 }
+
613 if (err)
+
614 return -1;
+
615
+
616 return 0;
+
617}
+
618
+
619static int create_dir(const char *path, const char **dir_files)
+
620{
+
621 int res;
+
622 int i;
+
623
+
624 rmdir(path);
+
625 res = mkdir(path, 0755);
+
626 if (res == -1) {
+
627 PERROR("mkdir");
+
628 return -1;
+
629 }
+
630 res = check_type(path, S_IFDIR);
+
631 if (res == -1)
+
632 return -1;
+
633 res = check_mode(path, 0755);
+
634 if (res == -1)
+
635 return -1;
+
636
+
637 for (i = 0; dir_files[i]; i++) {
+
638 char fpath[1280];
+
639 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
640 res = create_file(fpath, "", 0);
+
641 if (res == -1) {
+
642 cleanup_dir(path, dir_files, 1);
+
643 return -1;
+
644 }
+
645 }
+
646 res = check_dir_contents(path, dir_files);
+
647 if (res == -1) {
+
648 cleanup_dir(path, dir_files, 1);
+
649 return -1;
+
650 }
+
651
+
652 return 0;
+
653}
+
654
+
655static int test_truncate(int len)
+
656{
+
657 const char *data = testdata;
+
658 int datalen = testdatalen;
+
659 int res;
+
660
+
661 start_test("truncate(%u)", (int) len);
+
662 res = create_testfile(testfile, data, datalen);
+
663 if (res == -1)
+
664 return -1;
+
665
+
666 res = truncate(testfile, len);
+
667 if (res == -1) {
+
668 PERROR("truncate");
+
669 return -1;
+
670 }
+
671 res = check_testfile_size(testfile, len);
+
672 if (res == -1)
+
673 return -1;
+
674
+
675 if (len > 0) {
+
676 if (len <= datalen) {
+
677 res = check_data(testfile, data, 0, len);
+
678 if (res == -1)
+
679 return -1;
+
680 } else {
+
681 res = check_data(testfile, data, 0, datalen);
+
682 if (res == -1)
+
683 return -1;
+
684 res = check_data(testfile, zerodata, datalen,
+
685 len - datalen);
+
686 if (res == -1)
+
687 return -1;
+
688 }
+
689 }
+
690 res = unlink(testfile);
+
691 if (res == -1) {
+
692 PERROR("unlink");
+
693 return -1;
+
694 }
+
695 res = check_nonexist(testfile);
+
696 if (res == -1)
+
697 return -1;
+
698
+
699 success();
+
700 return 0;
+
701}
+
702
+
703static int test_ftruncate(int len, int mode)
+
704{
+
705 const char *data = testdata;
+
706 int datalen = testdatalen;
+
707 int res;
+
708 int fd;
+
709
+
710 start_test("ftruncate(%u) mode: 0%03o", len, mode);
+
711 res = create_testfile(testfile, data, datalen);
+
712 if (res == -1)
+
713 return -1;
+
714
+
715 fd = open(testfile, O_WRONLY);
+
716 if (fd == -1) {
+
717 PERROR("open");
+
718 return -1;
+
719 }
+
720
+
721 res = fchmod(fd, mode);
+
722 if (res == -1) {
+
723 PERROR("fchmod");
+
724 close(fd);
+
725 return -1;
+
726 }
+
727 res = check_testfile_mode(testfile, mode);
+
728 if (res == -1) {
+
729 close(fd);
+
730 return -1;
+
731 }
+
732 res = ftruncate(fd, len);
+
733 if (res == -1) {
+
734 PERROR("ftruncate");
+
735 close(fd);
+
736 return -1;
+
737 }
+
738 close(fd);
+
739 res = check_testfile_size(testfile, len);
+
740 if (res == -1)
+
741 return -1;
+
742
+
743 if (len > 0) {
+
744 if (len <= datalen) {
+
745 res = check_data(testfile, data, 0, len);
+
746 if (res == -1)
+
747 return -1;
+
748 } else {
+
749 res = check_data(testfile, data, 0, datalen);
+
750 if (res == -1)
+
751 return -1;
+
752 res = check_data(testfile, zerodata, datalen,
+
753 len - datalen);
+
754 if (res == -1)
+
755 return -1;
+
756 }
+
757 }
+
758 res = unlink(testfile);
+
759 if (res == -1) {
+
760 PERROR("unlink");
+
761 return -1;
+
762 }
+
763 res = check_nonexist(testfile);
+
764 if (res == -1)
+
765 return -1;
+
766
+
767 success();
+
768 return 0;
+
769}
+
770
+
771static int test_seekdir(void)
+
772{
+
773 int i;
+
774 int res;
+
775 DIR *dp;
+
776 struct dirent *de = NULL;
+
777
+
778 start_test("seekdir");
+
779 res = create_dir(testdir, testdir_files);
+
780 if (res == -1)
+
781 return res;
+
782
+
783 dp = opendir(testdir);
+
784 if (dp == NULL) {
+
785 PERROR("opendir");
+
786 return -1;
+
787 }
+
788
+
789 /* Remember dir offsets */
+
790 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
+
791 seekdir_offsets[i] = telldir(dp);
+
792 errno = 0;
+
793 de = readdir(dp);
+
794 if (de == NULL) {
+
795 if (errno) {
+
796 PERROR("readdir");
+
797 goto fail;
+
798 }
+
799 break;
+
800 }
+
801 }
+
802
+
803 /* Walk until the end of directory */
+
804 while (de)
+
805 de = readdir(dp);
+
806
+
807 /* Start from the last valid dir offset and seek backwards */
+
808 for (i--; i >= 0; i--) {
+
809 seekdir(dp, seekdir_offsets[i]);
+
810 de = readdir(dp);
+
811 if (de == NULL) {
+
812 ERROR("Unexpected end of directory after seekdir()");
+
813 goto fail;
+
814 }
+
815 }
+
816
+
817 closedir(dp);
+
818 res = cleanup_dir(testdir, testdir_files, 0);
+
819 if (!res)
+
820 success();
+
821 return res;
+
822fail:
+
823 closedir(dp);
+
824 cleanup_dir(testdir, testdir_files, 1);
+
825 return -1;
+
826}
+
827
+
828#ifdef HAVE_COPY_FILE_RANGE
+
829static int test_copy_file_range(void)
+
830{
+
831 const char *data = testdata;
+
832 int datalen = testdatalen;
+
833 int err = 0;
+
834 int res;
+
835 int fd_in, fd_out;
+
836 off_t pos_in = 0, pos_out = 0;
+
837
+
838 start_test("copy_file_range");
+
839 unlink(testfile);
+
840 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
+
841 if (fd_in == -1) {
+
842 PERROR("creat");
+
843 return -1;
+
844 }
+
845 res = write(fd_in, data, datalen);
+
846 if (res == -1) {
+
847 PERROR("write");
+
848 close(fd_in);
+
849 return -1;
+
850 }
+
851 if (res != datalen) {
+
852 ERROR("write is short: %u instead of %u", res, datalen);
+
853 close(fd_in);
+
854 return -1;
+
855 }
+
856
+
857 unlink(testfile2);
+
858 fd_out = creat(testfile2, 0644);
+
859 if (fd_out == -1) {
+
860 PERROR("creat");
+
861 close(fd_in);
+
862 return -1;
+
863 }
+
864 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
+
865 if (res == -1) {
+
866 PERROR("copy_file_range");
+
867 close(fd_in);
+
868 close(fd_out);
+
869 return -1;
+
870 }
+
871 if (res != datalen) {
+
872 ERROR("copy is short: %u instead of %u", res, datalen);
+
873 close(fd_in);
+
874 close(fd_out);
+
875 return -1;
+
876 }
+
877
+
878 res = close(fd_in);
+
879 if (res == -1) {
+
880 PERROR("close");
+
881 close(fd_out);
+
882 return -1;
+
883 }
+
884 res = close(fd_out);
+
885 if (res == -1) {
+
886 PERROR("close");
+
887 return -1;
+
888 }
+
889
+
890 err = check_data(testfile2, data, 0, datalen);
+
891
+
892 res = unlink(testfile);
+
893 if (res == -1) {
+
894 PERROR("unlink");
+
895 return -1;
+
896 }
+
897 res = check_nonexist(testfile);
+
898 if (res == -1)
+
899 return -1;
+
900 if (err)
+
901 return -1;
+
902
+
903 res = unlink(testfile2);
+
904 if (res == -1) {
+
905 PERROR("unlink");
+
906 return -1;
+
907 }
+
908 res = check_nonexist(testfile2);
+
909 if (res == -1)
+
910 return -1;
+
911 if (err)
+
912 return -1;
+
913
+
914 success();
+
915 return 0;
+
916}
+
917#else
+
918static int test_copy_file_range(void)
+
919{
+
920 return 0;
+
921}
+
922#endif
+
923
+
924static int test_utime(void)
+
925{
+
926 struct utimbuf utm;
+
927 time_t atime = 987631200;
+
928 time_t mtime = 123116400;
+
929 int res;
+
930
+
931 start_test("utime");
+
932 res = create_testfile(testfile, NULL, 0);
+
933 if (res == -1)
+
934 return -1;
+
935
+
936 utm.actime = atime;
+
937 utm.modtime = mtime;
+
938 res = utime(testfile, &utm);
+
939 if (res == -1) {
+
940 PERROR("utime");
+
941 return -1;
+
942 }
+
943 res = check_times(testfile, atime, mtime);
+
944 if (res == -1) {
+
945 return -1;
+
946 }
+
947 res = unlink(testfile);
+
948 if (res == -1) {
+
949 PERROR("unlink");
+
950 return -1;
+
951 }
+
952 res = check_nonexist(testfile);
+
953 if (res == -1)
+
954 return -1;
+
955
+
956 success();
+
957 return 0;
+
958}
+
959
+
960static int test_create(void)
+
961{
+
962 const char *data = testdata;
+
963 int datalen = testdatalen;
+
964 int err = 0;
+
965 int res;
+
966 int fd;
+
967
+
968 start_test("create");
+
969 unlink(testfile);
+
970 fd = creat(testfile, 0644);
+
971 if (fd == -1) {
+
972 PERROR("creat");
+
973 return -1;
+
974 }
+
975 res = write(fd, data, datalen);
+
976 if (res == -1) {
+
977 PERROR("write");
+
978 close(fd);
+
979 return -1;
+
980 }
+
981 if (res != datalen) {
+
982 ERROR("write is short: %u instead of %u", res, datalen);
+
983 close(fd);
+
984 return -1;
+
985 }
+
986 res = close(fd);
+
987 if (res == -1) {
+
988 PERROR("close");
+
989 return -1;
+
990 }
+
991 res = check_type(testfile, S_IFREG);
+
992 if (res == -1)
+
993 return -1;
+
994 err += check_mode(testfile, 0644);
+
995 err += check_nlink(testfile, 1);
+
996 err += check_size(testfile, datalen);
+
997 err += check_data(testfile, data, 0, datalen);
+
998 res = unlink(testfile);
+
999 if (res == -1) {
+
1000 PERROR("unlink");
+
1001 return -1;
+
1002 }
+
1003 res = check_nonexist(testfile);
+
1004 if (res == -1)
+
1005 return -1;
+
1006 if (err)
+
1007 return -1;
+
1008
+
1009 success();
+
1010 return 0;
+
1011}
+
1012
+
1013static int test_create_unlink(void)
+
1014{
+
1015 const char *data = testdata;
+
1016 int datalen = testdatalen;
+
1017 int err = 0;
+
1018 int res;
+
1019 int fd;
+
1020
+
1021 start_test("create+unlink");
+
1022 unlink(testfile);
+
1023 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
+
1024 if (fd == -1) {
+
1025 PERROR("creat");
+
1026 return -1;
+
1027 }
+
1028 res = unlink(testfile);
+
1029 if (res == -1) {
+
1030 PERROR("unlink");
+
1031 close(fd);
+
1032 return -1;
+
1033 }
+
1034 res = check_nonexist(testfile);
+
1035 if (res == -1) {
+
1036 close(fd);
+
1037 return -1;
+
1038 }
+
1039 res = write(fd, data, datalen);
+
1040 if (res == -1) {
+
1041 PERROR("write");
+
1042 close(fd);
+
1043 return -1;
+
1044 }
+
1045 if (res != datalen) {
+
1046 ERROR("write is short: %u instead of %u", res, datalen);
+
1047 close(fd);
+
1048 return -1;
+
1049 }
+
1050 struct stat st = {
+
1051 .st_mode = S_IFREG | 0644,
+
1052 .st_size = datalen,
+
1053 };
+
1054 err = fcheck_stat(fd, O_RDWR, &st);
+
1055 err += fcheck_data(fd, data, 0, datalen);
+
1056 res = close(fd);
+
1057 if (res == -1) {
+
1058 PERROR("close");
+
1059 err--;
+
1060 }
+
1061 if (err)
+
1062 return -1;
+
1063
+
1064 success();
+
1065 return 0;
+
1066}
+
1067
+
1068#ifndef __FreeBSD__
+
1069static int test_mknod(void)
+
1070{
+
1071 int err = 0;
+
1072 int res;
+
1073
+
1074 start_test("mknod");
+
1075 unlink(testfile);
+
1076 res = mknod(testfile, 0644, 0);
+
1077 if (res == -1) {
+
1078 PERROR("mknod");
+
1079 return -1;
+
1080 }
+
1081 res = check_type(testfile, S_IFREG);
+
1082 if (res == -1)
+
1083 return -1;
+
1084 err += check_mode(testfile, 0644);
+
1085 err += check_nlink(testfile, 1);
+
1086 err += check_size(testfile, 0);
+
1087 res = unlink(testfile);
+
1088 if (res == -1) {
+
1089 PERROR("unlink");
+
1090 return -1;
+
1091 }
+
1092 res = check_nonexist(testfile);
+
1093 if (res == -1)
+
1094 return -1;
+
1095 if (err)
+
1096 return -1;
+
1097
+
1098 success();
+
1099 return 0;
+
1100}
+
1101#endif
+
1102
+
1103#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
+
1104
+
1105static int do_test_open(int exist, int flags, const char *flags_str, int mode)
+
1106{
+
1107 char buf[4096];
+
1108 const char *data = testdata;
+
1109 int datalen = testdatalen;
+
1110 unsigned currlen = 0;
+
1111 int err = 0;
+
1112 int res;
+
1113 int fd;
+
1114 off_t off;
+
1115
+
1116 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
+
1117 unlink(testfile);
+
1118 if (exist) {
+
1119 res = create_file(testfile_r, testdata2, testdata2len);
+
1120 if (res == -1)
+
1121 return -1;
+
1122
+
1123 currlen = testdata2len;
+
1124 }
+
1125
+
1126 fd = open(testfile, flags, mode);
+
1127 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
+
1128 if (fd != -1) {
+
1129 ERROR("open should have failed");
+
1130 close(fd);
+
1131 return -1;
+
1132 } else if (errno == EEXIST)
+
1133 goto succ;
+
1134 }
+
1135 if (!(flags & O_CREAT) && !exist) {
+
1136 if (fd != -1) {
+
1137 ERROR("open should have failed");
+
1138 close(fd);
+
1139 return -1;
+
1140 } else if (errno == ENOENT)
+
1141 goto succ;
+
1142 }
+
1143 if (fd == -1) {
+
1144 PERROR("open");
+
1145 return -1;
+
1146 }
+
1147
+
1148 if (flags & O_TRUNC)
+
1149 currlen = 0;
+
1150
+
1151 err += check_type(testfile, S_IFREG);
+
1152 if (exist)
+
1153 err += check_mode(testfile, 0644);
+
1154 else
+
1155 err += check_mode(testfile, mode);
+
1156 err += check_nlink(testfile, 1);
+
1157 err += check_size(testfile, currlen);
+
1158 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
+
1159 err += check_data(testfile, testdata2, 0, testdata2len);
+
1160
+
1161 res = write(fd, data, datalen);
+
1162 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1163 if (res == -1) {
+
1164 PERROR("write");
+
1165 err --;
+
1166 } else if (res != datalen) {
+
1167 ERROR("write is short: %u instead of %u", res, datalen);
+
1168 err --;
+
1169 } else {
+
1170 if (datalen > (int) currlen)
+
1171 currlen = datalen;
+
1172
+
1173 err += check_size(testfile, currlen);
+
1174
+
1175 if (mode & S_IRUSR) {
+
1176 err += check_data(testfile, data, 0, datalen);
+
1177 if (exist && !(flags & O_TRUNC) &&
+
1178 testdata2len > datalen)
+
1179 err += check_data(testfile,
+
1180 testdata2 + datalen,
+
1181 datalen,
+
1182 testdata2len - datalen);
+
1183 }
+
1184 }
+
1185 } else {
+
1186 if (res != -1) {
+
1187 ERROR("write should have failed");
+
1188 err --;
+
1189 } else if (errno != EBADF) {
+
1190 PERROR("write");
+
1191 err --;
+
1192 }
+
1193 }
+
1194 off = lseek(fd, SEEK_SET, 0);
+
1195 if (off == (off_t) -1) {
+
1196 PERROR("lseek");
+
1197 err--;
+
1198 } else if (off != 0) {
+
1199 ERROR("offset should have returned 0");
+
1200 err --;
+
1201 }
+
1202 res = read(fd, buf, sizeof(buf));
+
1203 if ((flags & O_ACCMODE) != O_WRONLY) {
+
1204 if (res == -1) {
+
1205 PERROR("read");
+
1206 err--;
+
1207 } else {
+
1208 int readsize =
+
1209 currlen < sizeof(buf) ? currlen : sizeof(buf);
+
1210 if (res != readsize) {
+
1211 ERROR("read is short: %i instead of %u",
+
1212 res, readsize);
+
1213 err--;
+
1214 } else {
+
1215 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1216 err += check_buffer(buf, data, datalen);
+
1217 if (exist && !(flags & O_TRUNC) &&
+
1218 testdata2len > datalen)
+
1219 err += check_buffer(buf + datalen,
+
1220 testdata2 + datalen,
+
1221 testdata2len - datalen);
+
1222 } else if (exist)
+
1223 err += check_buffer(buf, testdata2,
+
1224 testdata2len);
+
1225 }
+
1226 }
+
1227 } else {
+
1228 if (res != -1) {
+
1229 ERROR("read should have failed");
+
1230 err --;
+
1231 } else if (errno != EBADF) {
+
1232 PERROR("read");
+
1233 err --;
+
1234 }
+
1235 }
+
1236
+
1237 res = close(fd);
+
1238 if (res == -1) {
+
1239 PERROR("close");
+
1240 return -1;
+
1241 }
+
1242 res = unlink(testfile);
+
1243 if (res == -1) {
+
1244 PERROR("unlink");
+
1245 return -1;
+
1246 }
+
1247 res = check_nonexist(testfile);
+
1248 if (res == -1)
+
1249 return -1;
+
1250 res = check_nonexist(testfile_r);
+
1251 if (res == -1)
+
1252 return -1;
+
1253 if (err)
+
1254 return -1;
+
1255
+
1256succ:
+
1257 success();
+
1258 return 0;
+
1259}
+
1260
+
1261#define test_open_acc(flags, mode, err) \
+
1262 do_test_open_acc(flags, #flags, mode, err)
+
1263
+
1264static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
+
1265{
+
1266 const char *data = testdata;
+
1267 int datalen = testdatalen;
+
1268 int res;
+
1269 int fd;
+
1270
+
1271 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
+
1272 strerror(err));
+
1273 unlink(testfile);
+
1274 res = create_testfile(testfile, data, datalen);
+
1275 if (res == -1)
+
1276 return -1;
+
1277
+
1278 res = chmod(testfile, mode);
+
1279 if (res == -1) {
+
1280 PERROR("chmod");
+
1281 return -1;
+
1282 }
+
1283
+
1284 res = check_testfile_mode(testfile, mode);
+
1285 if (res == -1)
+
1286 return -1;
+
1287
+
1288 fd = open(testfile, flags);
+
1289 if (fd == -1) {
+
1290 if (err != errno) {
+
1291 PERROR("open");
+
1292 return -1;
+
1293 }
+
1294 } else {
+
1295 if (err) {
+
1296 ERROR("open should have failed");
+
1297 close(fd);
+
1298 return -1;
+
1299 }
+
1300 close(fd);
+
1301 }
+
1302
+
1303 res = unlink(testfile);
+
1304 if (res == -1) {
+
1305 PERROR("unlink");
+
1306 return -1;
+
1307 }
+
1308 res = check_nonexist(testfile);
+
1309 if (res == -1)
+
1310 return -1;
+
1311 res = check_nonexist(testfile_r);
+
1312 if (res == -1)
+
1313 return -1;
+
1314
+
1315 success();
+
1316 return 0;
+
1317}
+
1318
+
1319static int test_symlink(void)
+
1320{
+
1321 char buf[1024];
+
1322 const char *data = testdata;
+
1323 int datalen = testdatalen;
+
1324 int linklen = strlen(testfile);
+
1325 int err = 0;
+
1326 int res;
+
1327
+
1328 start_test("symlink");
+
1329 res = create_testfile(testfile, data, datalen);
+
1330 if (res == -1)
+
1331 return -1;
+
1332
+
1333 unlink(testfile2);
+
1334 res = symlink(testfile, testfile2);
+
1335 if (res == -1) {
+
1336 PERROR("symlink");
+
1337 return -1;
+
1338 }
+
1339 res = check_type(testfile2, S_IFLNK);
+
1340 if (res == -1)
+
1341 return -1;
+
1342 err += check_mode(testfile2, 0777);
+
1343 err += check_nlink(testfile2, 1);
+
1344 res = readlink(testfile2, buf, sizeof(buf));
+
1345 if (res == -1) {
+
1346 PERROR("readlink");
+
1347 err--;
+
1348 }
+
1349 if (res != linklen) {
+
1350 ERROR("short readlink: %u instead of %u", res, linklen);
+
1351 err--;
+
1352 }
+
1353 if (memcmp(buf, testfile, linklen) != 0) {
+
1354 ERROR("link mismatch");
+
1355 err--;
+
1356 }
+
1357 err += check_size(testfile2, datalen);
+
1358 err += check_data(testfile2, data, 0, datalen);
+
1359 res = unlink(testfile2);
+
1360 if (res == -1) {
+
1361 PERROR("unlink");
+
1362 return -1;
+
1363 }
+
1364 res = check_nonexist(testfile2);
+
1365 if (res == -1)
+
1366 return -1;
+
1367 if (err)
+
1368 return -1;
+
1369
+
1370 res = unlink(testfile);
+
1371 if (res == -1) {
+
1372 PERROR("unlink");
+
1373 return -1;
+
1374 }
+
1375 res = check_nonexist(testfile);
+
1376 if (res == -1)
+
1377 return -1;
+
1378
+
1379 success();
+
1380 return 0;
+
1381}
+
1382
+
1383static int test_link(void)
+
1384{
+
1385 const char *data = testdata;
+
1386 int datalen = testdatalen;
+
1387 int err = 0;
+
1388 int res;
+
1389
+
1390 start_test("link");
+
1391 res = create_testfile(testfile, data, datalen);
+
1392 if (res == -1)
+
1393 return -1;
+
1394
+
1395 unlink(testfile2);
+
1396 res = link(testfile, testfile2);
+
1397 if (res == -1) {
+
1398 PERROR("link");
+
1399 return -1;
+
1400 }
+
1401 res = check_type(testfile2, S_IFREG);
+
1402 if (res == -1)
+
1403 return -1;
+
1404 err += check_mode(testfile2, 0644);
+
1405 err += check_nlink(testfile2, 2);
+
1406 err += check_size(testfile2, datalen);
+
1407 err += check_data(testfile2, data, 0, datalen);
+
1408 res = unlink(testfile);
+
1409 if (res == -1) {
+
1410 PERROR("unlink");
+
1411 return -1;
+
1412 }
+
1413 res = check_nonexist(testfile);
+
1414 if (res == -1)
+
1415 return -1;
+
1416
+
1417 err += check_nlink(testfile2, 1);
+
1418 res = unlink(testfile2);
+
1419 if (res == -1) {
+
1420 PERROR("unlink");
+
1421 return -1;
+
1422 }
+
1423 res = check_nonexist(testfile2);
+
1424 if (res == -1)
+
1425 return -1;
+
1426 if (err)
+
1427 return -1;
+
1428
+
1429 success();
+
1430 return 0;
+
1431}
+
1432
+
1433static int test_link2(void)
+
1434{
+
1435 const char *data = testdata;
+
1436 int datalen = testdatalen;
+
1437 int err = 0;
+
1438 int res;
+
1439
+
1440 start_test("link-unlink-link");
+
1441 res = create_testfile(testfile, data, datalen);
+
1442 if (res == -1)
+
1443 return -1;
+
1444
+
1445 unlink(testfile2);
+
1446 res = link(testfile, testfile2);
+
1447 if (res == -1) {
+
1448 PERROR("link");
+
1449 return -1;
+
1450 }
+
1451 res = unlink(testfile);
+
1452 if (res == -1) {
+
1453 PERROR("unlink");
+
1454 return -1;
+
1455 }
+
1456 res = check_nonexist(testfile);
+
1457 if (res == -1)
+
1458 return -1;
+
1459 res = link(testfile2, testfile);
+
1460 if (res == -1) {
+
1461 PERROR("link");
+
1462 }
+
1463 res = check_type(testfile, S_IFREG);
+
1464 if (res == -1)
+
1465 return -1;
+
1466 err += check_mode(testfile, 0644);
+
1467 err += check_nlink(testfile, 2);
+
1468 err += check_size(testfile, datalen);
+
1469 err += check_data(testfile, data, 0, datalen);
+
1470
+
1471 res = unlink(testfile2);
+
1472 if (res == -1) {
+
1473 PERROR("unlink");
+
1474 return -1;
+
1475 }
+
1476 err += check_nlink(testfile, 1);
+
1477 res = unlink(testfile);
+
1478 if (res == -1) {
+
1479 PERROR("unlink");
+
1480 return -1;
+
1481 }
+
1482 res = check_nonexist(testfile);
+
1483 if (res == -1)
+
1484 return -1;
+
1485 if (err)
+
1486 return -1;
+
1487
+
1488 success();
+
1489 return 0;
+
1490}
+
1491
+
1492static int test_rename_file(void)
+
1493{
+
1494 const char *data = testdata;
+
1495 int datalen = testdatalen;
+
1496 int err = 0;
+
1497 int res;
+
1498
+
1499 start_test("rename file");
+
1500 res = create_testfile(testfile, data, datalen);
+
1501 if (res == -1)
+
1502 return -1;
+
1503
+
1504 unlink(testfile2);
+
1505 res = rename(testfile, testfile2);
+
1506 if (res == -1) {
+
1507 PERROR("rename");
+
1508 return -1;
+
1509 }
+
1510 res = check_nonexist(testfile);
+
1511 if (res == -1)
+
1512 return -1;
+
1513 res = check_type(testfile2, S_IFREG);
+
1514 if (res == -1)
+
1515 return -1;
+
1516 err += check_mode(testfile2, 0644);
+
1517 err += check_nlink(testfile2, 1);
+
1518 err += check_size(testfile2, datalen);
+
1519 err += check_data(testfile2, data, 0, datalen);
+
1520 res = unlink(testfile2);
+
1521 if (res == -1) {
+
1522 PERROR("unlink");
+
1523 return -1;
+
1524 }
+
1525 res = check_nonexist(testfile2);
+
1526 if (res == -1)
+
1527 return -1;
+
1528 if (err)
+
1529 return -1;
+
1530
+
1531 success();
+
1532 return 0;
+
1533}
+
1534
+
1535static int test_rename_dir(void)
+
1536{
+
1537 int err = 0;
+
1538 int res;
+
1539
+
1540 start_test("rename dir");
+
1541 res = create_dir(testdir, testdir_files);
+
1542 if (res == -1)
+
1543 return -1;
+
1544
+
1545 rmdir(testdir2);
+
1546 res = rename(testdir, testdir2);
+
1547 if (res == -1) {
+
1548 PERROR("rename");
+
1549 cleanup_dir(testdir, testdir_files, 1);
+
1550 return -1;
+
1551 }
+
1552 res = check_nonexist(testdir);
+
1553 if (res == -1) {
+
1554 cleanup_dir(testdir, testdir_files, 1);
+
1555 return -1;
+
1556 }
+
1557 res = check_type(testdir2, S_IFDIR);
+
1558 if (res == -1) {
+
1559 cleanup_dir(testdir2, testdir_files, 1);
+
1560 return -1;
+
1561 }
+
1562 err += check_mode(testdir2, 0755);
+
1563 err += check_dir_contents(testdir2, testdir_files);
+
1564 err += cleanup_dir(testdir2, testdir_files, 0);
+
1565 res = rmdir(testdir2);
+
1566 if (res == -1) {
+
1567 PERROR("rmdir");
+
1568 return -1;
+
1569 }
+
1570 res = check_nonexist(testdir2);
+
1571 if (res == -1)
+
1572 return -1;
+
1573 if (err)
+
1574 return -1;
+
1575
+
1576 success();
+
1577 return 0;
+
1578}
+
1579
+
1580static int test_rename_dir_loop(void)
+
1581{
+
1582#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
+
1583#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
+
1584
+
1585 char path[1280], path2[1280];
+
1586 int err = 0;
+
1587 int res;
+
1588
+
1589 start_test("rename dir loop");
+
1590
+
1591 res = create_dir(testdir, testdir_files);
+
1592 if (res == -1)
+
1593 return -1;
+
1594
+
1595 res = mkdir(PATH("a"), 0755);
+
1596 if (res == -1) {
+
1597 PERROR("mkdir");
+
1598 goto fail;
+
1599 }
+
1600
+
1601 res = rename(PATH("a"), PATH2("a"));
+
1602 if (res == -1) {
+
1603 PERROR("rename");
+
1604 goto fail;
+
1605 }
+
1606
+
1607 errno = 0;
+
1608 res = rename(PATH("a"), PATH2("a/b"));
+
1609 if (res == 0 || errno != EINVAL) {
+
1610 PERROR("rename");
+
1611 goto fail;
+
1612 }
+
1613
+
1614 res = mkdir(PATH("a/b"), 0755);
+
1615 if (res == -1) {
+
1616 PERROR("mkdir");
+
1617 goto fail;
+
1618 }
+
1619
+
1620 res = mkdir(PATH("a/b/c"), 0755);
+
1621 if (res == -1) {
+
1622 PERROR("mkdir");
+
1623 goto fail;
+
1624 }
+
1625
+
1626 errno = 0;
+
1627 res = rename(PATH("a"), PATH2("a/b/c"));
+
1628 if (res == 0 || errno != EINVAL) {
+
1629 PERROR("rename");
+
1630 goto fail;
+
1631 }
+
1632
+
1633 errno = 0;
+
1634 res = rename(PATH("a"), PATH2("a/b/c/a"));
+
1635 if (res == 0 || errno != EINVAL) {
+
1636 PERROR("rename");
+
1637 goto fail;
+
1638 }
+
1639
+
1640 errno = 0;
+
1641 res = rename(PATH("a/b/c"), PATH2("a"));
+
1642 if (res == 0 || errno != ENOTEMPTY) {
+
1643 PERROR("rename");
+
1644 goto fail;
+
1645 }
+
1646
+
1647 res = open(PATH("a/foo"), O_CREAT, 0644);
+
1648 if (res == -1) {
+
1649 PERROR("open");
+
1650 goto fail;
+
1651 }
+
1652 close(res);
+
1653
+
1654 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1655 if (res == -1) {
+
1656 PERROR("rename");
+
1657 goto fail;
+
1658 }
+
1659
+
1660 res = rename(PATH("a/bar"), PATH2("a/foo"));
+
1661 if (res == -1) {
+
1662 PERROR("rename");
+
1663 goto fail;
+
1664 }
+
1665
+
1666 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
+
1667 if (res == -1) {
+
1668 PERROR("rename");
+
1669 goto fail;
+
1670 }
+
1671
+
1672 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
+
1673 if (res == -1) {
+
1674 PERROR("rename");
+
1675 goto fail;
+
1676 }
+
1677
+
1678 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
+
1679 if (res == -1) {
+
1680 PERROR("rename");
+
1681 goto fail;
+
1682 }
+
1683
+
1684 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
+
1685 if (res == -1) {
+
1686 PERROR("rename");
+
1687 goto fail;
+
1688 }
+
1689
+
1690 res = open(PATH("a/bar"), O_CREAT, 0644);
+
1691 if (res == -1) {
+
1692 PERROR("open");
+
1693 goto fail;
+
1694 }
+
1695 close(res);
+
1696
+
1697 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1698 if (res == -1) {
+
1699 PERROR("rename");
+
1700 goto fail;
+
1701 }
+
1702
+
1703 unlink(PATH("a/bar"));
+
1704
+
1705 res = rename(PATH("a/b"), PATH2("a/d"));
+
1706 if (res == -1) {
+
1707 PERROR("rename");
+
1708 goto fail;
+
1709 }
+
1710
+
1711 res = rename(PATH("a/d"), PATH2("a/b"));
+
1712 if (res == -1) {
+
1713 PERROR("rename");
+
1714 goto fail;
+
1715 }
+
1716
+
1717 res = mkdir(PATH("a/d"), 0755);
+
1718 if (res == -1) {
+
1719 PERROR("mkdir");
+
1720 goto fail;
+
1721 }
+
1722
+
1723 res = rename(PATH("a/b"), PATH2("a/d"));
+
1724 if (res == -1) {
+
1725 PERROR("rename");
+
1726 goto fail;
+
1727 }
+
1728
+
1729 res = rename(PATH("a/d"), PATH2("a/b"));
+
1730 if (res == -1) {
+
1731 PERROR("rename");
+
1732 goto fail;
+
1733 }
+
1734
+
1735 res = mkdir(PATH("a/d"), 0755);
+
1736 if (res == -1) {
+
1737 PERROR("mkdir");
+
1738 goto fail;
+
1739 }
+
1740
+
1741 res = mkdir(PATH("a/d/e"), 0755);
+
1742 if (res == -1) {
+
1743 PERROR("mkdir");
+
1744 goto fail;
+
1745 }
+
1746
+
1747 errno = 0;
+
1748 res = rename(PATH("a/b"), PATH2("a/d"));
+
1749 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
+
1750 PERROR("rename");
+
1751 goto fail;
+
1752 }
+
1753
+
1754 rmdir(PATH("a/d/e"));
+
1755 rmdir(PATH("a/d"));
+
1756
+
1757 rmdir(PATH("a/b/c"));
+
1758 rmdir(PATH("a/b"));
+
1759 rmdir(PATH("a"));
+
1760
+
1761 err += cleanup_dir(testdir, testdir_files, 0);
+
1762 res = rmdir(testdir);
+
1763 if (res == -1) {
+
1764 PERROR("rmdir");
+
1765 goto fail;
+
1766 }
+
1767 res = check_nonexist(testdir);
+
1768 if (res == -1)
+
1769 return -1;
+
1770 if (err)
+
1771 return -1;
+
1772
+
1773 success();
+
1774 return 0;
+
1775
+
1776fail:
+
1777 unlink(PATH("a/bar"));
+
1778
+
1779 rmdir(PATH("a/d/e"));
+
1780 rmdir(PATH("a/d"));
+
1781
+
1782 rmdir(PATH("a/b/c"));
+
1783 rmdir(PATH("a/b"));
+
1784 rmdir(PATH("a"));
+
1785
+
1786 cleanup_dir(testdir, testdir_files, 1);
+
1787 rmdir(testdir);
+
1788
+
1789 return -1;
+
1790
+
1791#undef PATH2
+
1792#undef PATH
+
1793}
+
1794
+
1795#ifndef __FreeBSD__
+
1796static int test_mkfifo(void)
+
1797{
+
1798 int res;
+
1799 int err = 0;
+
1800
+
1801 start_test("mkfifo");
+
1802 unlink(testfile);
+
1803 res = mkfifo(testfile, 0644);
+
1804 if (res == -1) {
+
1805 PERROR("mkfifo");
+
1806 return -1;
+
1807 }
+
1808 res = check_type(testfile, S_IFIFO);
+
1809 if (res == -1)
+
1810 return -1;
+
1811 err += check_mode(testfile, 0644);
+
1812 err += check_nlink(testfile, 1);
+
1813 res = unlink(testfile);
+
1814 if (res == -1) {
+
1815 PERROR("unlink");
+
1816 return -1;
+
1817 }
+
1818 res = check_nonexist(testfile);
+
1819 if (res == -1)
+
1820 return -1;
+
1821 if (err)
+
1822 return -1;
+
1823
+
1824 success();
+
1825 return 0;
+
1826}
+
1827#endif
+
1828
+
1829static int test_mkdir(void)
+
1830{
+
1831 int res;
+
1832 int err = 0;
+
1833 const char *dir_contents[] = {NULL};
+
1834
+
1835 start_test("mkdir");
+
1836 rmdir(testdir);
+
1837 res = mkdir(testdir, 0755);
+
1838 if (res == -1) {
+
1839 PERROR("mkdir");
+
1840 return -1;
+
1841 }
+
1842 res = check_type(testdir, S_IFDIR);
+
1843 if (res == -1)
+
1844 return -1;
+
1845 err += check_mode(testdir, 0755);
+
1846 /* Some file systems (like btrfs) don't track link
+
1847 count for directories */
+
1848 //err += check_nlink(testdir, 2);
+
1849 err += check_dir_contents(testdir, dir_contents);
+
1850 res = rmdir(testdir);
+
1851 if (res == -1) {
+
1852 PERROR("rmdir");
+
1853 return -1;
+
1854 }
+
1855 res = check_nonexist(testdir);
+
1856 if (res == -1)
+
1857 return -1;
+
1858 if (err)
+
1859 return -1;
+
1860
+
1861 success();
+
1862 return 0;
+
1863}
+
1864
+
1865static int test_socket(void)
+
1866{
+
1867 struct sockaddr_un su;
+
1868 int fd;
+
1869 int res;
+
1870 int err = 0;
+
1871 const size_t test_sock_len = strlen(testsock) + 1;
+
1872
+
1873 start_test("socket");
+
1874 if (test_sock_len > sizeof(su.sun_path)) {
+
1875 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
+
1876 strlen(testsock) + 1 - sizeof(su.sun_path));
+
1877 return -1;
+
1878 }
+
1879 unlink(testsock);
+
1880 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
1881 if (fd < 0) {
+
1882 PERROR("socket");
+
1883 return -1;
+
1884 }
+
1885 su.sun_family = AF_UNIX;
+
1886
+
1887 strncpy(su.sun_path, testsock, test_sock_len);
+
1888 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
+
1889 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
+
1890 if (res == -1) {
+
1891 PERROR("bind");
+
1892 return -1;
+
1893 }
+
1894
+
1895 res = check_type(testsock, S_IFSOCK);
+
1896 if (res == -1) {
+
1897 close(fd);
+
1898 return -1;
+
1899 }
+
1900 err += check_nlink(testsock, 1);
+
1901 close(fd);
+
1902 res = unlink(testsock);
+
1903 if (res == -1) {
+
1904 PERROR("unlink");
+
1905 return -1;
+
1906 }
+
1907 res = check_nonexist(testsock);
+
1908 if (res == -1)
+
1909 return -1;
+
1910 if (err)
+
1911 return -1;
+
1912
+
1913 success();
+
1914 return 0;
+
1915}
+
1916
+
1917#define test_create_ro_dir(flags) \
+
1918 do_test_create_ro_dir(flags, #flags)
+
1919
+
1920static int do_test_create_ro_dir(int flags, const char *flags_str)
+
1921{
+
1922 int res;
+
1923 int err = 0;
+
1924 int fd;
+
1925
+
1926 start_test("open(%s) in read-only directory", flags_str);
+
1927 rmdir(testdir);
+
1928 res = mkdir(testdir, 0555);
+
1929 if (res == -1) {
+
1930 PERROR("mkdir");
+
1931 return -1;
+
1932 }
+
1933 fd = open(subfile, flags, 0644);
+
1934 if (fd != -1) {
+
1935 close(fd);
+
1936 unlink(subfile);
+
1937 ERROR("open should have failed");
+
1938 err--;
+
1939 } else {
+
1940 res = check_nonexist(subfile);
+
1941 if (res == -1)
+
1942 err--;
+
1943 }
+
1944 unlink(subfile);
+
1945 res = rmdir(testdir);
+
1946 if (res == -1) {
+
1947 PERROR("rmdir");
+
1948 return -1;
+
1949 }
+
1950 res = check_nonexist(testdir);
+
1951 if (res == -1)
+
1952 return -1;
+
1953 if (err)
+
1954 return -1;
+
1955
+
1956 success();
+
1957 return 0;
+
1958}
+
1959
+
1960/* this tests open with O_TMPFILE
+
1961 note that this will only work with the fuse low level api
+
1962 you will get ENOTSUP with the high level api */
+
1963static int test_create_tmpfile(void)
+
1964{
+
1965 rmdir(testdir);
+
1966 int res = mkdir(testdir, 0777);
+
1967 if (res)
+
1968 return -1;
+
1969
+
1970 start_test("create tmpfile");
+
1971
+
1972 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
1973 if(fd == -1) {
+
1974 if (errno == ENOTSUP) {
+
1975 /* don't bother if we're working on an old kernel
+
1976 or on the high level API */
+
1977 return 0;
+
1978 }
+
1979
+
1980 PERROR("open O_TMPFILE | O_RDWR");
+
1981 return -1;
+
1982 }
+
1983 close(fd);
+
1984
+
1985 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
+
1986 if(fd == -1){
+
1987 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
+
1988 return -1;
+
1989 };
+
1990 close(fd);
+
1991
+
1992 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
+
1993 if (fd != -1) {
+
1994 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
+
1995 return -1;
+
1996 }
+
1997
+
1998 success();
+
1999 return 0;
+
2000}
+
2001
+
2002static int test_create_and_link_tmpfile(void)
+
2003{
+
2004 /* skip this test for now since the github runner will fail in the linkat call below */
+
2005 return 0;
+
2006
+
2007 rmdir(testdir);
+
2008 unlink(testfile);
+
2009
+
2010 int res = mkdir(testdir, 0777);
+
2011 if (res)
+
2012 return -1;
+
2013
+
2014 start_test("create and link tmpfile");
+
2015
+
2016 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
+
2017 if(fd == -1) {
+
2018 if (errno == ENOTSUP) {
+
2019 /* don't bother if we're working on an old kernel
+
2020 or on the high level API */
+
2021 return 0;
+
2022 }
+
2023 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
+
2024 return -1;
+
2025 }
+
2026
+
2027 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2028 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
+
2029 return -1;
+
2030 }
+
2031 close(fd);
+
2032
+
2033 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2034 if(fd == -1) {
+
2035 PERROR("open O_TMPFILE");
+
2036 return -1;
+
2037 }
+
2038
+
2039 if (check_nonexist(testfile)) {
+
2040 return -1;
+
2041 }
+
2042
+
2043 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2044 PERROR("linkat tempfile");
+
2045 return -1;
+
2046 }
+
2047 close(fd);
+
2048
+
2049 if (check_nlink(testfile, 1)) {
+
2050 return -1;
+
2051 }
+
2052 unlink(testfile);
+
2053
+
2054 success();
+
2055 return 0;
+
2056}
+
2057
+
2058int main(int argc, char *argv[])
+
2059{
+
2060 int err = 0;
+
2061 int a;
+
2062 int is_root;
+
2063
+
2064 umask(0);
+
2065 if (argc < 2 || argc > 4) {
+
2066 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
+
2067 return 1;
+
2068 }
+
2069 basepath = argv[1];
+
2070 basepath_r = basepath;
+
2071 for (a = 2; a < argc; a++) {
+
2072 char *endptr;
+
2073 char *arg = argv[a];
+
2074 if (arg[0] == ':') {
+
2075 basepath_r = arg + 1;
+
2076 } else {
+
2077 if (arg[0] == '-') {
+
2078 arg++;
+
2079 if (arg[0] == 'u') {
+
2080 unlinked_test = 1;
+
2081 endptr = arg + 1;
+
2082 } else {
+
2083 skip_test = strtoul(arg, &endptr, 10);
+
2084 }
+
2085 } else {
+
2086 select_test = strtoul(arg, &endptr, 10);
+
2087 }
+
2088 if (arg[0] == '\0' || *endptr != '\0') {
+
2089 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
+
2090 return 1;
+
2091 }
+
2092 }
+
2093 }
+
2094 assert(strlen(basepath) < 512);
+
2095 assert(strlen(basepath_r) < 512);
+
2096 if (basepath[0] != '/') {
+
2097 fprintf(stderr, "testdir must be an absolute path\n");
+
2098 return 1;
+
2099 }
+
2100
+
2101 sprintf(testfile, "%s/testfile", basepath);
+
2102 sprintf(testfile2, "%s/testfile2", basepath);
+
2103 sprintf(testdir, "%s/testdir", basepath);
+
2104 sprintf(testdir2, "%s/testdir2", basepath);
+
2105 sprintf(subfile, "%s/subfile", testdir2);
+
2106 sprintf(testsock, "%s/testsock", basepath);
+
2107
+
2108 sprintf(testfile_r, "%s/testfile", basepath_r);
+
2109 sprintf(testfile2_r, "%s/testfile2", basepath_r);
+
2110 sprintf(testdir_r, "%s/testdir", basepath_r);
+
2111 sprintf(testdir2_r, "%s/testdir2", basepath_r);
+
2112 sprintf(subfile_r, "%s/subfile", testdir2_r);
+
2113
+
2114 is_root = (geteuid() == 0);
+
2115
+
2116 err += test_create();
+
2117 err += test_create_unlink();
+
2118 err += test_symlink();
+
2119 err += test_link();
+
2120 err += test_link2();
+
2121#ifndef __FreeBSD__
+
2122 err += test_mknod();
+
2123 err += test_mkfifo();
+
2124#endif
+
2125 err += test_mkdir();
+
2126 err += test_rename_file();
+
2127 err += test_rename_dir();
+
2128 err += test_rename_dir_loop();
+
2129 err += test_seekdir();
+
2130 err += test_socket();
+
2131 err += test_utime();
+
2132 err += test_truncate(0);
+
2133 err += test_truncate(testdatalen / 2);
+
2134 err += test_truncate(testdatalen);
+
2135 err += test_truncate(testdatalen + 100);
+
2136 err += test_ftruncate(0, 0600);
+
2137 err += test_ftruncate(testdatalen / 2, 0600);
+
2138 err += test_ftruncate(testdatalen, 0600);
+
2139 err += test_ftruncate(testdatalen + 100, 0600);
+
2140 err += test_ftruncate(0, 0400);
+
2141 err += test_ftruncate(0, 0200);
+
2142 err += test_ftruncate(0, 0000);
+
2143 err += test_open(0, O_RDONLY, 0);
+
2144 err += test_open(1, O_RDONLY, 0);
+
2145 err += test_open(1, O_RDWR, 0);
+
2146 err += test_open(1, O_WRONLY, 0);
+
2147 err += test_open(0, O_RDWR | O_CREAT, 0600);
+
2148 err += test_open(1, O_RDWR | O_CREAT, 0600);
+
2149 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2150 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2151 err += test_open(0, O_RDONLY | O_CREAT, 0600);
+
2152 err += test_open(0, O_RDONLY | O_CREAT, 0400);
+
2153 err += test_open(0, O_RDONLY | O_CREAT, 0200);
+
2154 err += test_open(0, O_RDONLY | O_CREAT, 0000);
+
2155 err += test_open(0, O_WRONLY | O_CREAT, 0600);
+
2156 err += test_open(0, O_WRONLY | O_CREAT, 0400);
+
2157 err += test_open(0, O_WRONLY | O_CREAT, 0200);
+
2158 err += test_open(0, O_WRONLY | O_CREAT, 0000);
+
2159 err += test_open(0, O_RDWR | O_CREAT, 0400);
+
2160 err += test_open(0, O_RDWR | O_CREAT, 0200);
+
2161 err += test_open(0, O_RDWR | O_CREAT, 0000);
+
2162 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2163 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2164 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2165 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2166 err += test_open_acc(O_RDONLY, 0600, 0);
+
2167 err += test_open_acc(O_WRONLY, 0600, 0);
+
2168 err += test_open_acc(O_RDWR, 0600, 0);
+
2169 err += test_open_acc(O_RDONLY, 0400, 0);
+
2170 err += test_open_acc(O_WRONLY, 0200, 0);
+
2171 if(!is_root) {
+
2172 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
+
2173 err += test_open_acc(O_WRONLY, 0400, EACCES);
+
2174 err += test_open_acc(O_RDWR, 0400, EACCES);
+
2175 err += test_open_acc(O_RDONLY, 0200, EACCES);
+
2176 err += test_open_acc(O_RDWR, 0200, EACCES);
+
2177 err += test_open_acc(O_RDONLY, 0000, EACCES);
+
2178 err += test_open_acc(O_WRONLY, 0000, EACCES);
+
2179 err += test_open_acc(O_RDWR, 0000, EACCES);
+
2180 }
+
2181 err += test_create_ro_dir(O_CREAT);
+
2182 err += test_create_ro_dir(O_CREAT | O_EXCL);
+
2183 err += test_create_ro_dir(O_CREAT | O_WRONLY);
+
2184 err += test_create_ro_dir(O_CREAT | O_TRUNC);
+
2185 err += test_copy_file_range();
+
2186 err += test_create_tmpfile();
+
2187 err += test_create_and_link_tmpfile();
+
2188
+
2189 unlink(testfile2);
+
2190 unlink(testsock);
+
2191 rmdir(testdir);
+
2192 rmdir(testdir2);
+
2193
+
2194 if (err) {
+
2195 fprintf(stderr, "%i tests failed\n", -err);
+
2196 return 1;
+
2197 }
+
2198
+
2199 return check_unlinked_testfiles();
+
2200}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2test_2test__write__cache_8c_source.html b/doc/html/fuse-3_817_81-rc0_2test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..2f82836 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2test_2test__write__cache_8c_source.html @@ -0,0 +1,403 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test/test_write_cache.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_write_cache.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <sys/stat.h>
+
26#include <pthread.h>
+
27#include <stdatomic.h>
+
28
+
29#ifndef __linux__
+
30#include <limits.h>
+
31#else
+
32#include <linux/limits.h>
+
33#endif
+
34
+
35#define FILE_INO 2
+
36#define FILE_NAME "write_me"
+
37
+
38/* Command line parsing */
+
39struct options {
+
40 int writeback;
+
41 int data_size;
+
42 int delay_ms;
+
43} options = {
+
44 .writeback = 0,
+
45 .data_size = 2048,
+
46 .delay_ms = 0,
+
47};
+
48
+
49#define WRITE_SYSCALLS 64
+
50
+
51#define OPTION(t, p) \
+
52 { t, offsetof(struct options, p), 1 }
+
53static const struct fuse_opt option_spec[] = {
+
54 OPTION("writeback_cache", writeback),
+
55 OPTION("--data-size=%d", data_size),
+
56 OPTION("--delay_ms=%d", delay_ms),
+ +
58};
+
59static int got_write;
+
60static atomic_int write_cnt;
+
61
+
62pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
63pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
64static int write_start, write_done;
+
65
+
66static void tfs_init (void *userdata, struct fuse_conn_info *conn)
+
67{
+
68 (void) userdata;
+
69
+
70 if(options.writeback) {
+
71 assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE));
+ +
73 }
+
74}
+
75
+
76static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
77 stbuf->st_ino = ino;
+
78 if (ino == FUSE_ROOT_ID) {
+
79 stbuf->st_mode = S_IFDIR | 0755;
+
80 stbuf->st_nlink = 1;
+
81 }
+
82
+
83 else if (ino == FILE_INO) {
+
84 stbuf->st_mode = S_IFREG | 0222;
+
85 stbuf->st_nlink = 1;
+
86 stbuf->st_size = 0;
+
87 }
+
88
+
89 else
+
90 return -1;
+
91
+
92 return 0;
+
93}
+
94
+
95static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
96 const char *name) {
+
97 struct fuse_entry_param e;
+
98 memset(&e, 0, sizeof(e));
+
99
+
100 if (parent != FUSE_ROOT_ID)
+
101 goto err_out;
+
102 else if (strcmp(name, FILE_NAME) == 0)
+
103 e.ino = FILE_INO;
+
104 else
+
105 goto err_out;
+
106
+
107 if (tfs_stat(e.ino, &e.attr) != 0)
+
108 goto err_out;
+
109 fuse_reply_entry(req, &e);
+
110 return;
+
111
+
112err_out:
+
113 fuse_reply_err(req, ENOENT);
+
114}
+
115
+
116static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
117 struct fuse_file_info *fi) {
+
118 struct stat stbuf;
+
119
+
120 (void) fi;
+
121
+
122 memset(&stbuf, 0, sizeof(stbuf));
+
123 if (tfs_stat(ino, &stbuf) != 0)
+
124 fuse_reply_err(req, ENOENT);
+
125 else
+
126 fuse_reply_attr(req, &stbuf, 5);
+
127}
+
128
+
129static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
130 struct fuse_file_info *fi) {
+
131 if (ino == FUSE_ROOT_ID)
+
132 fuse_reply_err(req, EISDIR);
+
133 else {
+
134 assert(ino == FILE_INO);
+
135 /* Test close(rofd) does not block waiting for pending writes */
+
136 fi->noflush = !options.writeback && options.delay_ms &&
+
137 (fi->flags & O_ACCMODE) == O_RDONLY;
+
138 fuse_reply_open(req, fi);
+
139 }
+
140}
+
141
+
142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
143 size_t size, off_t off, struct fuse_file_info *fi) {
+
144 (void) fi; (void) buf; (void) off;
+
145 size_t expected;
+
146
+
147 assert(ino == FILE_INO);
+
148 expected = options.data_size;
+
149 if(options.writeback)
+
150 expected *= 2;
+
151
+
152 write_cnt++;
+
153
+
154 if(size != expected && !options.writeback)
+
155 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
+
156 expected, size);
+
157 else
+
158 got_write = 1;
+
159
+
160 /* Simulate waiting for pending writes */
+
161 if (options.delay_ms) {
+
162 pthread_mutex_lock(&lock);
+
163 write_start = 1;
+
164 pthread_cond_signal(&cond);
+
165 pthread_mutex_unlock(&lock);
+
166
+
167 usleep(options.delay_ms * 1000);
+
168
+
169 pthread_mutex_lock(&lock);
+
170 write_done = 1;
+
171 pthread_cond_signal(&cond);
+
172 pthread_mutex_unlock(&lock);
+
173 }
+
174
+
175 fuse_reply_write(req, size);
+
176}
+
177
+
178static struct fuse_lowlevel_ops tfs_oper = {
+
179 .init = tfs_init,
+
180 .lookup = tfs_lookup,
+
181 .getattr = tfs_getattr,
+
182 .open = tfs_open,
+
183 .write = tfs_write,
+
184};
+
185
+
186static void* close_rofd(void *data) {
+
187 int rofd = (int)(long) data;
+
188
+
189 /* Wait for first write to start */
+
190 pthread_mutex_lock(&lock);
+
191 while (!write_start && !write_done)
+
192 pthread_cond_wait(&cond, &lock);
+
193 pthread_mutex_unlock(&lock);
+
194
+
195 close(rofd);
+
196 printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
197
+
198 /* First write should not have been completed */
+
199 if (write_done)
+
200 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
+
201
+
202 return NULL;
+
203}
+
204
+
205static void* run_fs(void *data) {
+
206 struct fuse_session *se = (struct fuse_session*) data;
+
207 assert(fuse_session_loop(se) == 0);
+
208 return NULL;
+
209}
+
210
+
211static void test_fs(char *mountpoint) {
+
212 char fname[PATH_MAX];
+
213 char *buf;
+
214 const size_t iosize = options.data_size;
+
215 const size_t dsize = options.data_size * WRITE_SYSCALLS;
+
216 int fd, rofd;
+
217 pthread_t rofd_thread;
+
218 off_t off = 0;
+
219
+
220 buf = malloc(dsize);
+
221 assert(buf != NULL);
+
222 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
+
223 assert(read(fd, buf, dsize) == dsize);
+
224 close(fd);
+
225
+
226 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
227 mountpoint) > 0);
+
228 fd = open(fname, O_WRONLY);
+
229 if (fd == -1) {
+
230 perror(fname);
+
231 assert(0);
+
232 }
+
233
+
234 if (options.delay_ms) {
+
235 /* Verify that close(rofd) does not block waiting for pending writes */
+
236 rofd = open(fname, O_RDONLY);
+
237 assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0);
+
238 /* Give close_rofd time to start */
+
239 usleep(options.delay_ms * 1000);
+
240 }
+
241
+
242 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
+
243 assert(pwrite(fd, buf + off, iosize, off) == iosize);
+
244 off += iosize;
+
245 assert(off <= dsize);
+
246 }
+
247 free(buf);
+
248 close(fd);
+
249
+
250 if (options.delay_ms) {
+
251 printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
252 assert(pthread_join(rofd_thread, NULL) == 0);
+
253 }
+
254}
+
255
+
256int main(int argc, char *argv[]) {
+
257 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
258 struct fuse_session *se;
+
259 struct fuse_cmdline_opts fuse_opts;
+
260 pthread_t fs_thread;
+
261
+
262 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
+
263 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
264#ifndef __FreeBSD__
+
265 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
266#endif
+
267 se = fuse_session_new(&args, &tfs_oper,
+
268 sizeof(tfs_oper), NULL);
+
269 fuse_opt_free_args(&args);
+
270 assert (se != NULL);
+
271 assert(fuse_set_signal_handlers(se) == 0);
+
272 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
273
+
274 /* Start file-system thread */
+
275 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
276
+
277 /* Write test data */
+
278 test_fs(fuse_opts.mountpoint);
+
279 free(fuse_opts.mountpoint);
+
280
+
281 /* Stop file system */
+ + +
284 assert(pthread_join(fs_thread, NULL) == 0);
+
285
+
286 assert(got_write == 1);
+
287
+
288 /*
+
289 * when writeback cache is enabled, kernel side can merge requests, but
+
290 * memory pressure, system 'sync' might trigger data flushes before - flush
+
291 * might happen in between write syscalls - merging subpage writes into
+
292 * a single page and pages into large fuse requests might or might not work.
+
293 * Though we can expect that that at least some (but maybe all) write
+
294 * system calls can be merged.
+
295 */
+
296 if (options.writeback)
+
297 assert(write_cnt < WRITE_SYSCALLS);
+
298 else
+
299 assert(write_cnt == WRITE_SYSCALLS);
+
300
+ + +
303
+
304 printf("Test completed successfully.\n");
+
305 return 0;
+
306}
+
307
+
308
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
+
fuse_ino_t ino
+ +
uint32_t noflush
+ + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2test_2wrong__command_8c_source.html b/doc/html/fuse-3_817_81-rc0_2test_2wrong__command_8c_source.html new file mode 100644 index 0000000..afc6813 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/test/wrong_command.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
wrong_command.c
+
+
+
1#include <stdio.h>
+
2
+
3int main(void) {
+
4#ifdef MESON_IS_SUBPROJECT
+
5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
+
6 "If you wish to run them try:\n"
+
7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
+
8 return 77; /* report as a skipped test */
+
9#else
+
10 fprintf(stderr, "\x1B[31m\e[1m"
+
11 "This is not the command you are looking for.\n"
+
12 "You probably want to run 'python3 -m pytest test/' instead"
+
13 "\e[0m\n");
+
14 return 1;
+
15#endif
+
16}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2util_2fusermount_8c_source.html b/doc/html/fuse-3_817_81-rc0_2util_2fusermount_8c_source.html new file mode 100644 index 0000000..25bfe4a --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2util_2fusermount_8c_source.html @@ -0,0 +1,1767 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/util/fusermount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone and strchrnul */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
40
+
41#define FUSE_DEV "/dev/fuse"
+
42
+
43static const char *progname;
+
44
+
45static int user_allow_other = 0;
+
46static int mount_max = 1000;
+
47
+
48static int auto_unmount = 0;
+
49
+
50#ifdef GETMNTENT_NEEDS_UNESCAPING
+
51// Older versions of musl libc don't unescape entries in /etc/mtab
+
52
+
53// unescapes octal sequences like \040 in-place
+
54// That's ok, because unescaping can not extend the length of the string.
+
55static void unescape(char *buf) {
+
56 char *src = buf;
+
57 char *dest = buf;
+
58 while (1) {
+
59 char *next_src = strchrnul(src, '\\');
+
60 int offset = next_src - src;
+
61 memmove(dest, src, offset);
+
62 src = next_src;
+
63 dest += offset;
+
64
+
65 if(*src == '\0') {
+
66 *dest = *src;
+
67 return;
+
68 }
+
69 src++;
+
70
+
71 if('0' <= src[0] && src[0] < '2' &&
+
72 '0' <= src[1] && src[1] < '8' &&
+
73 '0' <= src[2] && src[2] < '8') {
+
74 *dest++ = (src[0] - '0') << 6
+
75 | (src[1] - '0') << 3
+
76 | (src[2] - '0') << 0;
+
77 src += 3;
+
78 } else if (src[0] == '\\') {
+
79 *dest++ = '\\';
+
80 src += 1;
+
81 } else {
+
82 *dest++ = '\\';
+
83 }
+
84 }
+
85}
+
86
+
87static struct mntent *GETMNTENT(FILE *stream)
+
88{
+
89 struct mntent *entp = getmntent(stream);
+
90 if(entp != NULL) {
+
91 unescape(entp->mnt_fsname);
+
92 unescape(entp->mnt_dir);
+
93 unescape(entp->mnt_type);
+
94 unescape(entp->mnt_opts);
+
95 }
+
96 return entp;
+
97}
+
98#else
+
99#define GETMNTENT getmntent
+
100#endif // GETMNTENT_NEEDS_UNESCAPING
+
101
+
102/*
+
103 * Take a ',' separated option string and extract "x-" options
+
104 */
+
105static int extract_x_options(const char *original, char **non_x_opts,
+
106 char **x_opts)
+
107{
+
108 size_t orig_len;
+
109 const char *opt, *opt_end;
+
110
+
111 orig_len = strlen(original) + 1;
+
112
+
113 *non_x_opts = calloc(1, orig_len);
+
114 *x_opts = calloc(1, orig_len);
+
115
+
116 size_t non_x_opts_len = orig_len;
+
117 size_t x_opts_len = orig_len;
+
118
+
119 if (*non_x_opts == NULL || *x_opts == NULL) {
+
120 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
121 __func__, orig_len);
+
122 return -ENOMEM;
+
123 }
+
124
+
125 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
126 char *opt_buf;
+
127
+
128 opt_end = strchr(opt, ',');
+
129 if (opt_end == NULL)
+
130 opt_end = original + orig_len;
+
131
+
132 size_t opt_len = opt_end - opt;
+
133 size_t opt_len_left = orig_len - (opt - original);
+
134 size_t buf_len;
+
135 bool is_x_opts;
+
136
+
137 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
138 buf_len = x_opts_len;
+
139 is_x_opts = true;
+
140 opt_buf = *x_opts;
+
141 } else {
+
142 buf_len = non_x_opts_len;
+
143 is_x_opts = false;
+
144 opt_buf = *non_x_opts;
+
145 }
+
146
+
147 if (buf_len < orig_len) {
+
148 strncat(opt_buf, ",", 2);
+
149 buf_len -= 1;
+
150 }
+
151
+
152 /* omits ',' */
+
153 if ((ssize_t)(buf_len - opt_len) < 0) {
+
154 /* This would be a bug */
+
155 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
156 __func__, original);
+
157 return -EIO;
+
158 }
+
159
+
160 strncat(opt_buf, opt, opt_end - opt);
+
161 buf_len -= opt_len;
+
162
+
163 if (is_x_opts)
+
164 x_opts_len = buf_len;
+
165 else
+
166 non_x_opts_len = buf_len;
+
167 }
+
168
+
169 return 0;
+
170}
+
171
+
172static const char *get_user_name(void)
+
173{
+
174 struct passwd *pw = getpwuid(getuid());
+
175 if (pw != NULL && pw->pw_name != NULL)
+
176 return pw->pw_name;
+
177 else {
+
178 fprintf(stderr, "%s: could not determine username\n", progname);
+
179 return NULL;
+
180 }
+
181}
+
182
+
183static uid_t oldfsuid;
+
184static gid_t oldfsgid;
+
185
+
186static void drop_privs(void)
+
187{
+
188 if (getuid() != 0) {
+
189 oldfsuid = setfsuid(getuid());
+
190 oldfsgid = setfsgid(getgid());
+
191 }
+
192}
+
193
+
194static void restore_privs(void)
+
195{
+
196 if (getuid() != 0) {
+
197 setfsuid(oldfsuid);
+
198 setfsgid(oldfsgid);
+
199 }
+
200}
+
201
+
202#ifndef IGNORE_MTAB
+
203/*
+
204 * Make sure that /etc/mtab is checked and updated atomically
+
205 */
+
206static int lock_umount(void)
+
207{
+
208 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
209 int mtablock;
+
210 int res;
+
211 struct stat mtab_stat;
+
212
+
213 /* /etc/mtab could be a symlink to /proc/mounts */
+
214 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
215 return -1;
+
216
+
217 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
218 if (mtablock == -1) {
+
219 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
220 progname, strerror(errno));
+
221 return -1;
+
222 }
+
223 res = lockf(mtablock, F_LOCK, 0);
+
224 if (res < 0) {
+
225 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
226 strerror(errno));
+
227 close(mtablock);
+
228 return -1;
+
229 }
+
230
+
231 return mtablock;
+
232}
+
233
+
234static void unlock_umount(int mtablock)
+
235{
+
236 if (mtablock >= 0) {
+
237 int res;
+
238
+
239 res = lockf(mtablock, F_ULOCK, 0);
+
240 if (res < 0) {
+
241 fprintf(stderr, "%s: error releasing lock: %s\n",
+
242 progname, strerror(errno));
+
243 }
+
244 close(mtablock);
+
245 }
+
246}
+
247
+
248static int add_mount(const char *source, const char *mnt, const char *type,
+
249 const char *opts)
+
250{
+
251 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
252}
+
253
+
254static int may_unmount(const char *mnt, int quiet)
+
255{
+
256 struct mntent *entp;
+
257 FILE *fp;
+
258 const char *user = NULL;
+
259 char uidstr[32];
+
260 unsigned uidlen = 0;
+
261 int found;
+
262 const char *mtab = _PATH_MOUNTED;
+
263
+
264 user = get_user_name();
+
265 if (user == NULL)
+
266 return -1;
+
267
+
268 fp = setmntent(mtab, "r");
+
269 if (fp == NULL) {
+
270 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
271 strerror(errno));
+
272 return -1;
+
273 }
+
274
+
275 uidlen = sprintf(uidstr, "%u", getuid());
+
276
+
277 found = 0;
+
278 while ((entp = GETMNTENT(fp)) != NULL) {
+
279 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
280 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
281 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
282 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
283 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
284 char *p = strstr(entp->mnt_opts, "user=");
+
285 if (p &&
+
286 (p == entp->mnt_opts || *(p-1) == ',') &&
+
287 strcmp(p + 5, user) == 0) {
+
288 found = 1;
+
289 break;
+
290 }
+
291 /* /etc/mtab is a link pointing to
+
292 /proc/mounts: */
+
293 else if ((p =
+
294 strstr(entp->mnt_opts, "user_id=")) &&
+
295 (p == entp->mnt_opts ||
+
296 *(p-1) == ',') &&
+
297 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
298 (*(p+8+uidlen) == ',' ||
+
299 *(p+8+uidlen) == '\0')) {
+
300 found = 1;
+
301 break;
+
302 }
+
303 }
+
304 }
+
305 endmntent(fp);
+
306
+
307 if (!found) {
+
308 if (!quiet)
+
309 fprintf(stderr,
+
310 "%s: entry for %s not found in %s\n",
+
311 progname, mnt, mtab);
+
312 return -1;
+
313 }
+
314
+
315 return 0;
+
316}
+
317#endif
+
318
+
319/*
+
320 * Check whether the file specified in "fusermount3 -u" is really a
+
321 * mountpoint and not a symlink. This is necessary otherwise the user
+
322 * could move the mountpoint away and replace it with a symlink
+
323 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
324 * unmounting that (umount(2) will follow symlinks).
+
325 *
+
326 * This is the child process running in a separate mount namespace, so
+
327 * we don't mess with the global namespace and if the process is
+
328 * killed for any reason, mounts are automatically cleaned up.
+
329 *
+
330 * First make sure nothing is propagated back into the parent
+
331 * namespace by marking all mounts "private".
+
332 *
+
333 * Then bind mount parent onto a stable base where the user can't move
+
334 * it around.
+
335 *
+
336 * Finally check /proc/mounts for an entry matching the requested
+
337 * mountpoint. If it's found then we are OK, and the user can't move
+
338 * it around within the parent directory as rename() will return
+
339 * EBUSY. Be careful to ignore any mounts that existed before the
+
340 * bind.
+
341 */
+
342static int check_is_mount_child(void *p)
+
343{
+
344 const char **a = p;
+
345 const char *last = a[0];
+
346 const char *mnt = a[1];
+
347 const char *type = a[2];
+
348 int res;
+
349 const char *procmounts = "/proc/mounts";
+
350 int found;
+
351 FILE *fp;
+
352 struct mntent *entp;
+
353 int count;
+
354
+
355 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
356 if (res == -1) {
+
357 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
358 progname, strerror(errno));
+
359 return 1;
+
360 }
+
361
+
362 fp = setmntent(procmounts, "r");
+
363 if (fp == NULL) {
+
364 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
365 procmounts, strerror(errno));
+
366 return 1;
+
367 }
+
368
+
369 count = 0;
+
370 while (GETMNTENT(fp) != NULL)
+
371 count++;
+
372 endmntent(fp);
+
373
+
374 fp = setmntent(procmounts, "r");
+
375 if (fp == NULL) {
+
376 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
377 procmounts, strerror(errno));
+
378 return 1;
+
379 }
+
380
+
381 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
382 if (res == -1) {
+
383 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
384 progname, strerror(errno));
+
385 return 1;
+
386 }
+
387
+
388 found = 0;
+
389 while ((entp = GETMNTENT(fp)) != NULL) {
+
390 if (count > 0) {
+
391 count--;
+
392 continue;
+
393 }
+
394 if (entp->mnt_dir[0] == '/' &&
+
395 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
396 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
397 found = 1;
+
398 break;
+
399 }
+
400 }
+
401 endmntent(fp);
+
402
+
403 if (!found) {
+
404 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
405 return 1;
+
406 }
+
407
+
408 return 0;
+
409}
+
410
+
411static pid_t clone_newns(void *a)
+
412{
+
413 char buf[131072];
+
414 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
415
+
416#ifdef __ia64__
+
417 extern int __clone2(int (*fn)(void *),
+
418 void *child_stack_base, size_t stack_size,
+
419 int flags, void *arg, pid_t *ptid,
+
420 void *tls, pid_t *ctid);
+
421
+
422 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
423 CLONE_NEWNS, a, NULL, NULL, NULL);
+
424#else
+
425 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
426#endif
+
427}
+
428
+
429static int check_is_mount(const char *last, const char *mnt, const char *type)
+
430{
+
431 pid_t pid, p;
+
432 int status;
+
433 const char *a[3] = { last, mnt, type };
+
434
+
435 pid = clone_newns((void *) a);
+
436 if (pid == (pid_t) -1) {
+
437 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
438 progname, strerror(errno));
+
439 return -1;
+
440 }
+
441 p = waitpid(pid, &status, __WCLONE);
+
442 if (p == (pid_t) -1) {
+
443 fprintf(stderr, "%s: waitpid failed: %s\n",
+
444 progname, strerror(errno));
+
445 return -1;
+
446 }
+
447 if (!WIFEXITED(status)) {
+
448 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
449 progname, status);
+
450 return -1;
+
451 }
+
452 if (WEXITSTATUS(status) != 0)
+
453 return -1;
+
454
+
455 return 0;
+
456}
+
457
+
458static int chdir_to_parent(char *copy, const char **lastp)
+
459{
+
460 char *tmp;
+
461 const char *parent;
+
462 char buf[65536];
+
463 int res;
+
464
+
465 tmp = strrchr(copy, '/');
+
466 if (tmp == NULL || tmp[1] == '\0') {
+
467 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
468 progname, copy);
+
469 return -1;
+
470 }
+
471 if (tmp != copy) {
+
472 *tmp = '\0';
+
473 parent = copy;
+
474 *lastp = tmp + 1;
+
475 } else if (tmp[1] != '\0') {
+
476 *lastp = tmp + 1;
+
477 parent = "/";
+
478 } else {
+
479 *lastp = ".";
+
480 parent = "/";
+
481 }
+
482
+
483 res = chdir(parent);
+
484 if (res == -1) {
+
485 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
486 progname, parent, strerror(errno));
+
487 return -1;
+
488 }
+
489
+
490 if (getcwd(buf, sizeof(buf)) == NULL) {
+
491 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
492 progname, strerror(errno));
+
493 return -1;
+
494 }
+
495 if (strcmp(buf, parent) != 0) {
+
496 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
497 parent, buf);
+
498 return -1;
+
499
+
500 }
+
501
+
502 return 0;
+
503}
+
504
+
505#ifndef IGNORE_MTAB
+
506static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
507{
+
508 int res;
+
509 char *copy;
+
510 const char *last;
+
511 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
512
+
513 if (getuid() != 0) {
+
514 res = may_unmount(mnt, quiet);
+
515 if (res == -1)
+
516 return -1;
+
517 }
+
518
+
519 copy = strdup(mnt);
+
520 if (copy == NULL) {
+
521 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
522 return -1;
+
523 }
+
524
+
525 drop_privs();
+
526 res = chdir_to_parent(copy, &last);
+
527 if (res == -1) {
+
528 restore_privs();
+
529 goto out;
+
530 }
+
531
+
532 res = umount2(last, umount_flags);
+
533 restore_privs();
+
534 if (res == -1 && !quiet) {
+
535 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
536 progname, mnt, strerror(errno));
+
537 }
+
538
+
539out:
+
540 free(copy);
+
541 if (res == -1)
+
542 return -1;
+
543
+
544 res = chdir("/");
+
545 if (res == -1) {
+
546 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
547 return -1;
+
548 }
+
549
+
550 return fuse_mnt_remove_mount(progname, mnt);
+
551}
+
552
+
553static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
554{
+
555 int res;
+
556 int mtablock = lock_umount();
+
557
+
558 res = unmount_fuse_locked(mnt, quiet, lazy);
+
559 unlock_umount(mtablock);
+
560
+
561 return res;
+
562}
+
563
+
564static int count_fuse_fs(void)
+
565{
+
566 struct mntent *entp;
+
567 int count = 0;
+
568 const char *mtab = _PATH_MOUNTED;
+
569 FILE *fp = setmntent(mtab, "r");
+
570 if (fp == NULL) {
+
571 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
572 strerror(errno));
+
573 return -1;
+
574 }
+
575 while ((entp = GETMNTENT(fp)) != NULL) {
+
576 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
577 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
578 count ++;
+
579 }
+
580 endmntent(fp);
+
581 return count;
+
582}
+
583
+
584
+
585#else /* IGNORE_MTAB */
+
586static int count_fuse_fs(void)
+
587{
+
588 return 0;
+
589}
+
590
+
591static int add_mount(const char *source, const char *mnt, const char *type,
+
592 const char *opts)
+
593{
+
594 (void) source;
+
595 (void) mnt;
+
596 (void) type;
+
597 (void) opts;
+
598 return 0;
+
599}
+
600
+
601static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
602{
+
603 (void) quiet;
+
604 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
605}
+
606#endif /* IGNORE_MTAB */
+
607
+
608static void strip_line(char *line)
+
609{
+
610 char *s = strchr(line, '#');
+
611 if (s != NULL)
+
612 s[0] = '\0';
+
613 for (s = line + strlen(line) - 1;
+
614 s >= line && isspace((unsigned char) *s); s--);
+
615 s[1] = '\0';
+
616 for (s = line; isspace((unsigned char) *s); s++);
+
617 if (s != line)
+
618 memmove(line, s, strlen(s)+1);
+
619}
+
620
+
621static void parse_line(char *line, int linenum)
+
622{
+
623 int tmp;
+
624 if (strcmp(line, "user_allow_other") == 0)
+
625 user_allow_other = 1;
+
626 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+
627 mount_max = tmp;
+
628 else if(line[0])
+
629 fprintf(stderr,
+
630 "%s: unknown parameter in %s at line %i: '%s'\n",
+
631 progname, FUSE_CONF, linenum, line);
+
632}
+
633
+
634static void read_conf(void)
+
635{
+
636 FILE *fp = fopen(FUSE_CONF, "r");
+
637 if (fp != NULL) {
+
638 int linenum = 1;
+
639 char line[256];
+
640 int isnewline = 1;
+
641 while (fgets(line, sizeof(line), fp) != NULL) {
+
642 if (isnewline) {
+
643 if (line[strlen(line)-1] == '\n') {
+
644 strip_line(line);
+
645 parse_line(line, linenum);
+
646 } else {
+
647 isnewline = 0;
+
648 }
+
649 } else if(line[strlen(line)-1] == '\n') {
+
650 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
651
+
652 isnewline = 1;
+
653 }
+
654 if (isnewline)
+
655 linenum ++;
+
656 }
+
657 if (!isnewline) {
+
658 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
659
+
660 }
+
661 if (ferror(fp)) {
+
662 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
663 exit(1);
+
664 }
+
665 fclose(fp);
+
666 } else if (errno != ENOENT) {
+
667 bool fatal = (errno != EACCES && errno != ELOOP &&
+
668 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
669 errno != EOVERFLOW);
+
670 fprintf(stderr, "%s: failed to open %s: %s\n",
+
671 progname, FUSE_CONF, strerror(errno));
+
672 if (fatal)
+
673 exit(1);
+
674 }
+
675}
+
676
+
677static int begins_with(const char *s, const char *beg)
+
678{
+
679 if (strncmp(s, beg, strlen(beg)) == 0)
+
680 return 1;
+
681 else
+
682 return 0;
+
683}
+
684
+
685struct mount_flags {
+
686 const char *opt;
+
687 unsigned long flag;
+
688 int on;
+
689 int safe;
+
690};
+
691
+
692static struct mount_flags mount_flags[] = {
+
693 {"rw", MS_RDONLY, 0, 1},
+
694 {"ro", MS_RDONLY, 1, 1},
+
695 {"suid", MS_NOSUID, 0, 0},
+
696 {"nosuid", MS_NOSUID, 1, 1},
+
697 {"dev", MS_NODEV, 0, 0},
+
698 {"nodev", MS_NODEV, 1, 1},
+
699 {"exec", MS_NOEXEC, 0, 1},
+
700 {"noexec", MS_NOEXEC, 1, 1},
+
701 {"async", MS_SYNCHRONOUS, 0, 1},
+
702 {"sync", MS_SYNCHRONOUS, 1, 1},
+
703 {"atime", MS_NOATIME, 0, 1},
+
704 {"noatime", MS_NOATIME, 1, 1},
+
705 {"diratime", MS_NODIRATIME, 0, 1},
+
706 {"nodiratime", MS_NODIRATIME, 1, 1},
+
707 {"lazytime", MS_LAZYTIME, 1, 1},
+
708 {"nolazytime", MS_LAZYTIME, 0, 1},
+
709 {"relatime", MS_RELATIME, 1, 1},
+
710 {"norelatime", MS_RELATIME, 0, 1},
+
711 {"strictatime", MS_STRICTATIME, 1, 1},
+
712 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
713 {"dirsync", MS_DIRSYNC, 1, 1},
+
714 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
715 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
716 {NULL, 0, 0, 0}
+
717};
+
718
+
719static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
720{
+
721 int i;
+
722
+
723 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
724 const char *opt = mount_flags[i].opt;
+
725 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
726 *on = mount_flags[i].on;
+
727 *flag = mount_flags[i].flag;
+
728 if (!mount_flags[i].safe && getuid() != 0) {
+
729 *flag = 0;
+
730 fprintf(stderr,
+
731 "%s: unsafe option %s ignored\n",
+
732 progname, opt);
+
733 }
+
734 return 1;
+
735 }
+
736 }
+
737 return 0;
+
738}
+
739
+
740static int add_option(char **optsp, const char *opt, unsigned expand)
+
741{
+
742 char *newopts;
+
743 if (*optsp == NULL)
+
744 newopts = strdup(opt);
+
745 else {
+
746 unsigned oldsize = strlen(*optsp);
+
747 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
748 newopts = (char *) realloc(*optsp, newsize);
+
749 if (newopts)
+
750 sprintf(newopts + oldsize, ",%s", opt);
+
751 }
+
752 if (newopts == NULL) {
+
753 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
754 return -1;
+
755 }
+
756 *optsp = newopts;
+
757 return 0;
+
758}
+
759
+
760static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
761{
+
762 int i;
+
763 int l;
+
764
+
765 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
766 return -1;
+
767
+
768 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
769 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
770 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
771 return -1;
+
772 }
+
773
+
774 if (add_option(mnt_optsp, opts, 0) == -1)
+
775 return -1;
+
776 /* remove comma from end of opts*/
+
777 l = strlen(*mnt_optsp);
+
778 if ((*mnt_optsp)[l-1] == ',')
+
779 (*mnt_optsp)[l-1] = '\0';
+
780 if (getuid() != 0) {
+
781 const char *user = get_user_name();
+
782 if (user == NULL)
+
783 return -1;
+
784
+
785 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
786 return -1;
+
787 strcat(*mnt_optsp, user);
+
788 }
+
789 return 0;
+
790}
+
791
+
792static int opt_eq(const char *s, unsigned len, const char *opt)
+
793{
+
794 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
795 return 1;
+
796 else
+
797 return 0;
+
798}
+
799
+
800static int get_string_opt(const char *s, unsigned len, const char *opt,
+
801 char **val)
+
802{
+
803 int i;
+
804 unsigned opt_len = strlen(opt);
+
805 char *d;
+
806
+
807 if (*val)
+
808 free(*val);
+
809 *val = (char *) malloc(len - opt_len + 1);
+
810 if (!*val) {
+
811 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
812 return 0;
+
813 }
+
814
+
815 d = *val;
+
816 s += opt_len;
+
817 len -= opt_len;
+
818 for (i = 0; i < len; i++) {
+
819 if (s[i] == '\\' && i + 1 < len)
+
820 i++;
+
821 *d++ = s[i];
+
822 }
+
823 *d = '\0';
+
824 return 1;
+
825}
+
826
+
827/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
828 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
829 * "group_id=1".
+
830 * This wrapper detects this case and bails out with an error.
+
831 */
+
832static int mount_notrunc(const char *source, const char *target,
+
833 const char *filesystemtype, unsigned long mountflags,
+
834 const char *data) {
+
835 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
836 fprintf(stderr, "%s: mount options too long\n", progname);
+
837 errno = EINVAL;
+
838 return -1;
+
839 }
+
840 return mount(source, target, filesystemtype, mountflags, data);
+
841}
+
842
+
843
+
844static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
845 int fd, const char *opts, const char *dev, char **sourcep,
+
846 char **mnt_optsp)
+
847{
+
848 int res;
+
849 int flags = MS_NOSUID | MS_NODEV;
+
850 char *optbuf;
+
851 char *mnt_opts = NULL;
+
852 const char *s;
+
853 char *d;
+
854 char *fsname = NULL;
+
855 char *subtype = NULL;
+
856 char *source = NULL;
+
857 char *type = NULL;
+
858 int blkdev = 0;
+
859
+
860 optbuf = (char *) malloc(strlen(opts) + 128);
+
861 if (!optbuf) {
+
862 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
863 return -1;
+
864 }
+
865
+
866 for (s = opts, d = optbuf; *s;) {
+
867 unsigned len;
+
868 const char *fsname_str = "fsname=";
+
869 const char *subtype_str = "subtype=";
+
870 bool escape_ok = begins_with(s, fsname_str) ||
+
871 begins_with(s, subtype_str);
+
872 for (len = 0; s[len]; len++) {
+
873 if (escape_ok && s[len] == '\\' && s[len + 1])
+
874 len++;
+
875 else if (s[len] == ',')
+
876 break;
+
877 }
+
878 if (begins_with(s, fsname_str)) {
+
879 if (!get_string_opt(s, len, fsname_str, &fsname))
+
880 goto err;
+
881 } else if (begins_with(s, subtype_str)) {
+
882 if (!get_string_opt(s, len, subtype_str, &subtype))
+
883 goto err;
+
884 } else if (opt_eq(s, len, "blkdev")) {
+
885 if (getuid() != 0) {
+
886 fprintf(stderr,
+
887 "%s: option blkdev is privileged\n",
+
888 progname);
+
889 goto err;
+
890 }
+
891 blkdev = 1;
+
892 } else if (opt_eq(s, len, "auto_unmount")) {
+
893 auto_unmount = 1;
+
894 } else if (!opt_eq(s, len, "nonempty") &&
+
895 !begins_with(s, "fd=") &&
+
896 !begins_with(s, "rootmode=") &&
+
897 !begins_with(s, "user_id=") &&
+
898 !begins_with(s, "group_id=")) {
+
899 int on;
+
900 int flag;
+
901 int skip_option = 0;
+
902 if (opt_eq(s, len, "large_read")) {
+
903 struct utsname utsname;
+
904 unsigned kmaj, kmin;
+
905 res = uname(&utsname);
+
906 if (res == 0 &&
+
907 sscanf(utsname.release, "%u.%u",
+
908 &kmaj, &kmin) == 2 &&
+
909 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
910 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
911 skip_option = 1;
+
912 }
+
913 }
+
914 if (getuid() != 0 && !user_allow_other &&
+
915 (opt_eq(s, len, "allow_other") ||
+
916 opt_eq(s, len, "allow_root"))) {
+
917 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
918 goto err;
+
919 }
+
920 if (!skip_option) {
+
921 if (find_mount_flag(s, len, &on, &flag)) {
+
922 if (on)
+
923 flags |= flag;
+
924 else
+
925 flags &= ~flag;
+
926 } else if (opt_eq(s, len, "default_permissions") ||
+
927 opt_eq(s, len, "allow_other") ||
+
928 begins_with(s, "max_read=") ||
+
929 begins_with(s, "blksize=")) {
+
930 memcpy(d, s, len);
+
931 d += len;
+
932 *d++ = ',';
+
933 } else {
+
934 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
935 exit(1);
+
936 }
+
937 }
+
938 }
+
939 s += len;
+
940 if (*s)
+
941 s++;
+
942 }
+
943 *d = '\0';
+
944 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
945 if (res == -1)
+
946 goto err;
+
947
+
948 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
949 fd, rootmode, getuid(), getgid());
+
950
+
951 source = malloc((fsname ? strlen(fsname) : 0) +
+
952 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
953
+
954 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
955 if (!type || !source) {
+
956 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
957 goto err;
+
958 }
+
959
+
960 if (subtype)
+
961 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
962 else
+
963 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
964
+
965 if (fsname)
+
966 strcpy(source, fsname);
+
967 else
+
968 strcpy(source, subtype ? subtype : dev);
+
969
+
970 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
971 if (res == -1 && errno == ENODEV && subtype) {
+
972 /* Probably missing subtype support */
+
973 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
974 if (fsname) {
+
975 if (!blkdev)
+
976 sprintf(source, "%s#%s", subtype, fsname);
+
977 } else {
+
978 strcpy(source, type);
+
979 }
+
980
+
981 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
982 }
+
983 if (res == -1 && errno == EINVAL) {
+
984 /* It could be an old version not supporting group_id */
+
985 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
986 fd, rootmode, getuid());
+
987 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
988 }
+
989 if (res == -1) {
+
990 int errno_save = errno;
+
991 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
992 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
993 progname);
+
994 else
+
995 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
996 strerror(errno_save));
+
997 goto err;
+
998 }
+
999 *sourcep = source;
+
1000 *typep = type;
+
1001 *mnt_optsp = mnt_opts;
+
1002 free(fsname);
+
1003 free(optbuf);
+
1004
+
1005 return 0;
+
1006
+
1007err:
+
1008 free(fsname);
+
1009 free(subtype);
+
1010 free(source);
+
1011 free(type);
+
1012 free(mnt_opts);
+
1013 free(optbuf);
+
1014 return -1;
+
1015}
+
1016
+
1017static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1018{
+
1019 int res;
+
1020 const char *mnt = *mntp;
+
1021 const char *origmnt = mnt;
+
1022 struct statfs fs_buf;
+
1023 size_t i;
+
1024
+
1025 res = lstat(mnt, stbuf);
+
1026 if (res == -1) {
+
1027 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1028 progname, mnt, strerror(errno));
+
1029 return -1;
+
1030 }
+
1031
+
1032 /* No permission checking is done for root */
+
1033 if (getuid() == 0)
+
1034 return 0;
+
1035
+
1036 if (S_ISDIR(stbuf->st_mode)) {
+
1037 res = chdir(mnt);
+
1038 if (res == -1) {
+
1039 fprintf(stderr,
+
1040 "%s: failed to chdir to mountpoint: %s\n",
+
1041 progname, strerror(errno));
+
1042 return -1;
+
1043 }
+
1044 mnt = *mntp = ".";
+
1045 res = lstat(mnt, stbuf);
+
1046 if (res == -1) {
+
1047 fprintf(stderr,
+
1048 "%s: failed to access mountpoint %s: %s\n",
+
1049 progname, origmnt, strerror(errno));
+
1050 return -1;
+
1051 }
+
1052
+
1053 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1054 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1055 progname, origmnt);
+
1056 return -1;
+
1057 }
+
1058
+
1059 res = access(mnt, W_OK);
+
1060 if (res == -1) {
+
1061 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1062 progname, origmnt);
+
1063 return -1;
+
1064 }
+
1065 } else if (S_ISREG(stbuf->st_mode)) {
+
1066 static char procfile[256];
+
1067 *mountpoint_fd = open(mnt, O_WRONLY);
+
1068 if (*mountpoint_fd == -1) {
+
1069 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1070 progname, mnt, strerror(errno));
+
1071 return -1;
+
1072 }
+
1073 res = fstat(*mountpoint_fd, stbuf);
+
1074 if (res == -1) {
+
1075 fprintf(stderr,
+
1076 "%s: failed to access mountpoint %s: %s\n",
+
1077 progname, mnt, strerror(errno));
+
1078 return -1;
+
1079 }
+
1080 if (!S_ISREG(stbuf->st_mode)) {
+
1081 fprintf(stderr,
+
1082 "%s: mountpoint %s is no longer a regular file\n",
+
1083 progname, mnt);
+
1084 return -1;
+
1085 }
+
1086
+
1087 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+
1088 *mntp = procfile;
+
1089 } else {
+
1090 fprintf(stderr,
+
1091 "%s: mountpoint %s is not a directory or a regular file\n",
+
1092 progname, mnt);
+
1093 return -1;
+
1094 }
+
1095
+
1096 /* Do not permit mounting over anything in procfs - it has a couple
+
1097 * places to which we have "write access" without being supposed to be
+
1098 * able to just put anything we want there.
+
1099 * Luckily, without allow_other, we can't get other users to actually
+
1100 * use any fake information we try to put there anyway.
+
1101 * Use a whitelist to be safe. */
+
1102 if (statfs(*mntp, &fs_buf)) {
+
1103 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1104 progname, mnt, strerror(errno));
+
1105 return -1;
+
1106 }
+
1107
+
1108 /* Define permitted filesystems for the mount target. This was
+
1109 * originally the same list as used by the ecryptfs mount helper
+
1110 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1111 * but got expanded as we found more filesystems that needed to be
+
1112 * overlaid. */
+
1113 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1114 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1115 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1116 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1117 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1118 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1119 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1120 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1121 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1122 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1123 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1124 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1125 0x01161970 /* GFS2_MAGIC */,
+
1126 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1127 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1128 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1129 0x3153464A /* JFS_SUPER_MAGIC */,
+
1130 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1131 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1132 0x0000564C /* NCP_SUPER_MAGIC */,
+
1133 0x00006969 /* NFS_SUPER_MAGIC */,
+
1134 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1135 0x5346544E /* NTFS_SB_MAGIC */,
+
1136 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1137 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1138 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1139 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1140 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1141 0x73717368 /* SQUASHFS_MAGIC */,
+
1142 0x01021994 /* TMPFS_MAGIC */,
+
1143 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1144 0x736675005346544e /* UFSD */,
+
1145 0x58465342 /* XFS_SB_MAGIC */,
+
1146 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1147 0x858458f6 /* RAMFS_MAGIC */,
+
1148 };
+
1149 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1150 if (f_type_whitelist[i] == fs_buf.f_type)
+
1151 return 0;
+
1152 }
+
1153
+
1154 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1155 progname, (unsigned long)fs_buf.f_type);
+
1156 return -1;
+
1157}
+
1158
+
1159static int try_open(const char *dev, char **devp, int silent)
+
1160{
+
1161 int fd = open(dev, O_RDWR);
+
1162 if (fd != -1) {
+
1163 *devp = strdup(dev);
+
1164 if (*devp == NULL) {
+
1165 fprintf(stderr, "%s: failed to allocate memory\n",
+
1166 progname);
+
1167 close(fd);
+
1168 fd = -1;
+
1169 }
+
1170 } else if (errno == ENODEV ||
+
1171 errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1172 return -2;
+
1173 else if (!silent) {
+
1174 fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
+
1175 strerror(errno));
+
1176 }
+
1177 return fd;
+
1178}
+
1179
+
1180static int try_open_fuse_device(char **devp)
+
1181{
+
1182 int fd;
+
1183
+
1184 drop_privs();
+
1185 fd = try_open(FUSE_DEV, devp, 0);
+
1186 restore_privs();
+
1187 return fd;
+
1188}
+
1189
+
1190static int open_fuse_device(char **devp)
+
1191{
+
1192 int fd = try_open_fuse_device(devp);
+
1193 if (fd >= -1)
+
1194 return fd;
+
1195
+
1196 fprintf(stderr,
+
1197 "%s: fuse device not found, try 'modprobe fuse' first\n",
+
1198 progname);
+
1199
+
1200 return -1;
+
1201}
+
1202
+
1203
+
1204static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1205{
+
1206 int res;
+
1207 int fd;
+
1208 char *dev;
+
1209 struct stat stbuf;
+
1210 char *source = NULL;
+
1211 char *mnt_opts = NULL;
+
1212 const char *real_mnt = mnt;
+
1213 int mountpoint_fd = -1;
+
1214 char *do_mount_opts = NULL;
+
1215 char *x_opts = NULL;
+
1216
+
1217 fd = open_fuse_device(&dev);
+
1218 if (fd == -1)
+
1219 return -1;
+
1220
+
1221 drop_privs();
+
1222 read_conf();
+
1223
+
1224 if (getuid() != 0 && mount_max != -1) {
+
1225 int mount_count = count_fuse_fs();
+
1226 if (mount_count >= mount_max) {
+
1227 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1228 goto fail_close_fd;
+
1229 }
+
1230 }
+
1231
+
1232 // Extract any options starting with "x-"
+
1233 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1234 if (res)
+
1235 goto fail_close_fd;
+
1236
+
1237 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1238 restore_privs();
+
1239 if (res != -1)
+
1240 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1241 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1242
+
1243 if (mountpoint_fd != -1)
+
1244 close(mountpoint_fd);
+
1245
+
1246 if (res == -1)
+
1247 goto fail_close_fd;
+
1248
+
1249 res = chdir("/");
+
1250 if (res == -1) {
+
1251 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1252 goto fail_close_fd;
+
1253 }
+
1254
+
1255 if (geteuid() == 0) {
+
1256 if (x_opts && strlen(x_opts) > 0) {
+
1257 /*
+
1258 * Add back the options starting with "x-" to opts from
+
1259 * do_mount. +2 for ',' and '\0'
+
1260 */
+
1261 size_t mnt_opts_len = strlen(mnt_opts);
+
1262 size_t x_mnt_opts_len = mnt_opts_len+
+
1263 strlen(x_opts) + 2;
+
1264 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1265
+
1266 if (mnt_opts_len) {
+
1267 strcpy(x_mnt_opts, mnt_opts);
+
1268 strncat(x_mnt_opts, ",", 2);
+
1269 }
+
1270
+
1271 strncat(x_mnt_opts, x_opts,
+
1272 x_mnt_opts_len - mnt_opts_len - 2);
+
1273
+
1274 free(mnt_opts);
+
1275 mnt_opts = x_mnt_opts;
+
1276 }
+
1277
+
1278 res = add_mount(source, mnt, *type, mnt_opts);
+
1279 if (res == -1) {
+
1280 /* Can't clean up mount in a non-racy way */
+
1281 goto fail_close_fd;
+
1282 }
+
1283 }
+
1284
+
1285out_free:
+
1286 free(source);
+
1287 free(mnt_opts);
+
1288 free(dev);
+
1289 free(x_opts);
+
1290 free(do_mount_opts);
+
1291
+
1292 return fd;
+
1293
+
1294fail_close_fd:
+
1295 close(fd);
+
1296 fd = -1;
+
1297 goto out_free;
+
1298}
+
1299
+
1300static int send_fd(int sock_fd, int fd)
+
1301{
+
1302 int retval;
+
1303 struct msghdr msg;
+
1304 struct cmsghdr *p_cmsg;
+
1305 struct iovec vec;
+
1306 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1307 int *p_fds;
+
1308 char sendchar = 0;
+
1309
+
1310 msg.msg_control = cmsgbuf;
+
1311 msg.msg_controllen = sizeof(cmsgbuf);
+
1312 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1313 p_cmsg->cmsg_level = SOL_SOCKET;
+
1314 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1315 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1316 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1317 *p_fds = fd;
+
1318 msg.msg_controllen = p_cmsg->cmsg_len;
+
1319 msg.msg_name = NULL;
+
1320 msg.msg_namelen = 0;
+
1321 msg.msg_iov = &vec;
+
1322 msg.msg_iovlen = 1;
+
1323 msg.msg_flags = 0;
+
1324 /* "To pass file descriptors or credentials you need to send/read at
+
1325 * least one byte" (man 7 unix) */
+
1326 vec.iov_base = &sendchar;
+
1327 vec.iov_len = sizeof(sendchar);
+
1328 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1329 if (retval != 1) {
+
1330 perror("sending file descriptor");
+
1331 return -1;
+
1332 }
+
1333 return 0;
+
1334}
+
1335
+
1336/* Helper for should_auto_unmount
+
1337 *
+
1338 * fusermount typically has the s-bit set - initial open of `mnt` was as root
+
1339 * and got EACCESS as 'allow_other' was not specified.
+
1340 * Try opening `mnt` again with uid and guid of the calling process.
+
1341 */
+
1342static int recheck_ENOTCONN_as_owner(const char *mnt)
+
1343{
+
1344 int pid = fork();
+
1345 if(pid == -1) {
+
1346 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1347 _exit(EXIT_FAILURE);
+
1348 } else if(pid == 0) {
+
1349 uid_t uid = getuid();
+
1350 gid_t gid = getgid();
+
1351 if(setresgid(gid, gid, gid) == -1) {
+
1352 perror("fuse: can't set resgid");
+
1353 _exit(EXIT_FAILURE);
+
1354 }
+
1355 if(setresuid(uid, uid, uid) == -1) {
+
1356 perror("fuse: can't set resuid");
+
1357 _exit(EXIT_FAILURE);
+
1358 }
+
1359
+
1360 int fd = open(mnt, O_RDONLY);
+
1361 if(fd == -1 && errno == ENOTCONN)
+
1362 _exit(EXIT_SUCCESS);
+
1363 else
+
1364 _exit(EXIT_FAILURE);
+
1365 } else {
+
1366 int status;
+
1367 int res = waitpid(pid, &status, 0);
+
1368 if (res == -1) {
+
1369 perror("fuse: waiting for child failed");
+
1370 _exit(EXIT_FAILURE);
+
1371 }
+
1372 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1373 }
+
1374}
+
1375
+
1376/* The parent fuse process has died: decide whether to auto_unmount.
+
1377 *
+
1378 * In the normal case (umount or fusermount -u), the filesystem
+
1379 * has already been unmounted. If we simply unmount again we can
+
1380 * cause problems with stacked mounts (e.g. autofs).
+
1381 *
+
1382 * So we unmount here only in abnormal case where fuse process has
+
1383 * died without unmount happening. To detect this, we first look in
+
1384 * the mount table to make sure the mountpoint is still mounted and
+
1385 * has proper type. If so, we then see if opening the mount dir is
+
1386 * returning 'Transport endpoint is not connected'.
+
1387 *
+
1388 * The order of these is important, because if autofs is in use,
+
1389 * opening the dir to check for ENOTCONN will cause a new mount
+
1390 * in the normal case where filesystem has been unmounted cleanly.
+
1391 */
+
1392static int should_auto_unmount(const char *mnt, const char *type)
+
1393{
+
1394 char *copy;
+
1395 const char *last;
+
1396 int result = 0;
+
1397 int fd;
+
1398
+
1399 copy = strdup(mnt);
+
1400 if (copy == NULL) {
+
1401 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1402 return 0;
+
1403 }
+
1404
+
1405 if (chdir_to_parent(copy, &last) == -1)
+
1406 goto out;
+
1407 if (check_is_mount(last, mnt, type) == -1)
+
1408 goto out;
+
1409
+
1410 fd = open(mnt, O_RDONLY);
+
1411
+
1412 if (fd != -1) {
+
1413 close(fd);
+
1414 } else {
+
1415 switch(errno) {
+
1416 case ENOTCONN:
+
1417 result = 1;
+
1418 break;
+
1419 case EACCES:
+
1420 result = recheck_ENOTCONN_as_owner(mnt);
+
1421 break;
+
1422 default:
+
1423 result = 0;
+
1424 break;
+
1425 }
+
1426 }
+
1427out:
+
1428 free(copy);
+
1429 return result;
+
1430}
+
1431
+
1432static void usage(void)
+
1433{
+
1434 printf("%s: [options] mountpoint\n"
+
1435 "Options:\n"
+
1436 " -h print help\n"
+
1437 " -V print version\n"
+
1438 " -o opt[,opt...] mount options\n"
+
1439 " -u unmount\n"
+
1440 " -q quiet\n"
+
1441 " -z lazy unmount\n",
+
1442 progname);
+
1443 exit(1);
+
1444}
+
1445
+
1446static void show_version(void)
+
1447{
+
1448 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1449 exit(0);
+
1450}
+
1451
+
1452/*
+
1453 * Close all inherited fds that are not needed
+
1454 * Ideally these wouldn't come up at all, applications should better
+
1455 * use FD_CLOEXEC / O_CLOEXEC
+
1456 */
+
1457static void close_inherited_fds(int cfd)
+
1458{
+
1459 int max_fd = sysconf(_SC_OPEN_MAX);
+
1460 int rc;
+
1461
+
1462#ifdef CLOSE_RANGE_CLOEXEC
+
1463 /* high range first to be able to log errors through stdout/err*/
+
1464 rc = close_range(cfd + 1, ~0U, 0);
+
1465 if (rc < 0) {
+
1466 fprintf(stderr, "Failed to close high range of FDs: %s",
+
1467 strerror(errno));
+
1468 goto fallback;
+
1469 }
+
1470
+
1471 rc = close_range(0, cfd - 1, 0);
+
1472 if (rc < 0) {
+
1473 fprintf(stderr, "Failed to close low range of FDs: %s",
+
1474 strerror(errno));
+
1475 goto fallback;
+
1476 }
+
1477#endif
+
1478
+
1479fallback:
+
1480 /*
+
1481 * This also needs to close stdout/stderr, as the application
+
1482 * using libfuse might have closed these FDs and might be using
+
1483 * it. Although issue is now that logging errors won't be possible
+
1484 * after that.
+
1485 */
+
1486 for (int fd = 0; fd <= max_fd; fd++) {
+
1487 if (fd != cfd)
+
1488 close(fd);
+
1489 }
+
1490}
+
1491
+
1492int main(int argc, char *argv[])
+
1493{
+
1494 sigset_t sigset;
+
1495 int ch;
+
1496 int fd;
+
1497 int res;
+
1498 char *origmnt;
+
1499 char *mnt;
+
1500 static int unmount = 0;
+
1501 static int lazy = 0;
+
1502 static int quiet = 0;
+
1503 char *commfd = NULL;
+
1504 long cfd;
+
1505 const char *opts = "";
+
1506 const char *type = NULL;
+
1507 int setup_auto_unmount_only = 0;
+
1508
+
1509 static const struct option long_opts[] = {
+
1510 {"unmount", no_argument, NULL, 'u'},
+
1511 {"lazy", no_argument, NULL, 'z'},
+
1512 {"quiet", no_argument, NULL, 'q'},
+
1513 {"help", no_argument, NULL, 'h'},
+
1514 {"version", no_argument, NULL, 'V'},
+
1515 {"options", required_argument, NULL, 'o'},
+
1516 // Note: auto-unmount and comm-fd don't have short versions.
+
1517 // They'ne meant for internal use by mount.c
+
1518 {"auto-unmount", no_argument, NULL, 'U'},
+
1519 {"comm-fd", required_argument, NULL, 'c'},
+
1520 {0, 0, 0, 0}};
+
1521
+
1522 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1523 if (progname == NULL) {
+
1524 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1525 exit(1);
+
1526 }
+
1527
+
1528 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1529 NULL)) != -1) {
+
1530 switch (ch) {
+
1531 case 'h':
+
1532 usage();
+
1533 break;
+
1534
+
1535 case 'V':
+
1536 show_version();
+
1537 break;
+
1538
+
1539 case 'o':
+
1540 opts = optarg;
+
1541 break;
+
1542
+
1543 case 'u':
+
1544 unmount = 1;
+
1545 break;
+
1546 case 'U':
+
1547 unmount = 1;
+
1548 auto_unmount = 1;
+
1549 setup_auto_unmount_only = 1;
+
1550 break;
+
1551 case 'c':
+
1552 commfd = optarg;
+
1553 break;
+
1554 case 'z':
+
1555 lazy = 1;
+
1556 break;
+
1557
+
1558 case 'q':
+
1559 quiet = 1;
+
1560 break;
+
1561
+
1562 default:
+
1563 exit(1);
+
1564 }
+
1565 }
+
1566
+
1567 if (lazy && !unmount) {
+
1568 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1569 exit(1);
+
1570 }
+
1571
+
1572 if (optind >= argc) {
+
1573 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1574 exit(1);
+
1575 } else if (argc > optind + 1) {
+
1576 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1577 progname);
+
1578 exit(1);
+
1579 }
+
1580
+
1581 origmnt = argv[optind];
+
1582
+
1583 drop_privs();
+
1584 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1585 if (mnt != NULL) {
+
1586 res = chdir("/");
+
1587 if (res == -1) {
+
1588 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1589 goto err_out;
+
1590 }
+
1591 }
+
1592 restore_privs();
+
1593 if (mnt == NULL)
+
1594 exit(1);
+
1595
+
1596 umask(033);
+
1597 if (!setup_auto_unmount_only && unmount)
+
1598 goto do_unmount;
+
1599
+
1600 if(commfd == NULL)
+
1601 commfd = getenv(FUSE_COMMFD_ENV);
+
1602 if (commfd == NULL) {
+
1603 fprintf(stderr, "%s: old style mounting not supported\n",
+
1604 progname);
+
1605 goto err_out;
+
1606 }
+
1607
+
1608 res = libfuse_strtol(commfd, &cfd);
+
1609 if (res) {
+
1610 fprintf(stderr,
+
1611 "%s: invalid _FUSE_COMMFD: %s\n",
+
1612 progname, commfd);
+
1613 goto err_out;
+
1614
+
1615 }
+
1616 {
+
1617 struct stat statbuf;
+
1618 fstat(cfd, &statbuf);
+
1619 if(!S_ISSOCK(statbuf.st_mode)) {
+
1620 fprintf(stderr,
+
1621 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1622 progname, cfd);
+
1623 goto err_out;
+
1624 }
+
1625 }
+
1626
+
1627 if (setup_auto_unmount_only)
+
1628 goto wait_for_auto_unmount;
+
1629
+
1630 fd = mount_fuse(mnt, opts, &type);
+
1631 if (fd == -1)
+
1632 goto err_out;
+
1633
+
1634 res = send_fd(cfd, fd);
+
1635 if (res != 0) {
+
1636 umount2(mnt, MNT_DETACH); /* lazy umount */
+
1637 goto err_out;
+
1638 }
+
1639 close(fd);
+
1640
+
1641 if (!auto_unmount) {
+
1642 free(mnt);
+
1643 free((void*) type);
+
1644 return 0;
+
1645 }
+
1646
+
1647wait_for_auto_unmount:
+
1648 /* Become a daemon and wait for the parent to exit or die.
+
1649 ie For the control socket to get closed.
+
1650 Btw, we don't want to use daemon() function here because
+
1651 it forks and messes with the file descriptors. */
+
1652
+
1653 close_inherited_fds(cfd);
+
1654
+
1655 setsid();
+
1656 res = chdir("/");
+
1657 if (res == -1) {
+
1658 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1659 goto err_out;
+
1660 }
+
1661
+
1662 sigfillset(&sigset);
+
1663 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1664
+
1665 lazy = 1;
+
1666 quiet = 1;
+
1667
+
1668 while (1) {
+
1669 unsigned char buf[16];
+
1670 int n = recv(cfd, buf, sizeof(buf), 0);
+
1671 if (!n)
+
1672 break;
+
1673
+
1674 if (n < 0) {
+
1675 if (errno == EINTR)
+
1676 continue;
+
1677 break;
+
1678 }
+
1679 }
+
1680
+
1681 if (!should_auto_unmount(mnt, type)) {
+
1682 goto success_out;
+
1683 }
+
1684
+
1685do_unmount:
+
1686 if (geteuid() == 0)
+
1687 res = unmount_fuse(mnt, quiet, lazy);
+
1688 else {
+
1689 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+
1690 if (res == -1 && !quiet)
+
1691 fprintf(stderr,
+
1692 "%s: failed to unmount %s: %s\n",
+
1693 progname, mnt, strerror(errno));
+
1694 }
+
1695 if (res == -1)
+
1696 goto err_out;
+
1697
+
1698success_out:
+
1699 free((void*) type);
+
1700 free(mnt);
+
1701 return 0;
+
1702
+
1703err_out:
+
1704 free((void*) type);
+
1705 free(mnt);
+
1706 exit(1);
+
1707}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc0_2util_2mount_8fuse_8c_source.html b/doc/html/fuse-3_817_81-rc0_2util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..0cad890 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc0_2util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: fuse-3.17.1-rc0/util/mount.fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9#include "fuse_config.h"
+
10
+
11#include <stdio.h>
+
12#include <stdlib.h>
+
13#include <string.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16#include <stdint.h>
+
17#include <fcntl.h>
+
18#include <pwd.h>
+
19#include <sys/wait.h>
+
20
+
21#ifdef linux
+
22#include <sys/prctl.h>
+
23#include <sys/syscall.h>
+
24#include <linux/capability.h>
+
25#include <linux/securebits.h>
+
26/* for 2.6 kernels */
+
27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
+
28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
+
29#endif
+
30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
+
31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
+
32#endif
+
33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
+
34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
+
35#endif
+
36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
+
37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
+
38#endif
+
39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
+
40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
+
41#endif
+
42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
+
43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
+
44#endif
+
45#endif
+
46/* linux < 3.5 */
+
47#ifndef PR_SET_NO_NEW_PRIVS
+
48#define PR_SET_NO_NEW_PRIVS 38
+
49#endif
+
50
+
51#include "fuse.h"
+
52
+
53static char *progname;
+
54
+
55static char *xstrdup(const char *s)
+
56{
+
57 char *t = strdup(s);
+
58 if (!t) {
+
59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
60 exit(1);
+
61 }
+
62 return t;
+
63}
+
64
+
65static void *xrealloc(void *oldptr, size_t size)
+
66{
+
67 void *ptr = realloc(oldptr, size);
+
68 if (!ptr) {
+
69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
70 exit(1);
+
71 }
+
72 return ptr;
+
73}
+
74
+
75static void add_arg(char **cmdp, const char *opt)
+
76{
+
77 size_t optlen = strlen(opt);
+
78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
+
79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
+
80 fprintf(stderr, "%s: argument too long\n", progname);
+
81 exit(1);
+
82 }
+
83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
+
84 char *s;
+
85 s = cmd + cmdlen;
+
86 if (*cmdp)
+
87 *s++ = ' ';
+
88
+
89 *s++ = '\'';
+
90 for (; *opt; opt++) {
+
91 if (*opt == '\'') {
+
92 *s++ = '\'';
+
93 *s++ = '\\';
+
94 *s++ = '\'';
+
95 *s++ = '\'';
+
96 } else
+
97 *s++ = *opt;
+
98 }
+
99 *s++ = '\'';
+
100 *s = '\0';
+
101 *cmdp = cmd;
+
102}
+
103
+
104static char *add_option(const char *opt, char *options)
+
105{
+
106 int oldlen = options ? strlen(options) : 0;
+
107
+
108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
+
109 if (!oldlen)
+
110 strcpy(options, opt);
+
111 else {
+
112 strcat(options, ",");
+
113 strcat(options, opt);
+
114 }
+
115 return options;
+
116}
+
117
+
118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
+
119 const char *options)
+
120{
+
121 int fuse_fd = -1;
+
122 int flags = -1;
+
123 int subtype_len = strlen(subtype) + 9;
+
124 char* options_copy = xrealloc(NULL, subtype_len);
+
125
+
126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
+
127 options_copy = add_option(options, options_copy);
+
128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
+
129 if (fuse_fd == -1) {
+
130 exit(1);
+
131 }
+
132
+
133 flags = fcntl(fuse_fd, F_GETFD);
+
134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
+
135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
+
136 progname, strerror(errno));
+
137 exit(1);
+
138 }
+
139
+
140 return fuse_fd;
+
141}
+
142
+
143#ifdef linux
+
144static uint64_t get_capabilities(void)
+
145{
+
146 /*
+
147 * This invokes the capset syscall directly to avoid the libcap
+
148 * dependency, which isn't really justified just for this.
+
149 */
+
150 struct __user_cap_header_struct header = {
+
151 .version = _LINUX_CAPABILITY_VERSION_3,
+
152 .pid = 0,
+
153 };
+
154 struct __user_cap_data_struct data[2];
+
155 memset(data, 0, sizeof(data));
+
156 if (syscall(SYS_capget, &header, data) == -1) {
+
157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
+
158 progname, strerror(errno));
+
159 exit(1);
+
160 }
+
161
+
162 return data[0].effective | ((uint64_t) data[1].effective << 32);
+
163}
+
164
+
165static void set_capabilities(uint64_t caps)
+
166{
+
167 /*
+
168 * This invokes the capset syscall directly to avoid the libcap
+
169 * dependency, which isn't really justified just for this.
+
170 */
+
171 struct __user_cap_header_struct header = {
+
172 .version = _LINUX_CAPABILITY_VERSION_3,
+
173 .pid = 0,
+
174 };
+
175 struct __user_cap_data_struct data[2];
+
176 memset(data, 0, sizeof(data));
+
177 data[0].effective = data[0].permitted = caps;
+
178 data[1].effective = data[1].permitted = caps >> 32;
+
179 if (syscall(SYS_capset, &header, data) == -1) {
+
180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
+
181 progname, strerror(errno));
+
182 exit(1);
+
183 }
+
184}
+
185
+
186static void drop_and_lock_capabilities(void)
+
187{
+
188 /* Set and lock securebits. */
+
189 if (prctl(PR_SET_SECUREBITS,
+
190 SECBIT_KEEP_CAPS_LOCKED |
+
191 SECBIT_NO_SETUID_FIXUP |
+
192 SECBIT_NO_SETUID_FIXUP_LOCKED |
+
193 SECBIT_NOROOT |
+
194 SECBIT_NOROOT_LOCKED) == -1) {
+
195 fprintf(stderr, "%s: Failed to set securebits %s\n",
+
196 progname, strerror(errno));
+
197 exit(1);
+
198 }
+
199
+
200 /* Clear the capability bounding set. */
+
201 int cap;
+
202 for (cap = 0; ; cap++) {
+
203 int cap_status = prctl(PR_CAPBSET_READ, cap);
+
204 if (cap_status == 0) {
+
205 continue;
+
206 }
+
207 if (cap_status == -1 && errno == EINVAL) {
+
208 break;
+
209 }
+
210
+
211 if (cap_status != 1) {
+
212 fprintf(stderr,
+
213 "%s: Failed to get capability %u: %s\n",
+
214 progname, cap, strerror(errno));
+
215 exit(1);
+
216 }
+
217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
+
218 fprintf(stderr,
+
219 "%s: Failed to drop capability %u: %s\n",
+
220 progname, cap, strerror(errno));
+
221 }
+
222 }
+
223
+
224 /* Drop capabilities. */
+
225 set_capabilities(0);
+
226
+
227 /* Prevent re-acquisition of privileges. */
+
228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+
229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
+
230 progname, strerror(errno));
+
231 exit(1);
+
232 }
+
233}
+
234#endif
+
235
+
236int main(int argc, char *argv[])
+
237{
+
238 char *type = NULL;
+
239 char *source;
+
240 char *dup_source = NULL;
+
241 const char *mountpoint;
+
242 char *basename;
+
243 char *options = NULL;
+
244 char *command = NULL;
+
245 char *setuid_name = NULL;
+
246 int i;
+
247 int dev = 1;
+
248 int suid = 1;
+
249 int pass_fuse_fd = 0;
+
250 int fuse_fd = 0;
+
251 int drop_privileges = 0;
+
252 char *dev_fd_mountpoint = NULL;
+
253
+
254 progname = argv[0];
+
255 basename = strrchr(argv[0], '/');
+
256 if (basename)
+
257 basename++;
+
258 else
+
259 basename = argv[0];
+
260
+
261 if (strncmp(basename, "mount.fuse.", 11) == 0)
+
262 type = basename + 11;
+
263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
+
264 type = basename + 14;
+
265
+
266 if (type && !type[0])
+
267 type = NULL;
+
268
+
269 if (argc < 3) {
+
270 fprintf(stderr,
+
271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
+
272 progname, type ? "source" : "type#[source]");
+
273 exit(1);
+
274 }
+
275
+
276 source = argv[1];
+
277 if (!source[0])
+
278 source = NULL;
+
279
+
280 mountpoint = argv[2];
+
281
+
282 for (i = 3; i < argc; i++) {
+
283 if (strcmp(argv[i], "-v") == 0) {
+
284 continue;
+
285 } else if (strcmp(argv[i], "-t") == 0) {
+
286 i++;
+
287
+
288 if (i == argc) {
+
289 fprintf(stderr,
+
290 "%s: missing argument to option '-t'\n",
+
291 progname);
+
292 exit(1);
+
293 }
+
294 type = argv[i];
+
295 if (strncmp(type, "fuse.", 5) == 0)
+
296 type += 5;
+
297 else if (strncmp(type, "fuseblk.", 8) == 0)
+
298 type += 8;
+
299
+
300 if (!type[0]) {
+
301 fprintf(stderr,
+
302 "%s: empty type given as argument to option '-t'\n",
+
303 progname);
+
304 exit(1);
+
305 }
+
306 } else if (strcmp(argv[i], "-o") == 0) {
+
307 char *opts;
+
308 char *opt;
+
309 i++;
+
310 if (i == argc)
+
311 break;
+
312
+
313 opts = xstrdup(argv[i]);
+
314 opt = strtok(opts, ",");
+
315 while (opt) {
+
316 int j;
+
317 int ignore = 0;
+
318 const char *ignore_opts[] = { "",
+
319 "user",
+
320 "nofail",
+
321 "nouser",
+
322 "users",
+
323 "auto",
+
324 "noauto",
+
325 "_netdev",
+
326 NULL};
+
327 if (strncmp(opt, "setuid=", 7) == 0) {
+
328 setuid_name = xstrdup(opt + 7);
+
329 ignore = 1;
+
330 } else if (strcmp(opt,
+
331 "drop_privileges") == 0) {
+
332 pass_fuse_fd = 1;
+
333 drop_privileges = 1;
+
334 ignore = 1;
+
335 }
+
336 for (j = 0; ignore_opts[j]; j++)
+
337 if (strcmp(opt, ignore_opts[j]) == 0)
+
338 ignore = 1;
+
339
+
340 if (!ignore) {
+
341 if (strcmp(opt, "nodev") == 0)
+
342 dev = 0;
+
343 else if (strcmp(opt, "nosuid") == 0)
+
344 suid = 0;
+
345
+
346 options = add_option(opt, options);
+
347 }
+
348 opt = strtok(NULL, ",");
+
349 }
+
350 free(opts);
+
351 }
+
352 }
+
353
+
354 if (drop_privileges) {
+
355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
+
356 CAP_TO_MASK(CAP_SYS_ADMIN);
+
357 if ((get_capabilities() & required_caps) != required_caps) {
+
358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
+
359 progname, progname);
+
360 exit(1);
+
361 }
+
362 }
+
363
+
364 if (dev)
+
365 options = add_option("dev", options);
+
366 if (suid)
+
367 options = add_option("suid", options);
+
368
+
369 if (!type) {
+
370 if (source) {
+
371 dup_source = xstrdup(source);
+
372 type = dup_source;
+
373 source = strchr(type, '#');
+
374 if (source)
+
375 *source++ = '\0';
+
376 if (!type[0]) {
+
377 fprintf(stderr, "%s: empty filesystem type\n",
+
378 progname);
+
379 exit(1);
+
380 }
+
381 } else {
+
382 fprintf(stderr, "%s: empty source\n", progname);
+
383 exit(1);
+
384 }
+
385 }
+
386
+
387 if (setuid_name && setuid_name[0]) {
+
388#ifdef linux
+
389 if (drop_privileges) {
+
390 /*
+
391 * Make securebits more permissive before calling
+
392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
+
393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
+
394 * have the side effect of dropping all capabilities,
+
395 * and we need to retain CAP_SETPCAP in order to drop
+
396 * all privileges before exec().
+
397 */
+
398 if (prctl(PR_SET_SECUREBITS,
+
399 SECBIT_KEEP_CAPS |
+
400 SECBIT_NO_SETUID_FIXUP) == -1) {
+
401 fprintf(stderr,
+
402 "%s: Failed to set securebits %s\n",
+
403 progname, strerror(errno));
+
404 exit(1);
+
405 }
+
406 }
+
407#endif
+
408
+
409 struct passwd *pwd = getpwnam(setuid_name);
+
410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
+
411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
+
412 progname, setuid_name, strerror(errno));
+
413 exit(1);
+
414 }
+
415 } else if (!getenv("HOME")) {
+
416 /* Hack to make filesystems work in the boot environment */
+
417 setenv("HOME", "/root", 0);
+
418 }
+
419
+
420 if (pass_fuse_fd) {
+
421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
+
422 dev_fd_mountpoint = xrealloc(NULL, 20);
+
423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
+
424 mountpoint = dev_fd_mountpoint;
+
425 }
+
426
+
427#ifdef linux
+
428 if (drop_privileges) {
+
429 drop_and_lock_capabilities();
+
430 }
+
431#endif
+
432 add_arg(&command, type);
+
433 if (source)
+
434 add_arg(&command, source);
+
435 add_arg(&command, mountpoint);
+
436 if (options) {
+
437 add_arg(&command, "-o");
+
438 add_arg(&command, options);
+
439 }
+
440
+
441 free(options);
+
442 free(dev_fd_mountpoint);
+
443 free(dup_source);
+
444 free(setuid_name);
+
445
+
446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+
447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+
448 strerror(errno));
+
449
+
450 if (pass_fuse_fd)
+
451 close(fuse_fd);
+
452 free(command);
+
453 return 1;
+
454}
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:479
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2cuse_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2cuse_8c.html new file mode 100644 index 0000000..7d6cf86 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2cuse_8c.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2cuse_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2cuse_8c_source.html new file mode 100644 index 0000000..d6f364f --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2cuse_8c_source.html @@ -0,0 +1,395 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c.html new file mode 100644 index 0000000..ac6e6d5 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c_source.html new file mode 100644 index 0000000..64e5220 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2hello_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2hello_8c.html new file mode 100644 index 0000000..a853129 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2hello_8c.html @@ -0,0 +1,257 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2hello_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2hello_8c_source.html new file mode 100644 index 0000000..3461676 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2hello_8c_source.html @@ -0,0 +1,246 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60 return NULL;
+
61}
+
62
+
63static int hello_getattr(const char *path, struct stat *stbuf,
+
64 struct fuse_file_info *fi)
+
65{
+
66 (void) fi;
+
67 int res = 0;
+
68
+
69 memset(stbuf, 0, sizeof(struct stat));
+
70 if (strcmp(path, "/") == 0) {
+
71 stbuf->st_mode = S_IFDIR | 0755;
+
72 stbuf->st_nlink = 2;
+
73 } else if (strcmp(path+1, options.filename) == 0) {
+
74 stbuf->st_mode = S_IFREG | 0444;
+
75 stbuf->st_nlink = 1;
+
76 stbuf->st_size = strlen(options.contents);
+
77 } else
+
78 res = -ENOENT;
+
79
+
80 return res;
+
81}
+
82
+
83static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
84 off_t offset, struct fuse_file_info *fi,
+
85 enum fuse_readdir_flags flags)
+
86{
+
87 (void) offset;
+
88 (void) fi;
+
89 (void) flags;
+
90
+
91 if (strcmp(path, "/") != 0)
+
92 return -ENOENT;
+
93
+
94 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
95 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
96 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
97
+
98 return 0;
+
99}
+
100
+
101static int hello_open(const char *path, struct fuse_file_info *fi)
+
102{
+
103 if (strcmp(path+1, options.filename) != 0)
+
104 return -ENOENT;
+
105
+
106 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
107 return -EACCES;
+
108
+
109 return 0;
+
110}
+
111
+
112static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
113 struct fuse_file_info *fi)
+
114{
+
115 size_t len;
+
116 (void) fi;
+
117 if(strcmp(path+1, options.filename) != 0)
+
118 return -ENOENT;
+
119
+
120 len = strlen(options.contents);
+
121 if (offset < len) {
+
122 if (offset + size > len)
+
123 size = len - offset;
+
124 memcpy(buf, options.contents + offset, size);
+
125 } else
+
126 size = 0;
+
127
+
128 return size;
+
129}
+
130
+
131static const struct fuse_operations hello_oper = {
+
132 .init = hello_init,
+
133 .getattr = hello_getattr,
+
134 .readdir = hello_readdir,
+
135 .open = hello_open,
+
136 .read = hello_read,
+
137};
+
138
+
139static void show_help(const char *progname)
+
140{
+
141 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
142 printf("File-system specific options:\n"
+
143 " --name=<s> Name of the \"hello\" file\n"
+
144 " (default: \"hello\")\n"
+
145 " --contents=<s> Contents \"hello\" file\n"
+
146 " (default \"Hello, World!\\n\")\n"
+
147 "\n");
+
148}
+
149
+
150int main(int argc, char *argv[])
+
151{
+
152 int ret;
+
153 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
154
+
155 /* Set defaults -- we have to use strdup so that
+
156 fuse_opt_parse can free the defaults if other
+
157 values are specified */
+
158 options.filename = strdup("hello");
+
159 options.contents = strdup("Hello World!\n");
+
160
+
161 /* Parse options */
+
162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
163 return 1;
+
164
+
165 /* When --help is specified, first print our own file-system
+
166 specific help text, then signal fuse_main to show
+
167 additional help (by adding `--help` to the options again)
+
168 without usage: line (by setting argv[0] to the empty
+
169 string) */
+
170 if (options.show_help) {
+
171 show_help(argv[0]);
+
172 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
173 args.argv[0][0] = '\0';
+
174 }
+
175
+
176 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
177 fuse_opt_free_args(&args);
+
178 return ret;
+
179}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c.html new file mode 100644 index 0000000..be513a1 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c.html @@ -0,0 +1,382 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c_source.html new file mode 100644 index 0000000..32d9271 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
21#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
22
+
23#include <fuse_lowlevel.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <unistd.h>
+
30#include <assert.h>
+
31
+
32static const char *hello_str = "Hello World!\n";
+
33static const char *hello_name = "hello";
+
34
+
35static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
36{
+
37 stbuf->st_ino = ino;
+
38 switch (ino) {
+
39 case 1:
+
40 stbuf->st_mode = S_IFDIR | 0755;
+
41 stbuf->st_nlink = 2;
+
42 break;
+
43
+
44 case 2:
+
45 stbuf->st_mode = S_IFREG | 0444;
+
46 stbuf->st_nlink = 1;
+
47 stbuf->st_size = strlen(hello_str);
+
48 break;
+
49
+
50 default:
+
51 return -1;
+
52 }
+
53 return 0;
+
54}
+
55
+
56static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
57{
+
58 (void)userdata;
+
59
+
60 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
61 conn->no_interrupt = 1;
+
62}
+
63
+
64static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
65 struct fuse_file_info *fi)
+
66{
+
67 struct stat stbuf;
+
68
+
69 (void) fi;
+
70
+
71 memset(&stbuf, 0, sizeof(stbuf));
+
72 if (hello_stat(ino, &stbuf) == -1)
+
73 fuse_reply_err(req, ENOENT);
+
74 else
+
75 fuse_reply_attr(req, &stbuf, 1.0);
+
76}
+
77
+
78static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
79{
+
80 struct fuse_entry_param e;
+
81
+
82 if (parent != 1 || strcmp(name, hello_name) != 0)
+
83 fuse_reply_err(req, ENOENT);
+
84 else {
+
85 memset(&e, 0, sizeof(e));
+
86 e.ino = 2;
+
87 e.attr_timeout = 1.0;
+
88 e.entry_timeout = 1.0;
+
89 hello_stat(e.ino, &e.attr);
+
90
+
91 fuse_reply_entry(req, &e);
+
92 }
+
93}
+
94
+
95struct dirbuf {
+
96 char *p;
+
97 size_t size;
+
98};
+
99
+
100static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
101 fuse_ino_t ino)
+
102{
+
103 struct stat stbuf;
+
104 size_t oldsize = b->size;
+
105 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
106 b->p = (char *) realloc(b->p, b->size);
+
107 memset(&stbuf, 0, sizeof(stbuf));
+
108 stbuf.st_ino = ino;
+
109 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
110 b->size);
+
111}
+
112
+
113#define min(x, y) ((x) < (y) ? (x) : (y))
+
114
+
115static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
116 off_t off, size_t maxsize)
+
117{
+
118 if (off < bufsize)
+
119 return fuse_reply_buf(req, buf + off,
+
120 min(bufsize - off, maxsize));
+
121 else
+
122 return fuse_reply_buf(req, NULL, 0);
+
123}
+
124
+
125static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
126 off_t off, struct fuse_file_info *fi)
+
127{
+
128 (void) fi;
+
129
+
130 if (ino != 1)
+
131 fuse_reply_err(req, ENOTDIR);
+
132 else {
+
133 struct dirbuf b;
+
134
+
135 memset(&b, 0, sizeof(b));
+
136 dirbuf_add(req, &b, ".", 1);
+
137 dirbuf_add(req, &b, "..", 1);
+
138 dirbuf_add(req, &b, hello_name, 2);
+
139 reply_buf_limited(req, b.p, b.size, off, size);
+
140 free(b.p);
+
141 }
+
142}
+
143
+
144static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
145 struct fuse_file_info *fi)
+
146{
+
147 if (ino != 2)
+
148 fuse_reply_err(req, EISDIR);
+
149 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
150 fuse_reply_err(req, EACCES);
+
151 else
+
152 fuse_reply_open(req, fi);
+
153}
+
154
+
155static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
156 off_t off, struct fuse_file_info *fi)
+
157{
+
158 (void) fi;
+
159
+
160 assert(ino == 2);
+
161 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
162}
+
163
+
164static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
165 size_t size)
+
166{
+
167 (void)size;
+
168 assert(ino == 1 || ino == 2);
+
169 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
170 {
+
171 const char *buf = "hello_ll_getxattr_value";
+
172 fuse_reply_buf(req, buf, strlen(buf));
+
173 }
+
174 else
+
175 {
+
176 fuse_reply_err(req, ENOTSUP);
+
177 }
+
178}
+
179
+
180static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
181 const char *value, size_t size, int flags)
+
182{
+
183 (void)flags;
+
184 (void)size;
+
185 assert(ino == 1 || ino == 2);
+
186 const char* exp_val = "hello_ll_setxattr_value";
+
187 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
188 strlen(exp_val) == size &&
+
189 strncmp(value, exp_val, size) == 0)
+
190 {
+
191 fuse_reply_err(req, 0);
+
192 }
+
193 else
+
194 {
+
195 fuse_reply_err(req, ENOTSUP);
+
196 }
+
197}
+
198
+
199static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
200{
+
201 assert(ino == 1 || ino == 2);
+
202 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
203 {
+
204 fuse_reply_err(req, 0);
+
205 }
+
206 else
+
207 {
+
208 fuse_reply_err(req, ENOTSUP);
+
209 }
+
210}
+
211
+
212static const struct fuse_lowlevel_ops hello_ll_oper = {
+
213 .init = hello_ll_init,
+
214 .lookup = hello_ll_lookup,
+
215 .getattr = hello_ll_getattr,
+
216 .readdir = hello_ll_readdir,
+
217 .open = hello_ll_open,
+
218 .read = hello_ll_read,
+
219 .setxattr = hello_ll_setxattr,
+
220 .getxattr = hello_ll_getxattr,
+
221 .removexattr = hello_ll_removexattr,
+
222};
+
223
+
224int main(int argc, char *argv[])
+
225{
+
226 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
227 struct fuse_session *se;
+
228 struct fuse_cmdline_opts opts;
+
229 struct fuse_loop_config *config;
+
230 int ret = -1;
+
231
+
232 if (fuse_parse_cmdline(&args, &opts) != 0)
+
233 return 1;
+
234 if (opts.show_help) {
+
235 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
238 ret = 0;
+
239 goto err_out1;
+
240 } else if (opts.show_version) {
+
241 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
243 ret = 0;
+
244 goto err_out1;
+
245 }
+
246
+
247 if(opts.mountpoint == NULL) {
+
248 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
249 printf(" %s --help\n", argv[0]);
+
250 ret = 1;
+
251 goto err_out1;
+
252 }
+
253
+
254 se = fuse_session_new(&args, &hello_ll_oper,
+
255 sizeof(hello_ll_oper), NULL);
+
256 if (se == NULL)
+
257 goto err_out1;
+
258
+
259 if (fuse_set_signal_handlers(se) != 0)
+
260 goto err_out2;
+
261
+
262 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
263 goto err_out3;
+
264
+
265 fuse_daemonize(opts.foreground);
+
266
+
267 /* Block until ctrl+c or fusermount -u */
+
268 if (opts.singlethread)
+
269 ret = fuse_session_loop(se);
+
270 else {
+
271 config = fuse_loop_cfg_create();
+
272 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
273 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
274 ret = fuse_session_loop_mt(se, config);
+
275 fuse_loop_cfg_destroy(config);
+
276 config = NULL;
+
277 }
+
278
+ +
280err_out3:
+ +
282err_out2:
+ +
284err_out1:
+
285 free(opts.mountpoint);
+
286 fuse_opt_free_args(&args);
+
287
+
288 return ret ? 1 : 0;
+
289}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c.html new file mode 100644 index 0000000..e812332 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c.html @@ -0,0 +1,385 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c_source.html new file mode 100644 index 0000000..7550d9b --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2hello__ll__uds_8c_source.html @@ -0,0 +1,442 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %zu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c.html new file mode 100644 index 0000000..7cf1b19 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c.html @@ -0,0 +1,390 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c_source.html new file mode 100644 index 0000000..81afa63 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2invalidate__path_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c.html new file mode 100644 index 0000000..de4fe34 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c.html @@ -0,0 +1,294 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c_source.html new file mode 100644 index 0000000..56cc45f --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h.html b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h.html new file mode 100644 index 0000000..4243128 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h_source.html b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h_source.html new file mode 100644 index 0000000..34c35e2 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c.html new file mode 100644 index 0000000..cfca4b4 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c.html @@ -0,0 +1,138 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program tests the ioctl.c example file systsem.
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c_source.html new file mode 100644 index 0000000..4c17ae9 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2ioctl__client_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program tests the ioctl.c example file systsem.
+
7
+
8 This program can be distributed under the terms of the GNU GPLv2.
+
9 See the file COPYING.
+
10*/
+
11
+
24#include <sys/types.h>
+
25#include <fcntl.h>
+
26#include <sys/stat.h>
+
27#include <sys/ioctl.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <ctype.h>
+
31#include <errno.h>
+
32#include <unistd.h>
+
33#include "ioctl.h"
+
34
+
35const char *usage =
+
36"Usage: fioclient FIOC_FILE [size]\n"
+
37"\n"
+
38"Get size if <size> is omitted, set size otherwise\n"
+
39"\n";
+
40
+
41int main(int argc, char **argv)
+
42{
+
43 size_t size;
+
44 int fd;
+
45 int ret = 0;
+
46
+
47 if (argc < 2) {
+
48 fprintf(stderr, "%s", usage);
+
49 return 1;
+
50 }
+
51
+
52 fd = open(argv[1], O_RDWR);
+
53 if (fd < 0) {
+
54 perror("open");
+
55 return 1;
+
56 }
+
57
+
58 if (argc == 2) {
+
59 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
60 perror("ioctl");
+
61 ret = 1;
+
62 goto out;
+
63 }
+
64 printf("%zu\n", size);
+
65 } else {
+
66 size = strtoul(argv[2], NULL, 0);
+
67 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
68 perror("ioctl");
+
69 ret = 1;
+
70 goto out;
+
71 }
+
72 }
+
73out:
+
74 close(fd);
+
75 return ret;
+
76}
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c.html new file mode 100644 index 0000000..6514ff8 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c.html @@ -0,0 +1,474 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/notify_inval_entry.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_entry.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

+

It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

+

To see the effect, first start the file system with the --no-notify

$ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
+

Observe that ls always prints the correct directory contents (since readdir output is not cached)::

$ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+Time_is_15h_48m_33s  current_time
+Time_is_15h_48m_34s  current_time
+Time_is_15h_48m_35s  current_time
+

However, if you try to access a file by name the kernel will report that it still exists:

$ file=$(ls mnt/); echo $file
+Time_is_15h_50m_09s
+$ sleep 5; stat mnt/$file
+  File: ‘mnt/Time_is_15h_50m_09s’
+  Size: 32                Blocks: 0          IO Block: 4096   regular file
+Device: 2ah/42d     Inode: 3           Links: 1
+Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+

Only once the kernel cache timeout has been reached will the stat call fail:

$ sleep 30; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+

In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

$ notify_inval_entry --update-interval=1 --timeout=30 mnt/
+$ file=$(ls mnt/); stat mnt/$file
+  File: ‘mnt/Time_is_20h_42m_11s’
+  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+Device: 2ah/42d     Inode: 2           Links: 1
+Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+$ sleep 1; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+

To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

+

+Compilation

+
gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <signal.h>
+
#include <stddef.h>
+
#include <sys/stat.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
#define MAX_STR_LEN 128
+
static char file_name[MAX_STR_LEN];
+
static fuse_ino_t file_ino = 2;
+
static int lookup_cnt = 0;
+
static pthread_t main_thread;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
float timeout;
+
int update_interval;
+
int only_expire;
+
};
+
static struct options options = {
+
.timeout = 5,
+
.no_notify = 0,
+
.update_interval = 1,
+
.only_expire = 0,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+
OPTION("--timeout=%f", timeout),
+
OPTION("--only-expire", only_expire),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == file_ino) {
+
stbuf->st_mode = S_IFREG | 0000;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = 0;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, file_name) == 0) {
+
e.ino = file_ino;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = options.timeout;
+
e.entry_timeout = options.timeout;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == file_ino)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, options.timeout);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, file_name, file_ino);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
time_t t;
+
struct tm *now;
+
ssize_t ret;
+
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
ret = strftime(file_name, MAX_STR_LEN,
+
"Time_is_%Hh_%Mm_%Ss", now);
+
assert(ret != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
char *old_name;
+
+
+
while(!fuse_session_exited(se)) {
+
old_name = strdup(file_name);
+
update_fs();
+
+
if (!options.no_notify && lookup_cnt) {
+
if(options.only_expire) { // expire entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
+
// no kernel support
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
printf("Exiting...\n");
+
+ +
// Make sure to exit now, rather than on next request from userspace
+
pthread_kill(main_thread, SIGPIPE);
+
+
break;
+
}
+
// 1) ret == 0: successful expire of an existing entry
+
// 2) ret == -ENOENT: kernel has already expired the entry /
+
// entry does not exist anymore in the kernel
+
assert(ret == 0 || ret == -ENOENT);
+
} else { // invalidate entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
}
+
}
+
free(old_name);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --timeout=<secs> Timeout for kernel caches\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
" --only-expire Expire entries instead of invalidating them\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), &se);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
// Needed to ensure that the main thread continues/restarts processing as soon
+
// as the fuse session ends (immediately after calling fuse_session_exit() )
+
// and not only on the next request from userspace
+
main_thread = pthread_self();
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread) {
+
ret = fuse_session_loop(se);
+
} else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_entry.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c_source.html new file mode 100644 index 0000000..8b6f8c6 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__entry_8c_source.html @@ -0,0 +1,424 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/notify_inval_entry.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_entry.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
79#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
80
+
81#include <fuse_lowlevel.h>
+
82#include <stdio.h>
+
83#include <stdlib.h>
+
84#include <string.h>
+
85#include <errno.h>
+
86#include <fcntl.h>
+
87#include <assert.h>
+
88#include <signal.h>
+
89#include <stddef.h>
+
90#include <sys/stat.h>
+
91#include <unistd.h>
+
92#include <pthread.h>
+
93
+
94#define MAX_STR_LEN 128
+
95static char file_name[MAX_STR_LEN];
+
96static fuse_ino_t file_ino = 2;
+
97static int lookup_cnt = 0;
+
98static pthread_t main_thread;
+
99
+
100/* Command line parsing */
+
101struct options {
+
102 int no_notify;
+
103 float timeout;
+
104 int update_interval;
+
105 int only_expire;
+
106};
+
107static struct options options = {
+
108 .timeout = 5,
+
109 .no_notify = 0,
+
110 .update_interval = 1,
+
111 .only_expire = 0,
+
112};
+
113
+
114#define OPTION(t, p) \
+
115 { t, offsetof(struct options, p), 1 }
+
116static const struct fuse_opt option_spec[] = {
+
117 OPTION("--no-notify", no_notify),
+
118 OPTION("--update-interval=%d", update_interval),
+
119 OPTION("--timeout=%f", timeout),
+
120 OPTION("--only-expire", only_expire),
+ +
122};
+
123
+
124static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
125 stbuf->st_ino = ino;
+
126 if (ino == FUSE_ROOT_ID) {
+
127 stbuf->st_mode = S_IFDIR | 0755;
+
128 stbuf->st_nlink = 1;
+
129 }
+
130
+
131 else if (ino == file_ino) {
+
132 stbuf->st_mode = S_IFREG | 0000;
+
133 stbuf->st_nlink = 1;
+
134 stbuf->st_size = 0;
+
135 }
+
136
+
137 else
+
138 return -1;
+
139
+
140 return 0;
+
141}
+
142
+
143static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
144 (void)userdata;
+
145
+
146 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
147 conn->no_interrupt = 1;
+
148}
+
149
+
150static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
151 const char *name) {
+
152 struct fuse_entry_param e;
+
153 memset(&e, 0, sizeof(e));
+
154
+
155 if (parent != FUSE_ROOT_ID)
+
156 goto err_out;
+
157 else if (strcmp(name, file_name) == 0) {
+
158 e.ino = file_ino;
+
159 lookup_cnt++;
+
160 } else
+
161 goto err_out;
+
162
+
163 e.attr_timeout = options.timeout;
+
164 e.entry_timeout = options.timeout;
+
165 if (tfs_stat(e.ino, &e.attr) != 0)
+
166 goto err_out;
+
167 fuse_reply_entry(req, &e);
+
168 return;
+
169
+
170err_out:
+
171 fuse_reply_err(req, ENOENT);
+
172}
+
173
+
174static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
175 uint64_t nlookup) {
+
176 (void) req;
+
177 if(ino == file_ino)
+
178 lookup_cnt -= nlookup;
+
179 else
+
180 assert(ino == FUSE_ROOT_ID);
+
181 fuse_reply_none(req);
+
182}
+
183
+
184static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
185 struct fuse_file_info *fi) {
+
186 struct stat stbuf;
+
187
+
188 (void) fi;
+
189
+
190 memset(&stbuf, 0, sizeof(stbuf));
+
191 if (tfs_stat(ino, &stbuf) != 0)
+
192 fuse_reply_err(req, ENOENT);
+
193 else
+
194 fuse_reply_attr(req, &stbuf, options.timeout);
+
195}
+
196
+
197struct dirbuf {
+
198 char *p;
+
199 size_t size;
+
200};
+
201
+
202static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
203 fuse_ino_t ino) {
+
204 struct stat stbuf;
+
205 size_t oldsize = b->size;
+
206 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
207 b->p = (char *) realloc(b->p, b->size);
+
208 memset(&stbuf, 0, sizeof(stbuf));
+
209 stbuf.st_ino = ino;
+
210 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
211 b->size);
+
212}
+
213
+
214#define min(x, y) ((x) < (y) ? (x) : (y))
+
215
+
216static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
217 off_t off, size_t maxsize) {
+
218 if (off < bufsize)
+
219 return fuse_reply_buf(req, buf + off,
+
220 min(bufsize - off, maxsize));
+
221 else
+
222 return fuse_reply_buf(req, NULL, 0);
+
223}
+
224
+
225static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
226 off_t off, struct fuse_file_info *fi) {
+
227 (void) fi;
+
228
+
229 if (ino != FUSE_ROOT_ID)
+
230 fuse_reply_err(req, ENOTDIR);
+
231 else {
+
232 struct dirbuf b;
+
233
+
234 memset(&b, 0, sizeof(b));
+
235 dirbuf_add(req, &b, file_name, file_ino);
+
236 reply_buf_limited(req, b.p, b.size, off, size);
+
237 free(b.p);
+
238 }
+
239}
+
240
+
241static const struct fuse_lowlevel_ops tfs_oper = {
+
242 .init = tfs_init,
+
243 .lookup = tfs_lookup,
+
244 .getattr = tfs_getattr,
+
245 .readdir = tfs_readdir,
+
246 .forget = tfs_forget,
+
247};
+
248
+
249static void update_fs(void) {
+
250 time_t t;
+
251 struct tm *now;
+
252 ssize_t ret;
+
253
+
254 t = time(NULL);
+
255 now = localtime(&t);
+
256 assert(now != NULL);
+
257
+
258 ret = strftime(file_name, MAX_STR_LEN,
+
259 "Time_is_%Hh_%Mm_%Ss", now);
+
260 assert(ret != 0);
+
261}
+
262
+
263static void* update_fs_loop(void *data) {
+
264 struct fuse_session *se = (struct fuse_session*) data;
+
265 char *old_name;
+
266
+
267
+
268 while(!fuse_session_exited(se)) {
+
269 old_name = strdup(file_name);
+
270 update_fs();
+
271
+
272 if (!options.no_notify && lookup_cnt) {
+
273 if(options.only_expire) { // expire entry
+ +
275 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
276
+
277 // no kernel support
+
278 if (ret == -ENOSYS) {
+
279 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
280 printf("Exiting...\n");
+
281
+ +
283 // Make sure to exit now, rather than on next request from userspace
+
284 pthread_kill(main_thread, SIGPIPE);
+
285
+
286 break;
+
287 }
+
288 // 1) ret == 0: successful expire of an existing entry
+
289 // 2) ret == -ENOENT: kernel has already expired the entry /
+
290 // entry does not exist anymore in the kernel
+
291 assert(ret == 0 || ret == -ENOENT);
+
292 } else { // invalidate entry
+ +
294 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
295 }
+
296 }
+
297 free(old_name);
+
298 sleep(options.update_interval);
+
299 }
+
300 return NULL;
+
301}
+
302
+
303static void show_help(const char *progname)
+
304{
+
305 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
306 printf("File-system specific options:\n"
+
307 " --timeout=<secs> Timeout for kernel caches\n"
+
308 " --update-interval=<secs> Update-rate of file system contents\n"
+
309 " --no-notify Disable kernel notifications\n"
+
310 " --only-expire Expire entries instead of invalidating them\n"
+
311 "\n");
+
312}
+
313
+
314int main(int argc, char *argv[]) {
+
315 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
316 struct fuse_session *se;
+
317 struct fuse_cmdline_opts opts;
+
318 struct fuse_loop_config *config;
+
319 pthread_t updater;
+
320 int ret = -1;
+
321
+
322 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
323 return 1;
+
324
+
325 if (fuse_parse_cmdline(&args, &opts) != 0)
+
326 return 1;
+
327 if (opts.show_help) {
+
328 show_help(argv[0]);
+ + +
331 ret = 0;
+
332 goto err_out1;
+
333 } else if (opts.show_version) {
+
334 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
336 ret = 0;
+
337 goto err_out1;
+
338 }
+
339
+
340 /* Initial contents */
+
341 update_fs();
+
342
+
343 se = fuse_session_new(&args, &tfs_oper,
+
344 sizeof(tfs_oper), &se);
+
345 if (se == NULL)
+
346 goto err_out1;
+
347
+
348 if (fuse_set_signal_handlers(se) != 0)
+
349 goto err_out2;
+
350
+
351 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
352 goto err_out3;
+
353
+
354 fuse_daemonize(opts.foreground);
+
355
+
356 // Needed to ensure that the main thread continues/restarts processing as soon
+
357 // as the fuse session ends (immediately after calling fuse_session_exit() )
+
358 // and not only on the next request from userspace
+
359 main_thread = pthread_self();
+
360
+
361 /* Start thread to update file contents */
+
362 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
363 if (ret != 0) {
+
364 fprintf(stderr, "pthread_create failed with %s\n",
+
365 strerror(ret));
+
366 goto err_out3;
+
367 }
+
368
+
369 /* Block until ctrl+c or fusermount -u */
+
370 if (opts.singlethread) {
+
371 ret = fuse_session_loop(se);
+
372 } else {
+
373 config = fuse_loop_cfg_create();
+
374 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
375 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
376 ret = fuse_session_loop_mt(se, config);
+
377 fuse_loop_cfg_destroy(config);
+
378 config = NULL;
+
379 }
+
380
+ +
382err_out3:
+ +
384err_out2:
+ +
386err_out1:
+
387 free(opts.mountpoint);
+
388 fuse_opt_free_args(&args);
+
389
+
390 return ret ? 1 : 0;
+
391}
+
392
+
393
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c.html new file mode 100644 index 0000000..14a84b8 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c.html @@ -0,0 +1,483 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/notify_inval_inode.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_inode.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

+

To see the effect, first start the file system with the --no-notify option:

+

$ notify_inval_inode –update-interval=1 –no-notify mnt/

+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

 $ notify_inval_inode --update-interval=1 mnt/
+ $ for i in 1 2 3 4 5; do
+ >     cat mnt/current_time
+ >     sleep 1
+ > done
+ The current time is 15:58:40
+ The current time is 15:58:41
+ The current time is 15:58:42
+ The current time is 15:58:43
+ The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
#include <stdatomic.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static size_t file_size;
+
static _Atomic bool is_stop = false;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_destroy(void *userarg)
+
{
+
(void)userarg;
+
+
is_stop = true;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO)
+
fuse_reply_open(req, fi);
+
else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.destroy = tfs_destroy,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
+
while(!is_stop) {
+
update_fs();
+
if (!options.no_notify && lookup_cnt) {
+
/* Only send notification if the kernel is aware of the inode */
+
+
/* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
int ret =
+
fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
if ((ret != 0 && !is_stop) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0) {
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+ +
free(opts.mountpoint);
+
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_inode.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c_source.html new file mode 100644 index 0000000..9397d7d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2notify__inval__inode_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/notify_inval_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_inode.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
63
+
64#include <fuse_lowlevel.h>
+
65#include <stdio.h>
+
66#include <stdlib.h>
+
67#include <string.h>
+
68#include <errno.h>
+
69#include <fcntl.h>
+
70#include <assert.h>
+
71#include <stddef.h>
+
72#include <unistd.h>
+
73#include <pthread.h>
+
74#include <stdbool.h>
+
75#include <stdatomic.h>
+
76
+
77/* We can't actually tell the kernel that there is no
+
78 timeout, so we just send a big value */
+
79#define NO_TIMEOUT 500000
+
80
+
81#define MAX_STR_LEN 128
+
82#define FILE_INO 2
+
83#define FILE_NAME "current_time"
+
84static char file_contents[MAX_STR_LEN];
+
85static int lookup_cnt = 0;
+
86static size_t file_size;
+
87static _Atomic bool is_stop = false;
+
88
+
89/* Command line parsing */
+
90struct options {
+
91 int no_notify;
+
92 int update_interval;
+
93};
+
94static struct options options = {
+
95 .no_notify = 0,
+
96 .update_interval = 1,
+
97};
+
98
+
99#define OPTION(t, p) \
+
100 { t, offsetof(struct options, p), 1 }
+
101static const struct fuse_opt option_spec[] = {
+
102 OPTION("--no-notify", no_notify),
+
103 OPTION("--update-interval=%d", update_interval),
+ +
105};
+
106
+
107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
108 stbuf->st_ino = ino;
+
109 if (ino == FUSE_ROOT_ID) {
+
110 stbuf->st_mode = S_IFDIR | 0755;
+
111 stbuf->st_nlink = 1;
+
112 }
+
113
+
114 else if (ino == FILE_INO) {
+
115 stbuf->st_mode = S_IFREG | 0444;
+
116 stbuf->st_nlink = 1;
+
117 stbuf->st_size = file_size;
+
118 }
+
119
+
120 else
+
121 return -1;
+
122
+
123 return 0;
+
124}
+
125
+
126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
127 (void)userdata;
+
128
+
129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
130 conn->no_interrupt = 1;
+
131}
+
132
+
133static void tfs_destroy(void *userarg)
+
134{
+
135 (void)userarg;
+
136
+
137 is_stop = true;
+
138}
+
139
+
140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
141 const char *name) {
+
142 struct fuse_entry_param e;
+
143 memset(&e, 0, sizeof(e));
+
144
+
145 if (parent != FUSE_ROOT_ID)
+
146 goto err_out;
+
147 else if (strcmp(name, FILE_NAME) == 0) {
+
148 e.ino = FILE_INO;
+
149 lookup_cnt++;
+
150 } else
+
151 goto err_out;
+
152
+
153 e.attr_timeout = NO_TIMEOUT;
+
154 e.entry_timeout = NO_TIMEOUT;
+
155 if (tfs_stat(e.ino, &e.attr) != 0)
+
156 goto err_out;
+
157 fuse_reply_entry(req, &e);
+
158 return;
+
159
+
160err_out:
+
161 fuse_reply_err(req, ENOENT);
+
162}
+
163
+
164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
165 uint64_t nlookup) {
+
166 (void) req;
+
167 if(ino == FILE_INO)
+
168 lookup_cnt -= nlookup;
+
169 else
+
170 assert(ino == FUSE_ROOT_ID);
+
171 fuse_reply_none(req);
+
172}
+
173
+
174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
175 struct fuse_file_info *fi) {
+
176 struct stat stbuf;
+
177
+
178 (void) fi;
+
179
+
180 memset(&stbuf, 0, sizeof(stbuf));
+
181 if (tfs_stat(ino, &stbuf) != 0)
+
182 fuse_reply_err(req, ENOENT);
+
183 else
+
184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
185}
+
186
+
187struct dirbuf {
+
188 char *p;
+
189 size_t size;
+
190};
+
191
+
192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
193 fuse_ino_t ino) {
+
194 struct stat stbuf;
+
195 size_t oldsize = b->size;
+
196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
197 b->p = (char *) realloc(b->p, b->size);
+
198 memset(&stbuf, 0, sizeof(stbuf));
+
199 stbuf.st_ino = ino;
+
200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
201 b->size);
+
202}
+
203
+
204#define min(x, y) ((x) < (y) ? (x) : (y))
+
205
+
206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
207 off_t off, size_t maxsize) {
+
208 if (off < bufsize)
+
209 return fuse_reply_buf(req, buf + off,
+
210 min(bufsize - off, maxsize));
+
211 else
+
212 return fuse_reply_buf(req, NULL, 0);
+
213}
+
214
+
215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
216 off_t off, struct fuse_file_info *fi) {
+
217 (void) fi;
+
218
+
219 if (ino != FUSE_ROOT_ID)
+
220 fuse_reply_err(req, ENOTDIR);
+
221 else {
+
222 struct dirbuf b;
+
223
+
224 memset(&b, 0, sizeof(b));
+
225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
226 reply_buf_limited(req, b.p, b.size, off, size);
+
227 free(b.p);
+
228 }
+
229}
+
230
+
231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
232 struct fuse_file_info *fi) {
+
233
+
234 /* Make cache persistent even if file is closed,
+
235 this makes it easier to see the effects */
+
236 fi->keep_cache = 1;
+
237
+
238 if (ino == FUSE_ROOT_ID)
+
239 fuse_reply_err(req, EISDIR);
+
240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
241 fuse_reply_err(req, EACCES);
+
242 else if (ino == FILE_INO)
+
243 fuse_reply_open(req, fi);
+
244 else {
+
245 // This should not happen
+
246 fprintf(stderr, "Got open for non-existing inode!\n");
+
247 fuse_reply_err(req, ENOENT);
+
248 }
+
249}
+
250
+
251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
252 off_t off, struct fuse_file_info *fi) {
+
253 (void) fi;
+
254
+
255 assert(ino == FILE_INO);
+
256 reply_buf_limited(req, file_contents, file_size, off, size);
+
257}
+
258
+
259static const struct fuse_lowlevel_ops tfs_oper = {
+
260 .init = tfs_init,
+
261 .destroy = tfs_destroy,
+
262 .lookup = tfs_lookup,
+
263 .getattr = tfs_getattr,
+
264 .readdir = tfs_readdir,
+
265 .open = tfs_open,
+
266 .read = tfs_read,
+
267 .forget = tfs_forget,
+
268};
+
269
+
270static void update_fs(void) {
+
271 struct tm *now;
+
272 time_t t;
+
273 t = time(NULL);
+
274 now = localtime(&t);
+
275 assert(now != NULL);
+
276
+
277 file_size = strftime(file_contents, MAX_STR_LEN,
+
278 "The current time is %H:%M:%S\n", now);
+
279 assert(file_size != 0);
+
280}
+
281
+
282static void* update_fs_loop(void *data) {
+
283 struct fuse_session *se = (struct fuse_session*) data;
+
284
+
285 while(!is_stop) {
+
286 update_fs();
+
287 if (!options.no_notify && lookup_cnt) {
+
288 /* Only send notification if the kernel is aware of the inode */
+
289
+
290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
291 * might come up during umount, when kernel side already releases
+
292 * all inodes, but does not send FUSE_DESTROY yet.
+
293 */
+
294 int ret =
+
295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
296 if ((ret != 0 && !is_stop) &&
+
297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
298 fprintf(stderr,
+
299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
300 strerror(-ret), -ret);
+
301 abort();
+
302 }
+
303 }
+
304 sleep(options.update_interval);
+
305 }
+
306 return NULL;
+
307}
+
308
+
309static void show_help(const char *progname)
+
310{
+
311 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
312 printf("File-system specific options:\n"
+
313 " --update-interval=<secs> Update-rate of file system contents\n"
+
314 " --no-notify Disable kernel notifications\n"
+
315 "\n");
+
316}
+
317
+
318int main(int argc, char *argv[]) {
+
319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
320 struct fuse_session *se;
+
321 struct fuse_cmdline_opts opts;
+
322 struct fuse_loop_config *config;
+
323 pthread_t updater;
+
324 int ret = -1;
+
325
+
326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
327 return 1;
+
328
+
329 if (fuse_parse_cmdline(&args, &opts) != 0) {
+
330 ret = 1;
+
331 goto err_out1;
+
332 }
+
333
+
334 if (opts.show_help) {
+
335 show_help(argv[0]);
+ + +
338 ret = 0;
+
339 goto err_out1;
+
340 } else if (opts.show_version) {
+
341 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
343 ret = 0;
+
344 goto err_out1;
+
345 }
+
346
+
347 /* Initial contents */
+
348 update_fs();
+
349
+
350 se = fuse_session_new(&args, &tfs_oper,
+
351 sizeof(tfs_oper), NULL);
+
352 if (se == NULL)
+
353 goto err_out1;
+
354
+
355 if (fuse_set_signal_handlers(se) != 0)
+
356 goto err_out2;
+
357
+
358 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
359 goto err_out3;
+
360
+
361 fuse_daemonize(opts.foreground);
+
362
+
363 /* Start thread to update file contents */
+
364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
365 if (ret != 0) {
+
366 fprintf(stderr, "pthread_create failed with %s\n",
+
367 strerror(ret));
+
368 goto err_out3;
+
369 }
+
370
+
371 /* Block until ctrl+c or fusermount -u */
+
372 if (opts.singlethread)
+
373 ret = fuse_session_loop(se);
+
374 else {
+
375 config = fuse_loop_cfg_create();
+
376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
378 ret = fuse_session_loop_mt(se, config);
+
379 fuse_loop_cfg_destroy(config);
+
380 config = NULL;
+
381 }
+
382
+ +
384err_out3:
+ +
386err_out2:
+ +
388err_out1:
+
389 fuse_opt_free_args(&args);
+
390 free(opts.mountpoint);
+
391
+
392 return ret ? 1 : 0;
+
393}
+
394
+
395
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..98aeaf7 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..95bd86d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2null_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2null_8c.html new file mode 100644 index 0000000..4308f7f --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2null_8c.html @@ -0,0 +1,763 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2null_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2null_8c_source.html new file mode 100644 index 0000000..082105a --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c.html new file mode 100644 index 0000000..e6a768f --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c.html @@ -0,0 +1,663 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#ifdef __FreeBSD__
+
#include <sys/socket.h>
+
#include <sys/un.h>
+
#endif
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = -posix_fallocate(fd, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c_source.html new file mode 100644 index 0000000..caeb58e --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2passthrough_8c_source.html @@ -0,0 +1,649 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#ifdef __FreeBSD__
+
44#include <sys/socket.h>
+
45#include <sys/un.h>
+
46#endif
+
47#include <sys/time.h>
+
48#ifdef HAVE_SETXATTR
+
49#include <sys/xattr.h>
+
50#endif
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54static int fill_dir_plus = 0;
+
55
+
56static void *xmp_init(struct fuse_conn_info *conn,
+
57 struct fuse_config *cfg)
+
58{
+
59 (void) conn;
+
60 cfg->use_ino = 1;
+
61
+
62 /* parallel_direct_writes feature depends on direct_io features.
+
63 To make parallel_direct_writes valid, need either set cfg->direct_io
+
64 in current function (recommended in high level API) or set fi->direct_io
+
65 in xmp_create() or xmp_open(). */
+
66 // cfg->direct_io = 1;
+ +
68
+
69 /* Pick up changes from lower filesystem right away. This is
+
70 also necessary for better hardlink support. When the kernel
+
71 calls the unlink() handler, it does not know the inode of
+
72 the to-be-removed entry and can therefore not invalidate
+
73 the cache of the associated inode - resulting in an
+
74 incorrect st_nlink value being reported for any remaining
+
75 hardlinks to this inode. */
+
76 if (!cfg->auto_cache) {
+
77 cfg->entry_timeout = 0;
+
78 cfg->attr_timeout = 0;
+
79 cfg->negative_timeout = 0;
+
80 }
+
81
+
82 return NULL;
+
83}
+
84
+
85static int xmp_getattr(const char *path, struct stat *stbuf,
+
86 struct fuse_file_info *fi)
+
87{
+
88 (void) fi;
+
89 int res;
+
90
+
91 res = lstat(path, stbuf);
+
92 if (res == -1)
+
93 return -errno;
+
94
+
95 return 0;
+
96}
+
97
+
98static int xmp_access(const char *path, int mask)
+
99{
+
100 int res;
+
101
+
102 res = access(path, mask);
+
103 if (res == -1)
+
104 return -errno;
+
105
+
106 return 0;
+
107}
+
108
+
109static int xmp_readlink(const char *path, char *buf, size_t size)
+
110{
+
111 int res;
+
112
+
113 res = readlink(path, buf, size - 1);
+
114 if (res == -1)
+
115 return -errno;
+
116
+
117 buf[res] = '\0';
+
118 return 0;
+
119}
+
120
+
121
+
122static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
123 off_t offset, struct fuse_file_info *fi,
+
124 enum fuse_readdir_flags flags)
+
125{
+
126 DIR *dp;
+
127 struct dirent *de;
+
128
+
129 (void) offset;
+
130 (void) fi;
+
131 (void) flags;
+
132
+
133 dp = opendir(path);
+
134 if (dp == NULL)
+
135 return -errno;
+
136
+
137 while ((de = readdir(dp)) != NULL) {
+
138 struct stat st;
+
139 if (fill_dir_plus) {
+
140 fstatat(dirfd(dp), de->d_name, &st,
+
141 AT_SYMLINK_NOFOLLOW);
+
142 } else {
+
143 memset(&st, 0, sizeof(st));
+
144 st.st_ino = de->d_ino;
+
145 st.st_mode = de->d_type << 12;
+
146 }
+
147 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
148 break;
+
149 }
+
150
+
151 closedir(dp);
+
152 return 0;
+
153}
+
154
+
155static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
156{
+
157 int res;
+
158
+
159 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
160 if (res == -1)
+
161 return -errno;
+
162
+
163 return 0;
+
164}
+
165
+
166static int xmp_mkdir(const char *path, mode_t mode)
+
167{
+
168 int res;
+
169
+
170 res = mkdir(path, mode);
+
171 if (res == -1)
+
172 return -errno;
+
173
+
174 return 0;
+
175}
+
176
+
177static int xmp_unlink(const char *path)
+
178{
+
179 int res;
+
180
+
181 res = unlink(path);
+
182 if (res == -1)
+
183 return -errno;
+
184
+
185 return 0;
+
186}
+
187
+
188static int xmp_rmdir(const char *path)
+
189{
+
190 int res;
+
191
+
192 res = rmdir(path);
+
193 if (res == -1)
+
194 return -errno;
+
195
+
196 return 0;
+
197}
+
198
+
199static int xmp_symlink(const char *from, const char *to)
+
200{
+
201 int res;
+
202
+
203 res = symlink(from, to);
+
204 if (res == -1)
+
205 return -errno;
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
211{
+
212 int res;
+
213
+
214 if (flags)
+
215 return -EINVAL;
+
216
+
217 res = rename(from, to);
+
218 if (res == -1)
+
219 return -errno;
+
220
+
221 return 0;
+
222}
+
223
+
224static int xmp_link(const char *from, const char *to)
+
225{
+
226 int res;
+
227
+
228 res = link(from, to);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_chmod(const char *path, mode_t mode,
+
236 struct fuse_file_info *fi)
+
237{
+
238 (void) fi;
+
239 int res;
+
240
+
241 res = chmod(path, mode);
+
242 if (res == -1)
+
243 return -errno;
+
244
+
245 return 0;
+
246}
+
247
+
248static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
249 struct fuse_file_info *fi)
+
250{
+
251 (void) fi;
+
252 int res;
+
253
+
254 res = lchown(path, uid, gid);
+
255 if (res == -1)
+
256 return -errno;
+
257
+
258 return 0;
+
259}
+
260
+
261static int xmp_truncate(const char *path, off_t size,
+
262 struct fuse_file_info *fi)
+
263{
+
264 int res;
+
265
+
266 if (fi != NULL)
+
267 res = ftruncate(fi->fh, size);
+
268 else
+
269 res = truncate(path, size);
+
270 if (res == -1)
+
271 return -errno;
+
272
+
273 return 0;
+
274}
+
275
+
276#ifdef HAVE_UTIMENSAT
+
277static int xmp_utimens(const char *path, const struct timespec ts[2],
+
278 struct fuse_file_info *fi)
+
279{
+
280 (void) fi;
+
281 int res;
+
282
+
283 /* don't use utime/utimes since they follow symlinks */
+
284 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
285 if (res == -1)
+
286 return -errno;
+
287
+
288 return 0;
+
289}
+
290#endif
+
291
+
292static int xmp_create(const char *path, mode_t mode,
+
293 struct fuse_file_info *fi)
+
294{
+
295 int res;
+
296
+
297 res = open(path, fi->flags, mode);
+
298 if (res == -1)
+
299 return -errno;
+
300
+
301 fi->fh = res;
+
302 return 0;
+
303}
+
304
+
305static int xmp_open(const char *path, struct fuse_file_info *fi)
+
306{
+
307 int res;
+
308
+
309 res = open(path, fi->flags);
+
310 if (res == -1)
+
311 return -errno;
+
312
+
313 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
314 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
315 for writes to the same file). */
+
316 if (fi->flags & O_DIRECT) {
+
317 fi->direct_io = 1;
+ +
319 }
+
320
+
321 fi->fh = res;
+
322 return 0;
+
323}
+
324
+
325static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
326 struct fuse_file_info *fi)
+
327{
+
328 int fd;
+
329 int res;
+
330
+
331 if(fi == NULL)
+
332 fd = open(path, O_RDONLY);
+
333 else
+
334 fd = fi->fh;
+
335
+
336 if (fd == -1)
+
337 return -errno;
+
338
+
339 res = pread(fd, buf, size, offset);
+
340 if (res == -1)
+
341 res = -errno;
+
342
+
343 if(fi == NULL)
+
344 close(fd);
+
345 return res;
+
346}
+
347
+
348static int xmp_write(const char *path, const char *buf, size_t size,
+
349 off_t offset, struct fuse_file_info *fi)
+
350{
+
351 int fd;
+
352 int res;
+
353
+
354 (void) fi;
+
355 if(fi == NULL)
+
356 fd = open(path, O_WRONLY);
+
357 else
+
358 fd = fi->fh;
+
359
+
360 if (fd == -1)
+
361 return -errno;
+
362
+
363 res = pwrite(fd, buf, size, offset);
+
364 if (res == -1)
+
365 res = -errno;
+
366
+
367 if(fi == NULL)
+
368 close(fd);
+
369 return res;
+
370}
+
371
+
372static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
373{
+
374 int res;
+
375
+
376 res = statvfs(path, stbuf);
+
377 if (res == -1)
+
378 return -errno;
+
379
+
380 return 0;
+
381}
+
382
+
383static int xmp_release(const char *path, struct fuse_file_info *fi)
+
384{
+
385 (void) path;
+
386 close(fi->fh);
+
387 return 0;
+
388}
+
389
+
390static int xmp_fsync(const char *path, int isdatasync,
+
391 struct fuse_file_info *fi)
+
392{
+
393 /* Just a stub. This method is optional and can safely be left
+
394 unimplemented */
+
395
+
396 (void) path;
+
397 (void) isdatasync;
+
398 (void) fi;
+
399 return 0;
+
400}
+
401
+
402#ifdef HAVE_POSIX_FALLOCATE
+
403static int xmp_fallocate(const char *path, int mode,
+
404 off_t offset, off_t length, struct fuse_file_info *fi)
+
405{
+
406 int fd;
+
407 int res;
+
408
+
409 (void) fi;
+
410
+
411 if (mode)
+
412 return -EOPNOTSUPP;
+
413
+
414 if(fi == NULL)
+
415 fd = open(path, O_WRONLY);
+
416 else
+
417 fd = fi->fh;
+
418
+
419 if (fd == -1)
+
420 return -errno;
+
421
+
422 res = -posix_fallocate(fd, offset, length);
+
423
+
424 if(fi == NULL)
+
425 close(fd);
+
426 return res;
+
427}
+
428#endif
+
429
+
430#ifdef HAVE_SETXATTR
+
431/* xattr operations are optional and can safely be left unimplemented */
+
432static int xmp_setxattr(const char *path, const char *name, const char *value,
+
433 size_t size, int flags)
+
434{
+
435 int res = lsetxattr(path, name, value, size, flags);
+
436 if (res == -1)
+
437 return -errno;
+
438 return 0;
+
439}
+
440
+
441static int xmp_getxattr(const char *path, const char *name, char *value,
+
442 size_t size)
+
443{
+
444 int res = lgetxattr(path, name, value, size);
+
445 if (res == -1)
+
446 return -errno;
+
447 return res;
+
448}
+
449
+
450static int xmp_listxattr(const char *path, char *list, size_t size)
+
451{
+
452 int res = llistxattr(path, list, size);
+
453 if (res == -1)
+
454 return -errno;
+
455 return res;
+
456}
+
457
+
458static int xmp_removexattr(const char *path, const char *name)
+
459{
+
460 int res = lremovexattr(path, name);
+
461 if (res == -1)
+
462 return -errno;
+
463 return 0;
+
464}
+
465#endif /* HAVE_SETXATTR */
+
466
+
467#ifdef HAVE_COPY_FILE_RANGE
+
468static ssize_t xmp_copy_file_range(const char *path_in,
+
469 struct fuse_file_info *fi_in,
+
470 off_t offset_in, const char *path_out,
+
471 struct fuse_file_info *fi_out,
+
472 off_t offset_out, size_t len, int flags)
+
473{
+
474 int fd_in, fd_out;
+
475 ssize_t res;
+
476
+
477 if(fi_in == NULL)
+
478 fd_in = open(path_in, O_RDONLY);
+
479 else
+
480 fd_in = fi_in->fh;
+
481
+
482 if (fd_in == -1)
+
483 return -errno;
+
484
+
485 if(fi_out == NULL)
+
486 fd_out = open(path_out, O_WRONLY);
+
487 else
+
488 fd_out = fi_out->fh;
+
489
+
490 if (fd_out == -1) {
+
491 close(fd_in);
+
492 return -errno;
+
493 }
+
494
+
495 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
496 flags);
+
497 if (res == -1)
+
498 res = -errno;
+
499
+
500 if (fi_out == NULL)
+
501 close(fd_out);
+
502 if (fi_in == NULL)
+
503 close(fd_in);
+
504
+
505 return res;
+
506}
+
507#endif
+
508
+
509static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
510{
+
511 int fd;
+
512 off_t res;
+
513
+
514 if (fi == NULL)
+
515 fd = open(path, O_RDONLY);
+
516 else
+
517 fd = fi->fh;
+
518
+
519 if (fd == -1)
+
520 return -errno;
+
521
+
522 res = lseek(fd, off, whence);
+
523 if (res == -1)
+
524 res = -errno;
+
525
+
526 if (fi == NULL)
+
527 close(fd);
+
528 return res;
+
529}
+
530
+
531static const struct fuse_operations xmp_oper = {
+
532 .init = xmp_init,
+
533 .getattr = xmp_getattr,
+
534 .access = xmp_access,
+
535 .readlink = xmp_readlink,
+
536 .readdir = xmp_readdir,
+
537 .mknod = xmp_mknod,
+
538 .mkdir = xmp_mkdir,
+
539 .symlink = xmp_symlink,
+
540 .unlink = xmp_unlink,
+
541 .rmdir = xmp_rmdir,
+
542 .rename = xmp_rename,
+
543 .link = xmp_link,
+
544 .chmod = xmp_chmod,
+
545 .chown = xmp_chown,
+
546 .truncate = xmp_truncate,
+
547#ifdef HAVE_UTIMENSAT
+
548 .utimens = xmp_utimens,
+
549#endif
+
550 .open = xmp_open,
+
551 .create = xmp_create,
+
552 .read = xmp_read,
+
553 .write = xmp_write,
+
554 .statfs = xmp_statfs,
+
555 .release = xmp_release,
+
556 .fsync = xmp_fsync,
+
557#ifdef HAVE_POSIX_FALLOCATE
+
558 .fallocate = xmp_fallocate,
+
559#endif
+
560#ifdef HAVE_SETXATTR
+
561 .setxattr = xmp_setxattr,
+
562 .getxattr = xmp_getxattr,
+
563 .listxattr = xmp_listxattr,
+
564 .removexattr = xmp_removexattr,
+
565#endif
+
566#ifdef HAVE_COPY_FILE_RANGE
+
567 .copy_file_range = xmp_copy_file_range,
+
568#endif
+
569 .lseek = xmp_lseek,
+
570};
+
571
+
572int main(int argc, char *argv[])
+
573{
+
574 enum { MAX_ARGS = 10 };
+
575 int i,new_argc;
+
576 char *new_argv[MAX_ARGS];
+
577
+
578 umask(0);
+
579 /* Process the "--plus" option apart */
+
580 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
581 if (!strcmp(argv[i], "--plus")) {
+
582 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
583 } else {
+
584 new_argv[new_argc++] = argv[i];
+
585 }
+
586 }
+
587 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
588}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c.html new file mode 100644 index 0000000..0faf00f --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c.html @@ -0,0 +1,766 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..a0ec9e3 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__fh_8c_source.html @@ -0,0 +1,751 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50static void *xmp_init(struct fuse_conn_info *conn,
+
51 struct fuse_config *cfg)
+
52{
+
53 (void) conn;
+
54 cfg->use_ino = 1;
+
55 cfg->nullpath_ok = 1;
+
56
+
57 /* parallel_direct_writes feature depends on direct_io features.
+
58 To make parallel_direct_writes valid, need either set cfg->direct_io
+
59 in current function (recommended in high level API) or set fi->direct_io
+
60 in xmp_create() or xmp_open(). */
+
61 // cfg->direct_io = 1;
+ +
63
+
64 /* Pick up changes from lower filesystem right away. This is
+
65 also necessary for better hardlink support. When the kernel
+
66 calls the unlink() handler, it does not know the inode of
+
67 the to-be-removed entry and can therefore not invalidate
+
68 the cache of the associated inode - resulting in an
+
69 incorrect st_nlink value being reported for any remaining
+
70 hardlinks to this inode. */
+
71 cfg->entry_timeout = 0;
+
72 cfg->attr_timeout = 0;
+
73 cfg->negative_timeout = 0;
+
74
+
75 return NULL;
+
76}
+
77
+
78static int xmp_getattr(const char *path, struct stat *stbuf,
+
79 struct fuse_file_info *fi)
+
80{
+
81 int res;
+
82
+
83 (void) path;
+
84
+
85 if(fi)
+
86 res = fstat(fi->fh, stbuf);
+
87 else
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118struct xmp_dirp {
+
119 DIR *dp;
+
120 struct dirent *entry;
+
121 off_t offset;
+
122};
+
123
+
124static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
125{
+
126 int res;
+
127 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
128 if (d == NULL)
+
129 return -ENOMEM;
+
130
+
131 d->dp = opendir(path);
+
132 if (d->dp == NULL) {
+
133 res = -errno;
+
134 free(d);
+
135 return res;
+
136 }
+
137 d->offset = 0;
+
138 d->entry = NULL;
+
139
+
140 fi->fh = (unsigned long) d;
+
141 return 0;
+
142}
+
143
+
144static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
145{
+
146 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
147}
+
148
+
149static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
150 off_t offset, struct fuse_file_info *fi,
+
151 enum fuse_readdir_flags flags)
+
152{
+
153 struct xmp_dirp *d = get_dirp(fi);
+
154
+
155 (void) path;
+
156 if (offset != d->offset) {
+
157#ifndef __FreeBSD__
+
158 seekdir(d->dp, offset);
+
159#else
+
160 /* Subtract the one that we add when calling
+
161 telldir() below */
+
162 seekdir(d->dp, offset-1);
+
163#endif
+
164 d->entry = NULL;
+
165 d->offset = offset;
+
166 }
+
167 while (1) {
+
168 struct stat st;
+
169 off_t nextoff;
+ +
171
+
172 if (!d->entry) {
+
173 d->entry = readdir(d->dp);
+
174 if (!d->entry)
+
175 break;
+
176 }
+
177#ifdef HAVE_FSTATAT
+
178 if (flags & FUSE_READDIR_PLUS) {
+
179 int res;
+
180
+
181 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
182 AT_SYMLINK_NOFOLLOW);
+
183 if (res != -1)
+
184 fill_flags |= FUSE_FILL_DIR_PLUS;
+
185 }
+
186#endif
+
187 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
188 memset(&st, 0, sizeof(st));
+
189 st.st_ino = d->entry->d_ino;
+
190 st.st_mode = d->entry->d_type << 12;
+
191 }
+
192 nextoff = telldir(d->dp);
+
193#ifdef __FreeBSD__
+
194 /* Under FreeBSD, telldir() may return 0 the first time
+
195 it is called. But for libfuse, an offset of zero
+
196 means that offsets are not supported, so we shift
+
197 everything by one. */
+
198 nextoff++;
+
199#endif
+
200 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
201 break;
+
202
+
203 d->entry = NULL;
+
204 d->offset = nextoff;
+
205 }
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
211{
+
212 struct xmp_dirp *d = get_dirp(fi);
+
213 (void) path;
+
214 closedir(d->dp);
+
215 free(d);
+
216 return 0;
+
217}
+
218
+
219static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
220{
+
221 int res;
+
222
+
223 if (S_ISFIFO(mode))
+
224 res = mkfifo(path, mode);
+
225 else
+
226 res = mknod(path, mode, rdev);
+
227 if (res == -1)
+
228 return -errno;
+
229
+
230 return 0;
+
231}
+
232
+
233static int xmp_mkdir(const char *path, mode_t mode)
+
234{
+
235 int res;
+
236
+
237 res = mkdir(path, mode);
+
238 if (res == -1)
+
239 return -errno;
+
240
+
241 return 0;
+
242}
+
243
+
244static int xmp_unlink(const char *path)
+
245{
+
246 int res;
+
247
+
248 res = unlink(path);
+
249 if (res == -1)
+
250 return -errno;
+
251
+
252 return 0;
+
253}
+
254
+
255static int xmp_rmdir(const char *path)
+
256{
+
257 int res;
+
258
+
259 res = rmdir(path);
+
260 if (res == -1)
+
261 return -errno;
+
262
+
263 return 0;
+
264}
+
265
+
266static int xmp_symlink(const char *from, const char *to)
+
267{
+
268 int res;
+
269
+
270 res = symlink(from, to);
+
271 if (res == -1)
+
272 return -errno;
+
273
+
274 return 0;
+
275}
+
276
+
277static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
278{
+
279 int res;
+
280
+
281 /* When we have renameat2() in libc, then we can implement flags */
+
282 if (flags)
+
283 return -EINVAL;
+
284
+
285 res = rename(from, to);
+
286 if (res == -1)
+
287 return -errno;
+
288
+
289 return 0;
+
290}
+
291
+
292static int xmp_link(const char *from, const char *to)
+
293{
+
294 int res;
+
295
+
296 res = link(from, to);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 return 0;
+
301}
+
302
+
303static int xmp_chmod(const char *path, mode_t mode,
+
304 struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 if(fi)
+
309 res = fchmod(fi->fh, mode);
+
310 else
+
311 res = chmod(path, mode);
+
312 if (res == -1)
+
313 return -errno;
+
314
+
315 return 0;
+
316}
+
317
+
318static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 int res;
+
322
+
323 if (fi)
+
324 res = fchown(fi->fh, uid, gid);
+
325 else
+
326 res = lchown(path, uid, gid);
+
327 if (res == -1)
+
328 return -errno;
+
329
+
330 return 0;
+
331}
+
332
+
333static int xmp_truncate(const char *path, off_t size,
+
334 struct fuse_file_info *fi)
+
335{
+
336 int res;
+
337
+
338 if(fi)
+
339 res = ftruncate(fi->fh, size);
+
340 else
+
341 res = truncate(path, size);
+
342
+
343 if (res == -1)
+
344 return -errno;
+
345
+
346 return 0;
+
347}
+
348
+
349#ifdef HAVE_UTIMENSAT
+
350static int xmp_utimens(const char *path, const struct timespec ts[2],
+
351 struct fuse_file_info *fi)
+
352{
+
353 int res;
+
354
+
355 /* don't use utime/utimes since they follow symlinks */
+
356 if (fi)
+
357 res = futimens(fi->fh, ts);
+
358 else
+
359 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
360 if (res == -1)
+
361 return -errno;
+
362
+
363 return 0;
+
364}
+
365#endif
+
366
+
367static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
368{
+
369 int fd;
+
370
+
371 fd = open(path, fi->flags, mode);
+
372 if (fd == -1)
+
373 return -errno;
+
374
+
375 fi->fh = fd;
+
376 return 0;
+
377}
+
378
+
379static int xmp_open(const char *path, struct fuse_file_info *fi)
+
380{
+
381 int fd;
+
382
+
383 fd = open(path, fi->flags);
+
384 if (fd == -1)
+
385 return -errno;
+
386
+
387 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
388 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
389 for writes to the same file). */
+
390 if (fi->flags & O_DIRECT) {
+
391 fi->direct_io = 1;
+ +
393 }
+
394
+
395 fi->fh = fd;
+
396 return 0;
+
397}
+
398
+
399static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
400 struct fuse_file_info *fi)
+
401{
+
402 int res;
+
403
+
404 (void) path;
+
405 res = pread(fi->fh, buf, size, offset);
+
406 if (res == -1)
+
407 res = -errno;
+
408
+
409 return res;
+
410}
+
411
+
412static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
413 size_t size, off_t offset, struct fuse_file_info *fi)
+
414{
+
415 struct fuse_bufvec *src;
+
416
+
417 (void) path;
+
418
+
419 src = malloc(sizeof(struct fuse_bufvec));
+
420 if (src == NULL)
+
421 return -ENOMEM;
+
422
+
423 *src = FUSE_BUFVEC_INIT(size);
+
424
+ +
426 src->buf[0].fd = fi->fh;
+
427 src->buf[0].pos = offset;
+
428
+
429 *bufp = src;
+
430
+
431 return 0;
+
432}
+
433
+
434static int xmp_write(const char *path, const char *buf, size_t size,
+
435 off_t offset, struct fuse_file_info *fi)
+
436{
+
437 int res;
+
438
+
439 (void) path;
+
440 res = pwrite(fi->fh, buf, size, offset);
+
441 if (res == -1)
+
442 res = -errno;
+
443
+
444 return res;
+
445}
+
446
+
447static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
448 off_t offset, struct fuse_file_info *fi)
+
449{
+
450 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
451
+
452 (void) path;
+
453
+ +
455 dst.buf[0].fd = fi->fh;
+
456 dst.buf[0].pos = offset;
+
457
+ +
459}
+
460
+
461static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
462{
+
463 int res;
+
464
+
465 res = statvfs(path, stbuf);
+
466 if (res == -1)
+
467 return -errno;
+
468
+
469 return 0;
+
470}
+
471
+
472static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
473{
+
474 int res;
+
475
+
476 (void) path;
+
477 /* This is called from every close on an open file, so call the
+
478 close on the underlying filesystem. But since flush may be
+
479 called multiple times for an open file, this must not really
+
480 close the file. This is important if used on a network
+
481 filesystem like NFS which flush the data/metadata on close() */
+
482 res = close(dup(fi->fh));
+
483 if (res == -1)
+
484 return -errno;
+
485
+
486 return 0;
+
487}
+
488
+
489static int xmp_release(const char *path, struct fuse_file_info *fi)
+
490{
+
491 (void) path;
+
492 close(fi->fh);
+
493
+
494 return 0;
+
495}
+
496
+
497static int xmp_fsync(const char *path, int isdatasync,
+
498 struct fuse_file_info *fi)
+
499{
+
500 int res;
+
501 (void) path;
+
502
+
503#ifndef HAVE_FDATASYNC
+
504 (void) isdatasync;
+
505#else
+
506 if (isdatasync)
+
507 res = fdatasync(fi->fh);
+
508 else
+
509#endif
+
510 res = fsync(fi->fh);
+
511 if (res == -1)
+
512 return -errno;
+
513
+
514 return 0;
+
515}
+
516
+
517#ifdef HAVE_POSIX_FALLOCATE
+
518static int xmp_fallocate(const char *path, int mode,
+
519 off_t offset, off_t length, struct fuse_file_info *fi)
+
520{
+
521 (void) path;
+
522
+
523 if (mode)
+
524 return -EOPNOTSUPP;
+
525
+
526 return -posix_fallocate(fi->fh, offset, length);
+
527}
+
528#endif
+
529
+
530#ifdef HAVE_SETXATTR
+
531/* xattr operations are optional and can safely be left unimplemented */
+
532static int xmp_setxattr(const char *path, const char *name, const char *value,
+
533 size_t size, int flags)
+
534{
+
535 int res = lsetxattr(path, name, value, size, flags);
+
536 if (res == -1)
+
537 return -errno;
+
538 return 0;
+
539}
+
540
+
541static int xmp_getxattr(const char *path, const char *name, char *value,
+
542 size_t size)
+
543{
+
544 int res = lgetxattr(path, name, value, size);
+
545 if (res == -1)
+
546 return -errno;
+
547 return res;
+
548}
+
549
+
550static int xmp_listxattr(const char *path, char *list, size_t size)
+
551{
+
552 int res = llistxattr(path, list, size);
+
553 if (res == -1)
+
554 return -errno;
+
555 return res;
+
556}
+
557
+
558static int xmp_removexattr(const char *path, const char *name)
+
559{
+
560 int res = lremovexattr(path, name);
+
561 if (res == -1)
+
562 return -errno;
+
563 return 0;
+
564}
+
565#endif /* HAVE_SETXATTR */
+
566
+
567#ifdef HAVE_LIBULOCKMGR
+
568static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
569 struct flock *lock)
+
570{
+
571 (void) path;
+
572
+
573 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
574 sizeof(fi->lock_owner));
+
575}
+
576#endif
+
577
+
578static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
579{
+
580 int res;
+
581 (void) path;
+
582
+
583 res = flock(fi->fh, op);
+
584 if (res == -1)
+
585 return -errno;
+
586
+
587 return 0;
+
588}
+
589
+
590#ifdef HAVE_COPY_FILE_RANGE
+
591static ssize_t xmp_copy_file_range(const char *path_in,
+
592 struct fuse_file_info *fi_in,
+
593 off_t off_in, const char *path_out,
+
594 struct fuse_file_info *fi_out,
+
595 off_t off_out, size_t len, int flags)
+
596{
+
597 ssize_t res;
+
598 (void) path_in;
+
599 (void) path_out;
+
600
+
601 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
602 flags);
+
603 if (res == -1)
+
604 return -errno;
+
605
+
606 return res;
+
607}
+
608#endif
+
609
+
610static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
611{
+
612 off_t res;
+
613 (void) path;
+
614
+
615 res = lseek(fi->fh, off, whence);
+
616 if (res == -1)
+
617 return -errno;
+
618
+
619 return res;
+
620}
+
621
+
622static const struct fuse_operations xmp_oper = {
+
623 .init = xmp_init,
+
624 .getattr = xmp_getattr,
+
625 .access = xmp_access,
+
626 .readlink = xmp_readlink,
+
627 .opendir = xmp_opendir,
+
628 .readdir = xmp_readdir,
+
629 .releasedir = xmp_releasedir,
+
630 .mknod = xmp_mknod,
+
631 .mkdir = xmp_mkdir,
+
632 .symlink = xmp_symlink,
+
633 .unlink = xmp_unlink,
+
634 .rmdir = xmp_rmdir,
+
635 .rename = xmp_rename,
+
636 .link = xmp_link,
+
637 .chmod = xmp_chmod,
+
638 .chown = xmp_chown,
+
639 .truncate = xmp_truncate,
+
640#ifdef HAVE_UTIMENSAT
+
641 .utimens = xmp_utimens,
+
642#endif
+
643 .create = xmp_create,
+
644 .open = xmp_open,
+
645 .read = xmp_read,
+
646 .read_buf = xmp_read_buf,
+
647 .write = xmp_write,
+
648 .write_buf = xmp_write_buf,
+
649 .statfs = xmp_statfs,
+
650 .flush = xmp_flush,
+
651 .release = xmp_release,
+
652 .fsync = xmp_fsync,
+
653#ifdef HAVE_POSIX_FALLOCATE
+
654 .fallocate = xmp_fallocate,
+
655#endif
+
656#ifdef HAVE_SETXATTR
+
657 .setxattr = xmp_setxattr,
+
658 .getxattr = xmp_getxattr,
+
659 .listxattr = xmp_listxattr,
+
660 .removexattr = xmp_removexattr,
+
661#endif
+
662#ifdef HAVE_LIBULOCKMGR
+
663 .lock = xmp_lock,
+
664#endif
+
665 .flock = xmp_flock,
+
666#ifdef HAVE_COPY_FILE_RANGE
+
667 .copy_file_range = xmp_copy_file_range,
+
668#endif
+
669 .lseek = xmp_lseek,
+
670};
+
671
+
672int main(int argc, char *argv[])
+
673{
+
674 umask(0);
+
675 return fuse_main(argc, argv, &xmp_oper, NULL);
+
676}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2passthrough__helpers_8h_source.html b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..5d19b5f --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__helpers_8h_source.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26/*
+
27 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
28 * operation
+
29 */
+
30static int mknod_wrapper(int dirfd, const char *path, const char *link,
+
31 int mode, dev_t rdev)
+
32{
+
33 int res;
+
34
+
35 if (S_ISREG(mode)) {
+
36 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
37 if (res >= 0)
+
38 res = close(res);
+
39 } else if (S_ISDIR(mode)) {
+
40 res = mkdirat(dirfd, path, mode);
+
41 } else if (S_ISLNK(mode) && link != NULL) {
+
42 res = symlinkat(link, dirfd, path);
+
43 } else if (S_ISFIFO(mode)) {
+
44 res = mkfifoat(dirfd, path, mode);
+
45#ifdef __FreeBSD__
+
46 } else if (S_ISSOCK(mode)) {
+
47 struct sockaddr_un su;
+
48 int fd;
+
49
+
50 if (strlen(path) >= sizeof(su.sun_path)) {
+
51 errno = ENAMETOOLONG;
+
52 return -1;
+
53 }
+
54 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
55 if (fd >= 0) {
+
56 /*
+
57 * We must bind the socket to the underlying file
+
58 * system to create the socket file, even though
+
59 * we'll never listen on this socket.
+
60 */
+
61 su.sun_family = AF_UNIX;
+
62 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
63 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
64 sizeof(su));
+
65 if (res == 0)
+
66 close(fd);
+
67 } else {
+
68 res = -1;
+
69 }
+
70#endif
+
71 } else {
+
72 res = mknodat(dirfd, path, mode, rdev);
+
73 }
+
74
+
75 return res;
+
76}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c.html new file mode 100644 index 0000000..75683a6 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c.html @@ -0,0 +1,1529 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three, but many operations are not implemented. In particular, it is not possible to remove files (or directories) because the code necessary to defer actual removal until the file is not opened anymore would make the example much more complicated.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
ino, out_buf.buf[0].size, (unsigned long) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err = EOPNOTSUPP;
+
(void) ino;
+
+
#ifdef HAVE_FALLOCATE
+
err = fallocate(fi->fh, mode, offset, length);
+
if (err < 0)
+
err = errno;
+
+
#elif defined(HAVE_POSIX_FALLOCATE)
+
if (mode) {
+
fuse_reply_err(req, EOPNOTSUPP);
+
return;
+
}
+
+
err = posix_fallocate(fi->fh, offset, length);
+
#endif
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, size=%zd, flags=0x%x)\n",
+
ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
@ FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_CAP_FLOCK_LOCKS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..b62a49e --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2passthrough__ll_8c_source.html @@ -0,0 +1,1508 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
37#define _GNU_SOURCE
+
38#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
39
+
40#include <fuse_lowlevel.h>
+
41#include <unistd.h>
+
42#include <stdlib.h>
+
43#include <stdio.h>
+
44#include <stddef.h>
+
45#include <stdbool.h>
+
46#include <string.h>
+
47#include <limits.h>
+
48#include <dirent.h>
+
49#include <assert.h>
+
50#include <errno.h>
+
51#include <inttypes.h>
+
52#include <pthread.h>
+
53#include <sys/file.h>
+
54#include <sys/xattr.h>
+
55
+
56#include "passthrough_helpers.h"
+
57
+
58/* We are re-using pointers to our `struct lo_inode` and `struct
+
59 lo_dirp` elements as inodes. This means that we must be able to
+
60 store uintptr_t values in a fuse_ino_t variable. The following
+
61 incantation checks this condition at compile time. */
+
62#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
63_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
64 "fuse_ino_t too small to hold uintptr_t values!");
+
65#else
+
66struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
67 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
68 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
69#endif
+
70
+
71struct lo_inode {
+
72 struct lo_inode *next; /* protected by lo->mutex */
+
73 struct lo_inode *prev; /* protected by lo->mutex */
+
74 int fd;
+
75 ino_t ino;
+
76 dev_t dev;
+
77 uint64_t refcount; /* protected by lo->mutex */
+
78};
+
79
+
80enum {
+
81 CACHE_NEVER,
+
82 CACHE_NORMAL,
+
83 CACHE_ALWAYS,
+
84};
+
85
+
86struct lo_data {
+
87 pthread_mutex_t mutex;
+
88 int debug;
+
89 int writeback;
+
90 int flock;
+
91 int xattr;
+
92 char *source;
+
93 double timeout;
+
94 int cache;
+
95 int timeout_set;
+
96 struct lo_inode root; /* protected by lo->mutex */
+
97};
+
98
+
99static const struct fuse_opt lo_opts[] = {
+
100 { "writeback",
+
101 offsetof(struct lo_data, writeback), 1 },
+
102 { "no_writeback",
+
103 offsetof(struct lo_data, writeback), 0 },
+
104 { "source=%s",
+
105 offsetof(struct lo_data, source), 0 },
+
106 { "flock",
+
107 offsetof(struct lo_data, flock), 1 },
+
108 { "no_flock",
+
109 offsetof(struct lo_data, flock), 0 },
+
110 { "xattr",
+
111 offsetof(struct lo_data, xattr), 1 },
+
112 { "no_xattr",
+
113 offsetof(struct lo_data, xattr), 0 },
+
114 { "timeout=%lf",
+
115 offsetof(struct lo_data, timeout), 0 },
+
116 { "timeout=",
+
117 offsetof(struct lo_data, timeout_set), 1 },
+
118 { "cache=never",
+
119 offsetof(struct lo_data, cache), CACHE_NEVER },
+
120 { "cache=auto",
+
121 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
122 { "cache=always",
+
123 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
124
+ +
126};
+
127
+
128static void passthrough_ll_help(void)
+
129{
+
130 printf(
+
131" -o writeback Enable writeback\n"
+
132" -o no_writeback Disable write back\n"
+
133" -o source=/home/dir Source directory to be mounted\n"
+
134" -o flock Enable flock\n"
+
135" -o no_flock Disable flock\n"
+
136" -o xattr Enable xattr\n"
+
137" -o no_xattr Disable xattr\n"
+
138" -o timeout=1.0 Caching timeout\n"
+
139" -o timeout=0/1 Timeout is set\n"
+
140" -o cache=never Disable cache\n"
+
141" -o cache=auto Auto enable cache\n"
+
142" -o cache=always Cache always\n");
+
143}
+
144
+
145static struct lo_data *lo_data(fuse_req_t req)
+
146{
+
147 return (struct lo_data *) fuse_req_userdata(req);
+
148}
+
149
+
150static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
151{
+
152 if (ino == FUSE_ROOT_ID)
+
153 return &lo_data(req)->root;
+
154 else
+
155 return (struct lo_inode *) (uintptr_t) ino;
+
156}
+
157
+
158static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
159{
+
160 return lo_inode(req, ino)->fd;
+
161}
+
162
+
163static bool lo_debug(fuse_req_t req)
+
164{
+
165 return lo_data(req)->debug != 0;
+
166}
+
167
+
168static void lo_init(void *userdata,
+
169 struct fuse_conn_info *conn)
+
170{
+
171 struct lo_data *lo = (struct lo_data *)userdata;
+
172 bool has_flag;
+
173
+
174 if (lo->writeback) {
+
175 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
176 if (lo->debug && has_flag)
+
177 fuse_log(FUSE_LOG_DEBUG,
+
178 "lo_init: activating writeback\n");
+
179 }
+
180 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
181 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
182 if (lo->debug && has_flag)
+
183 fuse_log(FUSE_LOG_DEBUG,
+
184 "lo_init: activating flock locks\n");
+
185 }
+
186
+
187 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
188 conn->no_interrupt = 1;
+
189}
+
190
+
191static void lo_destroy(void *userdata)
+
192{
+
193 struct lo_data *lo = (struct lo_data*) userdata;
+
194
+
195 while (lo->root.next != &lo->root) {
+
196 struct lo_inode* next = lo->root.next;
+
197 lo->root.next = next->next;
+
198 close(next->fd);
+
199 free(next);
+
200 }
+
201}
+
202
+
203static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
204 struct fuse_file_info *fi)
+
205{
+
206 int res;
+
207 struct stat buf;
+
208 struct lo_data *lo = lo_data(req);
+
209 int fd = fi ? fi->fh : lo_fd(req, ino);
+
210
+
211 (void) fi;
+
212
+
213 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
214 if (res == -1)
+
215 return (void) fuse_reply_err(req, errno);
+
216
+
217 fuse_reply_attr(req, &buf, lo->timeout);
+
218}
+
219
+
220static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
221 int valid, struct fuse_file_info *fi)
+
222{
+
223 int saverr;
+
224 char procname[64];
+
225 struct lo_inode *inode = lo_inode(req, ino);
+
226 int ifd = inode->fd;
+
227 int res;
+
228
+
229 if (valid & FUSE_SET_ATTR_MODE) {
+
230 if (fi) {
+
231 res = fchmod(fi->fh, attr->st_mode);
+
232 } else {
+
233 sprintf(procname, "/proc/self/fd/%i", ifd);
+
234 res = chmod(procname, attr->st_mode);
+
235 }
+
236 if (res == -1)
+
237 goto out_err;
+
238 }
+
239 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
240 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
241 attr->st_uid : (uid_t) -1;
+
242 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
243 attr->st_gid : (gid_t) -1;
+
244
+
245 res = fchownat(ifd, "", uid, gid,
+
246 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
247 if (res == -1)
+
248 goto out_err;
+
249 }
+
250 if (valid & FUSE_SET_ATTR_SIZE) {
+
251 if (fi) {
+
252 res = ftruncate(fi->fh, attr->st_size);
+
253 } else {
+
254 sprintf(procname, "/proc/self/fd/%i", ifd);
+
255 res = truncate(procname, attr->st_size);
+
256 }
+
257 if (res == -1)
+
258 goto out_err;
+
259 }
+
260 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
261 struct timespec tv[2];
+
262
+
263 tv[0].tv_sec = 0;
+
264 tv[1].tv_sec = 0;
+
265 tv[0].tv_nsec = UTIME_OMIT;
+
266 tv[1].tv_nsec = UTIME_OMIT;
+
267
+
268 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
269 tv[0].tv_nsec = UTIME_NOW;
+
270 else if (valid & FUSE_SET_ATTR_ATIME)
+
271 tv[0] = attr->st_atim;
+
272
+
273 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
274 tv[1].tv_nsec = UTIME_NOW;
+
275 else if (valid & FUSE_SET_ATTR_MTIME)
+
276 tv[1] = attr->st_mtim;
+
277
+
278 if (fi)
+
279 res = futimens(fi->fh, tv);
+
280 else {
+
281 sprintf(procname, "/proc/self/fd/%i", ifd);
+
282 res = utimensat(AT_FDCWD, procname, tv, 0);
+
283 }
+
284 if (res == -1)
+
285 goto out_err;
+
286 }
+
287
+
288 return lo_getattr(req, ino, fi);
+
289
+
290out_err:
+
291 saverr = errno;
+
292 fuse_reply_err(req, saverr);
+
293}
+
294
+
295static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
296{
+
297 struct lo_inode *p;
+
298 struct lo_inode *ret = NULL;
+
299
+
300 pthread_mutex_lock(&lo->mutex);
+
301 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
302 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
303 assert(p->refcount > 0);
+
304 ret = p;
+
305 ret->refcount++;
+
306 break;
+
307 }
+
308 }
+
309 pthread_mutex_unlock(&lo->mutex);
+
310 return ret;
+
311}
+
312
+
313
+
314static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
315{
+
316 struct lo_inode *inode = NULL;
+
317 struct lo_inode *prev, *next;
+
318
+
319 inode = calloc(1, sizeof(struct lo_inode));
+
320 if (!inode)
+
321 return NULL;
+
322
+
323 inode->refcount = 1;
+
324 inode->fd = fd;
+
325 inode->ino = e->attr.st_ino;
+
326 inode->dev = e->attr.st_dev;
+
327
+
328 pthread_mutex_lock(&lo->mutex);
+
329 prev = &lo->root;
+
330 next = prev->next;
+
331 next->prev = inode;
+
332 inode->next = next;
+
333 inode->prev = prev;
+
334 prev->next = inode;
+
335 pthread_mutex_unlock(&lo->mutex);
+
336 return inode;
+
337}
+
338
+
339static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
340{
+
341 int res;
+
342 struct lo_data *lo = lo_data(req);
+
343
+
344 memset(e, 0, sizeof(*e));
+
345 e->attr_timeout = lo->timeout;
+
346 e->entry_timeout = lo->timeout;
+
347
+
348 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
349 if (res == -1)
+
350 return errno;
+
351
+
352 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
353
+
354 if (lo_debug(req))
+
355 fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
356 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
357
+
358 return 0;
+
359
+
360}
+
361
+
362static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
363 struct fuse_entry_param *e)
+
364{
+
365 int newfd;
+
366 int res;
+
367 int saverr;
+
368 struct lo_data *lo = lo_data(req);
+
369 struct lo_inode *inode;
+
370
+
371 memset(e, 0, sizeof(*e));
+
372 e->attr_timeout = lo->timeout;
+
373 e->entry_timeout = lo->timeout;
+
374
+
375 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
376 if (newfd == -1)
+
377 goto out_err;
+
378
+
379 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
380 if (res == -1)
+
381 goto out_err;
+
382
+
383 inode = lo_find(lo_data(req), &e->attr);
+
384 if (inode) {
+
385 close(newfd);
+
386 newfd = -1;
+
387 } else {
+
388 inode = create_new_inode(newfd, e, lo);
+
389 if (!inode)
+
390 goto out_err;
+
391 }
+
392 e->ino = (uintptr_t) inode;
+
393
+
394 if (lo_debug(req))
+
395 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
396 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
397
+
398 return 0;
+
399
+
400out_err:
+
401 saverr = errno;
+
402 if (newfd != -1)
+
403 close(newfd);
+
404 return saverr;
+
405}
+
406
+
407static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
408{
+
409 struct fuse_entry_param e;
+
410 int err;
+
411
+
412 if (lo_debug(req))
+
413 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
414 parent, name);
+
415
+
416 err = lo_do_lookup(req, parent, name, &e);
+
417 if (err)
+
418 fuse_reply_err(req, err);
+
419 else
+
420 fuse_reply_entry(req, &e);
+
421}
+
422
+
423static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
424 const char *name, mode_t mode, dev_t rdev,
+
425 const char *link)
+
426{
+
427 int res;
+
428 int saverr;
+
429 struct lo_inode *dir = lo_inode(req, parent);
+
430 struct fuse_entry_param e;
+
431
+
432 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
433
+
434 saverr = errno;
+
435 if (res == -1)
+
436 goto out;
+
437
+
438 saverr = lo_do_lookup(req, parent, name, &e);
+
439 if (saverr)
+
440 goto out;
+
441
+
442 if (lo_debug(req))
+
443 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
444 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
445
+
446 fuse_reply_entry(req, &e);
+
447 return;
+
448
+
449out:
+
450 fuse_reply_err(req, saverr);
+
451}
+
452
+
453static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
454 const char *name, mode_t mode, dev_t rdev)
+
455{
+
456 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
457}
+
458
+
459static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
460 mode_t mode)
+
461{
+
462 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
463}
+
464
+
465static void lo_symlink(fuse_req_t req, const char *link,
+
466 fuse_ino_t parent, const char *name)
+
467{
+
468 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
469}
+
470
+
471static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
472 const char *name)
+
473{
+
474 int res;
+
475 struct lo_data *lo = lo_data(req);
+
476 struct lo_inode *inode = lo_inode(req, ino);
+
477 struct fuse_entry_param e;
+
478 char procname[64];
+
479 int saverr;
+
480
+
481 memset(&e, 0, sizeof(struct fuse_entry_param));
+
482 e.attr_timeout = lo->timeout;
+
483 e.entry_timeout = lo->timeout;
+
484
+
485 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
486 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
487 AT_SYMLINK_FOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
492 if (res == -1)
+
493 goto out_err;
+
494
+
495 pthread_mutex_lock(&lo->mutex);
+
496 inode->refcount++;
+
497 pthread_mutex_unlock(&lo->mutex);
+
498 e.ino = (uintptr_t) inode;
+
499
+
500 if (lo_debug(req))
+
501 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
502 (unsigned long long) parent, name,
+
503 (unsigned long long) e.ino);
+
504
+
505 fuse_reply_entry(req, &e);
+
506 return;
+
507
+
508out_err:
+
509 saverr = errno;
+
510 fuse_reply_err(req, saverr);
+
511}
+
512
+
513static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
514{
+
515 int res;
+
516
+
517 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
518
+
519 fuse_reply_err(req, res == -1 ? errno : 0);
+
520}
+
521
+
522static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
523 fuse_ino_t newparent, const char *newname,
+
524 unsigned int flags)
+
525{
+
526 int res;
+
527
+
528 if (flags) {
+
529 fuse_reply_err(req, EINVAL);
+
530 return;
+
531 }
+
532
+
533 res = renameat(lo_fd(req, parent), name,
+
534 lo_fd(req, newparent), newname);
+
535
+
536 fuse_reply_err(req, res == -1 ? errno : 0);
+
537}
+
538
+
539static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
540{
+
541 int res;
+
542
+
543 res = unlinkat(lo_fd(req, parent), name, 0);
+
544
+
545 fuse_reply_err(req, res == -1 ? errno : 0);
+
546}
+
547
+
548static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
549{
+
550 if (!inode)
+
551 return;
+
552
+
553 pthread_mutex_lock(&lo->mutex);
+
554 assert(inode->refcount >= n);
+
555 inode->refcount -= n;
+
556 if (!inode->refcount) {
+
557 struct lo_inode *prev, *next;
+
558
+
559 prev = inode->prev;
+
560 next = inode->next;
+
561 next->prev = prev;
+
562 prev->next = next;
+
563
+
564 pthread_mutex_unlock(&lo->mutex);
+
565 close(inode->fd);
+
566 free(inode);
+
567
+
568 } else {
+
569 pthread_mutex_unlock(&lo->mutex);
+
570 }
+
571}
+
572
+
573static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
574{
+
575 struct lo_data *lo = lo_data(req);
+
576 struct lo_inode *inode = lo_inode(req, ino);
+
577
+
578 if (lo_debug(req)) {
+
579 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
580 (unsigned long long) ino,
+
581 (unsigned long long) inode->refcount,
+
582 (unsigned long long) nlookup);
+
583 }
+
584
+
585 unref_inode(lo, inode, nlookup);
+
586}
+
587
+
588static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
589{
+
590 lo_forget_one(req, ino, nlookup);
+
591 fuse_reply_none(req);
+
592}
+
593
+
594static void lo_forget_multi(fuse_req_t req, size_t count,
+
595 struct fuse_forget_data *forgets)
+
596{
+
597 int i;
+
598
+
599 for (i = 0; i < count; i++)
+
600 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
601 fuse_reply_none(req);
+
602}
+
603
+
604static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
605{
+
606 char buf[PATH_MAX + 1];
+
607 int res;
+
608
+
609 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
610 if (res == -1)
+
611 return (void) fuse_reply_err(req, errno);
+
612
+
613 if (res == sizeof(buf))
+
614 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
615
+
616 buf[res] = '\0';
+
617
+
618 fuse_reply_readlink(req, buf);
+
619}
+
620
+
621struct lo_dirp {
+
622 DIR *dp;
+
623 struct dirent *entry;
+
624 off_t offset;
+
625};
+
626
+
627static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
628{
+
629 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
630}
+
631
+
632static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
633{
+
634 int error = ENOMEM;
+
635 struct lo_data *lo = lo_data(req);
+
636 struct lo_dirp *d;
+
637 int fd = -1;
+
638
+
639 d = calloc(1, sizeof(struct lo_dirp));
+
640 if (d == NULL)
+
641 goto out_err;
+
642
+
643 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
644 if (fd == -1)
+
645 goto out_errno;
+
646
+
647 d->dp = fdopendir(fd);
+
648 if (d->dp == NULL)
+
649 goto out_errno;
+
650
+
651 d->offset = 0;
+
652 d->entry = NULL;
+
653
+
654 fi->fh = (uintptr_t) d;
+
655 if (lo->cache != CACHE_NEVER)
+
656 fi->cache_readdir = 1;
+
657 if (lo->cache == CACHE_ALWAYS)
+
658 fi->keep_cache = 1;
+
659 fuse_reply_open(req, fi);
+
660 return;
+
661
+
662out_errno:
+
663 error = errno;
+
664out_err:
+
665 if (d) {
+
666 if (fd != -1)
+
667 close(fd);
+
668 free(d);
+
669 }
+
670 fuse_reply_err(req, error);
+
671}
+
672
+
673static int is_dot_or_dotdot(const char *name)
+
674{
+
675 return name[0] == '.' && (name[1] == '\0' ||
+
676 (name[1] == '.' && name[2] == '\0'));
+
677}
+
678
+
679static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
680 off_t offset, struct fuse_file_info *fi, int plus)
+
681{
+
682 struct lo_dirp *d = lo_dirp(fi);
+
683 char *buf;
+
684 char *p;
+
685 size_t rem = size;
+
686 int err;
+
687
+
688 (void) ino;
+
689
+
690 buf = calloc(1, size);
+
691 if (!buf) {
+
692 err = ENOMEM;
+
693 goto error;
+
694 }
+
695 p = buf;
+
696
+
697 if (offset != d->offset) {
+
698 seekdir(d->dp, offset);
+
699 d->entry = NULL;
+
700 d->offset = offset;
+
701 }
+
702 while (1) {
+
703 size_t entsize;
+
704 off_t nextoff;
+
705 const char *name;
+
706
+
707 if (!d->entry) {
+
708 errno = 0;
+
709 d->entry = readdir(d->dp);
+
710 if (!d->entry) {
+
711 if (errno) { // Error
+
712 err = errno;
+
713 goto error;
+
714 } else { // End of stream
+
715 break;
+
716 }
+
717 }
+
718 }
+
719 nextoff = d->entry->d_off;
+
720 name = d->entry->d_name;
+
721 fuse_ino_t entry_ino = 0;
+
722 if (plus) {
+
723 struct fuse_entry_param e;
+
724 if (is_dot_or_dotdot(name)) {
+
725 e = (struct fuse_entry_param) {
+
726 .attr.st_ino = d->entry->d_ino,
+
727 .attr.st_mode = d->entry->d_type << 12,
+
728 };
+
729 } else {
+
730 err = lo_do_lookup(req, ino, name, &e);
+
731 if (err)
+
732 goto error;
+
733 entry_ino = e.ino;
+
734 }
+
735
+
736 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
737 &e, nextoff);
+
738 } else {
+
739 struct stat st = {
+
740 .st_ino = d->entry->d_ino,
+
741 .st_mode = d->entry->d_type << 12,
+
742 };
+
743 entsize = fuse_add_direntry(req, p, rem, name,
+
744 &st, nextoff);
+
745 }
+
746 if (entsize > rem) {
+
747 if (entry_ino != 0)
+
748 lo_forget_one(req, entry_ino, 1);
+
749 break;
+
750 }
+
751
+
752 p += entsize;
+
753 rem -= entsize;
+
754
+
755 d->entry = NULL;
+
756 d->offset = nextoff;
+
757 }
+
758
+
759 err = 0;
+
760error:
+
761 // If there's an error, we can only signal it if we haven't stored
+
762 // any entries yet - otherwise we'd end up with wrong lookup
+
763 // counts for the entries that are already in the buffer. So we
+
764 // return what we've collected until that point.
+
765 if (err && rem == size)
+
766 fuse_reply_err(req, err);
+
767 else
+
768 fuse_reply_buf(req, buf, size - rem);
+
769 free(buf);
+
770}
+
771
+
772static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
773 off_t offset, struct fuse_file_info *fi)
+
774{
+
775 lo_do_readdir(req, ino, size, offset, fi, 0);
+
776}
+
777
+
778static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
779 off_t offset, struct fuse_file_info *fi)
+
780{
+
781 lo_do_readdir(req, ino, size, offset, fi, 1);
+
782}
+
783
+
784static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
785{
+
786 struct lo_dirp *d = lo_dirp(fi);
+
787 (void) ino;
+
788 closedir(d->dp);
+
789 free(d);
+
790 fuse_reply_err(req, 0);
+
791}
+
792
+
793static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
794 mode_t mode, struct fuse_file_info *fi)
+
795{
+
796 int fd;
+
797 struct lo_data *lo = lo_data(req);
+
798 struct fuse_entry_param e;
+
799 int err;
+
800
+
801 if (lo_debug(req))
+
802 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
803 parent);
+
804
+
805 fd = openat(lo_fd(req, parent), ".",
+
806 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
807 if (fd == -1)
+
808 return (void) fuse_reply_err(req, errno);
+
809
+
810 fi->fh = fd;
+
811 if (lo->cache == CACHE_NEVER)
+
812 fi->direct_io = 1;
+
813 else if (lo->cache == CACHE_ALWAYS)
+
814 fi->keep_cache = 1;
+
815
+
816 /* parallel_direct_writes feature depends on direct_io features.
+
817 To make parallel_direct_writes valid, need set fi->direct_io
+
818 in current function. */
+
819 fi->parallel_direct_writes = 1;
+
820
+
821 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
822 if (err)
+
823 fuse_reply_err(req, err);
+
824 else
+
825 fuse_reply_create(req, &e, fi);
+
826}
+
827
+
828static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
829 mode_t mode, struct fuse_file_info *fi)
+
830{
+
831 int fd;
+
832 struct lo_data *lo = lo_data(req);
+
833 struct fuse_entry_param e;
+
834 int err;
+
835
+
836 if (lo_debug(req))
+
837 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
838 parent, name);
+
839
+
840 fd = openat(lo_fd(req, parent), name,
+
841 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
842 if (fd == -1)
+
843 return (void) fuse_reply_err(req, errno);
+
844
+
845 fi->fh = fd;
+
846 if (lo->cache == CACHE_NEVER)
+
847 fi->direct_io = 1;
+
848 else if (lo->cache == CACHE_ALWAYS)
+
849 fi->keep_cache = 1;
+
850
+
851 /* parallel_direct_writes feature depends on direct_io features.
+
852 To make parallel_direct_writes valid, need set fi->direct_io
+
853 in current function. */
+ +
855
+
856 err = lo_do_lookup(req, parent, name, &e);
+
857 if (err)
+
858 fuse_reply_err(req, err);
+
859 else
+
860 fuse_reply_create(req, &e, fi);
+
861}
+
862
+
863static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
864 struct fuse_file_info *fi)
+
865{
+
866 int res;
+
867 int fd = dirfd(lo_dirp(fi)->dp);
+
868 (void) ino;
+
869 if (datasync)
+
870 res = fdatasync(fd);
+
871 else
+
872 res = fsync(fd);
+
873 fuse_reply_err(req, res == -1 ? errno : 0);
+
874}
+
875
+
876static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
877{
+
878 int fd;
+
879 char buf[64];
+
880 struct lo_data *lo = lo_data(req);
+
881
+
882 if (lo_debug(req))
+
883 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
884 ino, fi->flags);
+
885
+
886 /* With writeback cache, kernel may send read requests even
+
887 when userspace opened write-only */
+
888 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
889 fi->flags &= ~O_ACCMODE;
+
890 fi->flags |= O_RDWR;
+
891 }
+
892
+
893 /* With writeback cache, O_APPEND is handled by the kernel.
+
894 This breaks atomicity (since the file may change in the
+
895 underlying filesystem, so that the kernel's idea of the
+
896 end of the file isn't accurate anymore). In this example,
+
897 we just accept that. A more rigorous filesystem may want
+
898 to return an error here */
+
899 if (lo->writeback && (fi->flags & O_APPEND))
+
900 fi->flags &= ~O_APPEND;
+
901
+
902 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
903 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
904 if (fd == -1)
+
905 return (void) fuse_reply_err(req, errno);
+
906
+
907 fi->fh = fd;
+
908 if (lo->cache == CACHE_NEVER)
+
909 fi->direct_io = 1;
+
910 else if (lo->cache == CACHE_ALWAYS)
+
911 fi->keep_cache = 1;
+
912
+
913 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
914 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
915 for writes to the same file in the kernel). */
+
916 if (fi->flags & O_DIRECT)
+
917 fi->direct_io = 1;
+
918
+
919 /* parallel_direct_writes feature depends on direct_io features.
+
920 To make parallel_direct_writes valid, need set fi->direct_io
+
921 in current function. */
+ +
923
+
924 fuse_reply_open(req, fi);
+
925}
+
926
+
927static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
928{
+
929 (void) ino;
+
930
+
931 close(fi->fh);
+
932 fuse_reply_err(req, 0);
+
933}
+
934
+
935static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
936{
+
937 int res;
+
938 (void) ino;
+
939 res = close(dup(fi->fh));
+
940 fuse_reply_err(req, res == -1 ? errno : 0);
+
941}
+
942
+
943static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
944 struct fuse_file_info *fi)
+
945{
+
946 int res;
+
947 (void) ino;
+
948 if (datasync)
+
949 res = fdatasync(fi->fh);
+
950 else
+
951 res = fsync(fi->fh);
+
952 fuse_reply_err(req, res == -1 ? errno : 0);
+
953}
+
954
+
955static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
956 off_t offset, struct fuse_file_info *fi)
+
957{
+
958 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
959
+
960 if (lo_debug(req))
+
961 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
962 "off=%lu)\n", ino, size, (unsigned long) offset);
+
963
+ +
965 buf.buf[0].fd = fi->fh;
+
966 buf.buf[0].pos = offset;
+
967
+ +
969}
+
970
+
971static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
972 struct fuse_bufvec *in_buf, off_t off,
+
973 struct fuse_file_info *fi)
+
974{
+
975 (void) ino;
+
976 ssize_t res;
+
977 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
978
+ +
980 out_buf.buf[0].fd = fi->fh;
+
981 out_buf.buf[0].pos = off;
+
982
+
983 if (lo_debug(req))
+
984 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
985 ino, out_buf.buf[0].size, (unsigned long) off);
+
986
+
987 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
988 if(res < 0)
+
989 fuse_reply_err(req, -res);
+
990 else
+
991 fuse_reply_write(req, (size_t) res);
+
992}
+
993
+
994static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
995{
+
996 int res;
+
997 struct statvfs stbuf;
+
998
+
999 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
1000 if (res == -1)
+
1001 fuse_reply_err(req, errno);
+
1002 else
+
1003 fuse_reply_statfs(req, &stbuf);
+
1004}
+
1005
+
1006static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1007 off_t offset, off_t length, struct fuse_file_info *fi)
+
1008{
+
1009 int err = EOPNOTSUPP;
+
1010 (void) ino;
+
1011
+
1012#ifdef HAVE_FALLOCATE
+
1013 err = fallocate(fi->fh, mode, offset, length);
+
1014 if (err < 0)
+
1015 err = errno;
+
1016
+
1017#elif defined(HAVE_POSIX_FALLOCATE)
+
1018 if (mode) {
+
1019 fuse_reply_err(req, EOPNOTSUPP);
+
1020 return;
+
1021 }
+
1022
+
1023 err = posix_fallocate(fi->fh, offset, length);
+
1024#endif
+
1025
+
1026 fuse_reply_err(req, err);
+
1027}
+
1028
+
1029static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1030 int op)
+
1031{
+
1032 int res;
+
1033 (void) ino;
+
1034
+
1035 res = flock(fi->fh, op);
+
1036
+
1037 fuse_reply_err(req, res == -1 ? errno : 0);
+
1038}
+
1039
+
1040static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1041 size_t size)
+
1042{
+
1043 char *value = NULL;
+
1044 char procname[64];
+
1045 struct lo_inode *inode = lo_inode(req, ino);
+
1046 ssize_t ret;
+
1047 int saverr;
+
1048
+
1049 saverr = ENOSYS;
+
1050 if (!lo_data(req)->xattr)
+
1051 goto out;
+
1052
+
1053 if (lo_debug(req)) {
+
1054 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1055 ino, name, size);
+
1056 }
+
1057
+
1058 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1059
+
1060 if (size) {
+
1061 value = malloc(size);
+
1062 if (!value)
+
1063 goto out_err;
+
1064
+
1065 ret = getxattr(procname, name, value, size);
+
1066 if (ret == -1)
+
1067 goto out_err;
+
1068 saverr = 0;
+
1069 if (ret == 0)
+
1070 goto out;
+
1071
+
1072 fuse_reply_buf(req, value, ret);
+
1073 } else {
+
1074 ret = getxattr(procname, name, NULL, 0);
+
1075 if (ret == -1)
+
1076 goto out_err;
+
1077
+
1078 fuse_reply_xattr(req, ret);
+
1079 }
+
1080out_free:
+
1081 free(value);
+
1082 return;
+
1083
+
1084out_err:
+
1085 saverr = errno;
+
1086out:
+
1087 fuse_reply_err(req, saverr);
+
1088 goto out_free;
+
1089}
+
1090
+
1091static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1092{
+
1093 char *value = NULL;
+
1094 char procname[64];
+
1095 struct lo_inode *inode = lo_inode(req, ino);
+
1096 ssize_t ret;
+
1097 int saverr;
+
1098
+
1099 saverr = ENOSYS;
+
1100 if (!lo_data(req)->xattr)
+
1101 goto out;
+
1102
+
1103 if (lo_debug(req)) {
+
1104 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1105 ino, size);
+
1106 }
+
1107
+
1108 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1109
+
1110 if (size) {
+
1111 value = malloc(size);
+
1112 if (!value)
+
1113 goto out_err;
+
1114
+
1115 ret = listxattr(procname, value, size);
+
1116 if (ret == -1)
+
1117 goto out_err;
+
1118 saverr = 0;
+
1119 if (ret == 0)
+
1120 goto out;
+
1121
+
1122 fuse_reply_buf(req, value, ret);
+
1123 } else {
+
1124 ret = listxattr(procname, NULL, 0);
+
1125 if (ret == -1)
+
1126 goto out_err;
+
1127
+
1128 fuse_reply_xattr(req, ret);
+
1129 }
+
1130out_free:
+
1131 free(value);
+
1132 return;
+
1133
+
1134out_err:
+
1135 saverr = errno;
+
1136out:
+
1137 fuse_reply_err(req, saverr);
+
1138 goto out_free;
+
1139}
+
1140
+
1141static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1142 const char *value, size_t size, int flags)
+
1143{
+
1144 char procname[64];
+
1145 struct lo_inode *inode = lo_inode(req, ino);
+
1146 ssize_t ret;
+
1147 int saverr;
+
1148
+
1149 saverr = ENOSYS;
+
1150 if (!lo_data(req)->xattr)
+
1151 goto out;
+
1152
+
1153 if (lo_debug(req)) {
+
1154 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1155 ino, name, value, size);
+
1156 }
+
1157
+
1158 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1159
+
1160 ret = setxattr(procname, name, value, size, flags);
+
1161 saverr = ret == -1 ? errno : 0;
+
1162
+
1163out:
+
1164 fuse_reply_err(req, saverr);
+
1165}
+
1166
+
1167static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1168{
+
1169 char procname[64];
+
1170 struct lo_inode *inode = lo_inode(req, ino);
+
1171 ssize_t ret;
+
1172 int saverr;
+
1173
+
1174 saverr = ENOSYS;
+
1175 if (!lo_data(req)->xattr)
+
1176 goto out;
+
1177
+
1178 if (lo_debug(req)) {
+
1179 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1180 ino, name);
+
1181 }
+
1182
+
1183 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1184
+
1185 ret = removexattr(procname, name);
+
1186 saverr = ret == -1 ? errno : 0;
+
1187
+
1188out:
+
1189 fuse_reply_err(req, saverr);
+
1190}
+
1191
+
1192#ifdef HAVE_COPY_FILE_RANGE
+
1193static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1194 struct fuse_file_info *fi_in,
+
1195 fuse_ino_t ino_out, off_t off_out,
+
1196 struct fuse_file_info *fi_out, size_t len,
+
1197 int flags)
+
1198{
+
1199 ssize_t res;
+
1200
+
1201 if (lo_debug(req))
+
1202 fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
1203 "off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
1204 "off=%lu, size=%zd, flags=0x%x)\n",
+
1205 ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
1206 len, flags);
+
1207
+
1208 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1209 flags);
+
1210 if (res < 0)
+
1211 fuse_reply_err(req, errno);
+
1212 else
+
1213 fuse_reply_write(req, res);
+
1214}
+
1215#endif
+
1216
+
1217static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 off_t res;
+
1221
+
1222 (void)ino;
+
1223 res = lseek(fi->fh, off, whence);
+
1224 if (res != -1)
+
1225 fuse_reply_lseek(req, res);
+
1226 else
+
1227 fuse_reply_err(req, errno);
+
1228}
+
1229
+
1230static const struct fuse_lowlevel_ops lo_oper = {
+
1231 .init = lo_init,
+
1232 .destroy = lo_destroy,
+
1233 .lookup = lo_lookup,
+
1234 .mkdir = lo_mkdir,
+
1235 .mknod = lo_mknod,
+
1236 .symlink = lo_symlink,
+
1237 .link = lo_link,
+
1238 .unlink = lo_unlink,
+
1239 .rmdir = lo_rmdir,
+
1240 .rename = lo_rename,
+
1241 .forget = lo_forget,
+
1242 .forget_multi = lo_forget_multi,
+
1243 .getattr = lo_getattr,
+
1244 .setattr = lo_setattr,
+
1245 .readlink = lo_readlink,
+
1246 .opendir = lo_opendir,
+
1247 .readdir = lo_readdir,
+
1248 .readdirplus = lo_readdirplus,
+
1249 .releasedir = lo_releasedir,
+
1250 .fsyncdir = lo_fsyncdir,
+
1251 .create = lo_create,
+
1252 .tmpfile = lo_tmpfile,
+
1253 .open = lo_open,
+
1254 .release = lo_release,
+
1255 .flush = lo_flush,
+
1256 .fsync = lo_fsync,
+
1257 .read = lo_read,
+
1258 .write_buf = lo_write_buf,
+
1259 .statfs = lo_statfs,
+
1260 .fallocate = lo_fallocate,
+
1261 .flock = lo_flock,
+
1262 .getxattr = lo_getxattr,
+
1263 .listxattr = lo_listxattr,
+
1264 .setxattr = lo_setxattr,
+
1265 .removexattr = lo_removexattr,
+
1266#ifdef HAVE_COPY_FILE_RANGE
+
1267 .copy_file_range = lo_copy_file_range,
+
1268#endif
+
1269 .lseek = lo_lseek,
+
1270};
+
1271
+
1272int main(int argc, char *argv[])
+
1273{
+
1274 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1275 struct fuse_session *se;
+
1276 struct fuse_cmdline_opts opts;
+
1277 struct fuse_loop_config *config;
+
1278 struct lo_data lo = { .debug = 0,
+
1279 .writeback = 0 };
+
1280 int ret = -1;
+
1281
+
1282 /* Don't mask creation mode, kernel already did that */
+
1283 umask(0);
+
1284
+
1285 pthread_mutex_init(&lo.mutex, NULL);
+
1286 lo.root.next = lo.root.prev = &lo.root;
+
1287 lo.root.fd = -1;
+
1288 lo.cache = CACHE_NORMAL;
+
1289
+
1290 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1291 return 1;
+
1292 if (opts.show_help) {
+
1293 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1296 passthrough_ll_help();
+
1297 ret = 0;
+
1298 goto err_out1;
+
1299 } else if (opts.show_version) {
+
1300 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1302 ret = 0;
+
1303 goto err_out1;
+
1304 }
+
1305
+
1306 if(opts.mountpoint == NULL) {
+
1307 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1308 printf(" %s --help\n", argv[0]);
+
1309 ret = 1;
+
1310 goto err_out1;
+
1311 }
+
1312
+
1313 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1314 return 1;
+
1315
+
1316 lo.debug = opts.debug;
+
1317 lo.root.refcount = 2;
+
1318 if (lo.source) {
+
1319 struct stat stat;
+
1320 int res;
+
1321
+
1322 res = lstat(lo.source, &stat);
+
1323 if (res == -1) {
+
1324 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1325 lo.source);
+
1326 exit(1);
+
1327 }
+
1328 if (!S_ISDIR(stat.st_mode)) {
+
1329 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1330 exit(1);
+
1331 }
+
1332
+
1333 } else {
+
1334 lo.source = strdup("/");
+
1335 if(!lo.source) {
+
1336 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1337 exit(1);
+
1338 }
+
1339 }
+
1340 if (!lo.timeout_set) {
+
1341 switch (lo.cache) {
+
1342 case CACHE_NEVER:
+
1343 lo.timeout = 0.0;
+
1344 break;
+
1345
+
1346 case CACHE_NORMAL:
+
1347 lo.timeout = 1.0;
+
1348 break;
+
1349
+
1350 case CACHE_ALWAYS:
+
1351 lo.timeout = 86400.0;
+
1352 break;
+
1353 }
+
1354 } else if (lo.timeout < 0) {
+
1355 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1356 lo.timeout);
+
1357 exit(1);
+
1358 }
+
1359
+
1360 lo.root.fd = open(lo.source, O_PATH);
+
1361 if (lo.root.fd == -1) {
+
1362 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1363 lo.source);
+
1364 exit(1);
+
1365 }
+
1366
+
1367 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1368 if (se == NULL)
+
1369 goto err_out1;
+
1370
+
1371 if (fuse_set_signal_handlers(se) != 0)
+
1372 goto err_out2;
+
1373
+
1374 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1375 goto err_out3;
+
1376
+
1377 fuse_daemonize(opts.foreground);
+
1378
+
1379 /* Block until ctrl+c or fusermount -u */
+
1380 if (opts.singlethread)
+
1381 ret = fuse_session_loop(se);
+
1382 else {
+
1383 config = fuse_loop_cfg_create();
+
1384 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1385 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1386 ret = fuse_session_loop_mt(se, config);
+
1387 fuse_loop_cfg_destroy(config);
+
1388 config = NULL;
+
1389 }
+
1390
+ +
1392err_out3:
+ +
1394err_out2:
+ +
1396err_out1:
+
1397 free(opts.mountpoint);
+
1398 fuse_opt_free_args(&args);
+
1399
+
1400 if (lo.root.fd >= 0)
+
1401 close(lo.root.fd);
+
1402
+
1403 free(lo.source);
+
1404 return ret ? 1 : 0;
+
1405}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
@ FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_CAP_FLOCK_LOCKS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2poll_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2poll_8c.html new file mode 100644 index 0000000..8d4c046 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2poll_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2poll_8c_source.html new file mode 100644 index 0000000..2f6fe9c --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c.html new file mode 100644 index 0000000..642bcf8 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c_source.html new file mode 100644 index 0000000..bcf05ac --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2printcap_8c.html b/doc/html/fuse-3_817_81-rc1_2example_2printcap_8c.html new file mode 100644 index 0000000..0a3c5bb --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2printcap_8c.html @@ -0,0 +1,239 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
@ FUSE_CAP_POSIX_ACL
+
@ FUSE_CAP_READDIRPLUS
+
@ FUSE_CAP_NO_OPENDIR_SUPPORT
+
@ FUSE_CAP_PARALLEL_DIROPS
+
@ FUSE_CAP_ASYNC_DIO
+
@ FUSE_CAP_NO_EXPORT_SUPPORT
+
@ FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_CAP_IOCTL_DIR
+
@ FUSE_CAP_AUTO_INVAL_DATA
+
@ FUSE_CAP_SPLICE_READ
+
@ FUSE_CAP_SPLICE_MOVE
+
@ FUSE_CAP_POSIX_LOCKS
+
@ FUSE_CAP_HANDLE_KILLPRIV_V2
+
@ FUSE_CAP_HANDLE_KILLPRIV
+
@ FUSE_CAP_DONT_MASK
+
@ FUSE_CAP_ATOMIC_O_TRUNC
+
@ FUSE_CAP_SPLICE_WRITE
+
@ FUSE_CAP_PASSTHROUGH
+
@ FUSE_CAP_FLOCK_LOCKS
+
@ FUSE_CAP_EXPIRE_ONLY
+
@ FUSE_CAP_EXPORT_SUPPORT
+
@ FUSE_CAP_READDIRPLUS_AUTO
+
@ FUSE_CAP_NO_OPEN_SUPPORT
+
@ FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
@ FUSE_CAP_SETXATTR_EXT
+
@ FUSE_CAP_ASYNC_READ
+
@ FUSE_CAP_CACHE_SYMLINKS
+
@ FUSE_CAP_EXPLICIT_INVAL_DATA
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2example_2printcap_8c_source.html b/doc/html/fuse-3_817_81-rc1_2example_2printcap_8c_source.html new file mode 100644 index 0000000..65c3ea9 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2example_2printcap_8c_source.html @@ -0,0 +1,230 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
@ FUSE_CAP_POSIX_ACL
+
@ FUSE_CAP_READDIRPLUS
+
@ FUSE_CAP_NO_OPENDIR_SUPPORT
+
@ FUSE_CAP_PARALLEL_DIROPS
+
@ FUSE_CAP_ASYNC_DIO
+
@ FUSE_CAP_NO_EXPORT_SUPPORT
+
@ FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_CAP_IOCTL_DIR
+
@ FUSE_CAP_AUTO_INVAL_DATA
+
@ FUSE_CAP_SPLICE_READ
+
@ FUSE_CAP_SPLICE_MOVE
+
@ FUSE_CAP_POSIX_LOCKS
+
@ FUSE_CAP_HANDLE_KILLPRIV_V2
+
@ FUSE_CAP_HANDLE_KILLPRIV
+
@ FUSE_CAP_DONT_MASK
+
@ FUSE_CAP_ATOMIC_O_TRUNC
+
@ FUSE_CAP_SPLICE_WRITE
+
@ FUSE_CAP_PASSTHROUGH
+
@ FUSE_CAP_FLOCK_LOCKS
+
@ FUSE_CAP_EXPIRE_ONLY
+
@ FUSE_CAP_EXPORT_SUPPORT
+
@ FUSE_CAP_READDIRPLUS_AUTO
+
@ FUSE_CAP_NO_OPEN_SUPPORT
+
@ FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
@ FUSE_CAP_SETXATTR_EXT
+
@ FUSE_CAP_ASYNC_READ
+
@ FUSE_CAP_CACHE_SYMLINKS
+
@ FUSE_CAP_EXPLICIT_INVAL_DATA
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2cuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..a70425b --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse_8h.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse_8h.html new file mode 100644 index 0000000..35f2875 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse_8h.html @@ -0,0 +1,864 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1398 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 87 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1369 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: all file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+
+ +

Definition at line 58 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 42 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4433 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5153 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4639 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4854 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4644 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4520 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4654 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4663 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4673 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4744 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4577 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+

Main function of FUSE.

+

This is for the lazy. This is all that has to be called from the main() function.

+

This function does the following:

    +
  • parses command line options, and handles –help and –version
  • +
  • installs signal handlers for INT, HUP, TERM and PIPE
  • +
  • registers an exit handler to unmount the filesystem on program exit
  • +
  • creates a fuse handle
  • +
  • registers the operations
  • +
  • calls either the single-threaded or the multi-threaded event loop
  • +
+

Most file systems will have to parse some file-system specific arguments before calling this function. It is recommended to do this with fuse_opt_parse() and a processing function that passes through any unknown options (this can also be achieved by just passing NULL as the processing function). That way, the remaining options can be passed directly to fuse_main().

+

fuse_main() accepts all options that can be passed to fuse_parse_cmdline(), fuse_new(), or fuse_session_new().

+

Option parsing skips argv[0], which is assumed to contain the program name. This element must always be present and is used to construct a basic usage: message for the –help output. argv[0] may also be set to the empty string. In this case the usage message is suppressed. This can be used by file systems to print their own usage line first. See hello.c for an example of how to do this.

+

Note: this is currently implemented as a macro.

+

The following error codes may be returned from fuse_main(): 1: Invalid option arguments 2: No mount point specified 3: FUSE setup failed 4: Mounting failed 5: Failed to daemonize (detach from session) 6: Failed to set up signal handlers 7: An error occurred during the life of the file system

+
Parameters
+ + + + + +
argcthe argument counter passed to the main() function
argvthe argument vector passed to the main() function
opthe file system operation
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
0 on success, nonzero on failure
+

Example usage, see hello.c

+ +

Definition at line 311 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5204 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 479 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4904 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4912 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5209 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse_8h_source.html new file mode 100644 index 0000000..776d135 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse_8h_source.html @@ -0,0 +1,643 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
37struct fuse;
+
38
+
+ + +
52 FUSE_READDIR_PLUS = (1 << 0)
+
53};
+
+
54
+
+ + +
69 FUSE_FILL_DIR_PLUS = (1 << 1)
+
70};
+
+
71
+
87typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
88 const struct stat *stbuf, off_t off,
+
89 enum fuse_fill_dir_flags flags);
+
101struct fuse_config {
+
106 int32_t set_gid;
+
107 uint32_t gid;
+
108
+
113 int32_t set_uid;
+
114 uint32_t uid;
+
115
+
120 int32_t set_mode;
+
121 uint32_t umask;
+
122
+
127 double entry_timeout;
+
128
+
137 double negative_timeout;
+
138
+
143 double attr_timeout;
+
144
+
148 int32_t intr;
+
149
+
155 int32_t intr_signal;
+
156
+
167 int32_t remember;
+
168
+
185 int32_t hard_remove;
+
186
+
198 int32_t use_ino;
+
199
+
207 int32_t readdir_ino;
+
208
+
226 int32_t direct_io;
+
227
+
245 int32_t kernel_cache;
+
246
+
253 int32_t auto_cache;
+
254
+
255 /*
+
256 * The timeout in seconds for which file attributes are cached
+
257 * for the purpose of checking if auto_cache should flush the
+
258 * file data on open.
+
259 */
+
260 int32_t ac_attr_timeout_set;
+
261 double ac_attr_timeout;
+
262
+
273 int32_t nullpath_ok;
+
274
+
279 int32_t show_help;
+
280 char *modules;
+
281 int32_t debug;
+
282
+
288 uint32_t fmask;
+
289 uint32_t dmask;
+
290
+
297 int32_t no_rofd_flush;
+
298
+ +
313
+
314
+
318 uint32_t flags;
+
319
+
323 uint64_t reserved[48];
+
324};
+
325
+
326
+
349struct fuse_operations {
+
361 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
362
+
371 int (*readlink) (const char *, char *, size_t);
+
372
+
379 int (*mknod) (const char *, mode_t, dev_t);
+
380
+
387 int (*mkdir) (const char *, mode_t);
+
388
+
390 int (*unlink) (const char *);
+
391
+
393 int (*rmdir) (const char *);
+
394
+
396 int (*symlink) (const char *, const char *);
+
397
+
407 int (*rename) (const char *, const char *, unsigned int flags);
+
408
+
410 int (*link) (const char *, const char *);
+
411
+
417 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
418
+
427 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
428
+
437 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
438
+
486 int (*open) (const char *, struct fuse_file_info *);
+
487
+
497 int (*read) (const char *, char *, size_t, off_t,
+
498 struct fuse_file_info *);
+
499
+
509 int (*write) (const char *, const char *, size_t, off_t,
+
510 struct fuse_file_info *);
+
511
+
516 int (*statfs) (const char *, struct statvfs *);
+
517
+
546 int (*flush) (const char *, struct fuse_file_info *);
+
547
+
560 int (*release) (const char *, struct fuse_file_info *);
+
561
+
567 int (*fsync) (const char *, int, struct fuse_file_info *);
+
568
+
570 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
571
+
573 int (*getxattr) (const char *, const char *, char *, size_t);
+
574
+
576 int (*listxattr) (const char *, char *, size_t);
+
577
+
579 int (*removexattr) (const char *, const char *);
+
580
+
589 int (*opendir) (const char *, struct fuse_file_info *);
+
590
+
613 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
614 struct fuse_file_info *, enum fuse_readdir_flags);
+
615
+
621 int (*releasedir) (const char *, struct fuse_file_info *);
+
622
+
631 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
632
+
641 void *(*init) (struct fuse_conn_info *conn,
+
642 struct fuse_config *cfg);
+
643
+
649 void (*destroy) (void *private_data);
+
650
+
660 int (*access) (const char *, int);
+
661
+
672 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
673
+
704 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
705 struct flock *);
+
706
+
719 int (*utimens) (const char *, const struct timespec tv[2],
+
720 struct fuse_file_info *fi);
+
721
+
728 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
729
+
730#if FUSE_USE_VERSION < 35
+
731 int (*ioctl) (const char *, int cmd, void *arg,
+
732 struct fuse_file_info *, unsigned int flags, void *data);
+
733#else
+
750 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
751 struct fuse_file_info *, unsigned int flags, void *data);
+
752#endif
+
753
+
769 int (*poll) (const char *, struct fuse_file_info *,
+
770 struct fuse_pollhandle *ph, unsigned *reventsp);
+
771
+
781 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
782 struct fuse_file_info *);
+
783
+
798 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
799 size_t size, off_t off, struct fuse_file_info *);
+
818 int (*flock) (const char *, struct fuse_file_info *, int op);
+
819
+
828 int (*fallocate) (const char *, int, off_t, off_t,
+
829 struct fuse_file_info *);
+
830
+
843 ssize_t (*copy_file_range) (const char *path_in,
+
844 struct fuse_file_info *fi_in,
+
845 off_t offset_in, const char *path_out,
+
846 struct fuse_file_info *fi_out,
+
847 off_t offset_out, size_t size, int flags);
+
848
+
852 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
853};
+
854
+
860struct fuse_context {
+
862 struct fuse *fuse;
+
863
+
865 uid_t uid;
+
866
+
868 gid_t gid;
+
869
+
871 pid_t pid;
+
872
+
874 void *private_data;
+
875
+
877 mode_t umask;
+
878};
+
879
+
885int fuse_main_real_versioned(int argc, char *argv[],
+
886 const struct fuse_operations *op, size_t op_size,
+
887 struct libfuse_version *version, void *user_data);
+
888static inline int fuse_main_real(int argc, char *argv[],
+
889 const struct fuse_operations *op,
+
890 size_t op_size, void *user_data)
+
891{
+
892 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
893 .minor = FUSE_MINOR_VERSION,
+
894 .hotfix = FUSE_HOTFIX_VERSION,
+
895 .padding = 0 };
+
896
+
897 fuse_log(FUSE_LOG_ERR,
+
898 "%s is a libfuse internal function, please use fuse_main()\n",
+
899 __func__);
+
900
+
901 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
902 user_data);
+
903}
+
904
+
959int fuse_main_real_versioned(int argc, char *argv[],
+
960 const struct fuse_operations *op, size_t op_size,
+
961 struct libfuse_version *version, void *user_data);
+
962static inline int fuse_main_fn(int argc, char *argv[],
+
963 const struct fuse_operations *op,
+
964 void *user_data)
+
965{
+
966 struct libfuse_version version = {
+
967 .major = FUSE_MAJOR_VERSION,
+
968 .minor = FUSE_MINOR_VERSION,
+
969 .hotfix = FUSE_HOTFIX_VERSION,
+
970 .padding = 0
+
971 };
+
972
+
973 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
974 user_data);
+
975}
+
976#define fuse_main(argc, argv, op, user_data) \
+
977 fuse_main_fn(argc, argv, op, user_data)
+
978
+
979/* ----------------------------------------------------------- *
+
980 * More detailed API *
+
981 * ----------------------------------------------------------- */
+
982
+
994void fuse_lib_help(struct fuse_args *args);
+
995
+
996/* Do not call this directly, use fuse_new() instead */
+
997struct fuse *_fuse_new_30(struct fuse_args *args,
+
998 const struct fuse_operations *op, size_t op_size,
+
999 struct libfuse_version *version, void *user_data);
+
1000struct fuse *_fuse_new_31(struct fuse_args *args,
+
1001 const struct fuse_operations *op, size_t op_size,
+
1002 struct libfuse_version *version, void *user_data);
+
1003
+
1031#if FUSE_USE_VERSION == 30
+
1032static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1033 const struct fuse_operations *op,
+
1034 size_t op_size, void *user_data)
+
1035{
+
1036 struct libfuse_version version = {
+
1037 .major = FUSE_MAJOR_VERSION,
+
1038 .minor = FUSE_MINOR_VERSION,
+
1039 .hotfix = FUSE_HOTFIX_VERSION,
+
1040 .padding = 0
+
1041 };
+
1042
+
1043 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1044}
+
1045#else /* FUSE_USE_VERSION */
+
1046static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1047 const struct fuse_operations *op,
+
1048 size_t op_size, void *user_data)
+
1049{
+
1050 struct libfuse_version version = {
+
1051 .major = FUSE_MAJOR_VERSION,
+
1052 .minor = FUSE_MINOR_VERSION,
+
1053 .hotfix = FUSE_HOTFIX_VERSION,
+
1054 .padding = 0
+
1055 };
+
1056
+
1057 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1058}
+
1059#endif
+
1060#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1061
+
1070int fuse_mount(struct fuse *f, const char *mountpoint);
+
1071
+
1079void fuse_unmount(struct fuse *f);
+
1080
+
1089void fuse_destroy(struct fuse *f);
+
1090
+
1106int fuse_loop(struct fuse *f);
+
1107
+
1116void fuse_exit(struct fuse *f);
+
1117
+
1118#if FUSE_USE_VERSION < 32
+
1119int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1120#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1121#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1122int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1123#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1124#else
+
1156#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1157int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1158#else
+
1159#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1160#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1161#endif
+
1162
+
1163
+
1172struct fuse_context *fuse_get_context(void);
+
1173
+
1192int fuse_getgroups(int size, gid_t list[]);
+
1193
+
1199int fuse_interrupted(void);
+
1200
+
1212int fuse_invalidate_path(struct fuse *f, const char *path);
+
1213
+ +
1222
+
1229void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1230
+
1240int fuse_clean_cache(struct fuse *fuse);
+
1241
+
1242/*
+
1243 * Stacking API
+
1244 */
+
1245
+
1251struct fuse_fs;
+
1252
+
1253/*
+
1254 * These functions call the relevant filesystem operation, and return
+
1255 * the result.
+
1256 *
+
1257 * If the operation is not defined, they return -ENOSYS, with the
+
1258 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1259 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1260 */
+
1261
+
1262int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1263 struct fuse_file_info *fi);
+
1264int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1265 const char *newpath, unsigned int flags);
+
1266int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1267int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1268int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1269 const char *path);
+
1270int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1271int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1272 struct fuse_file_info *fi);
+
1273int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1274 struct fuse_file_info *fi);
+
1275int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1276 off_t off, struct fuse_file_info *fi);
+
1277int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1278 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1279 struct fuse_file_info *fi);
+
1280int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1281 size_t size, off_t off, struct fuse_file_info *fi);
+
1282int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1283 struct fuse_bufvec *buf, off_t off,
+
1284 struct fuse_file_info *fi);
+
1285int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1286 struct fuse_file_info *fi);
+
1287int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1290int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1291 struct fuse_file_info *fi);
+
1292int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1293 fuse_fill_dir_t filler, off_t off,
+
1294 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1295int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1296 struct fuse_file_info *fi);
+
1297int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1298 struct fuse_file_info *fi);
+
1299int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1300 struct fuse_file_info *fi);
+
1301int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1302 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1303int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1304 struct fuse_file_info *fi, int op);
+
1305int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1306 struct fuse_file_info *fi);
+
1307int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1308 struct fuse_file_info *fi);
+
1309int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1310 struct fuse_file_info *fi);
+
1311int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1312 const struct timespec tv[2], struct fuse_file_info *fi);
+
1313int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1314int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1315 size_t len);
+
1316int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1317 dev_t rdev);
+
1318int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1319int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1320 const char *value, size_t size, int flags);
+
1321int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1322 char *value, size_t size);
+
1323int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1324 size_t size);
+
1325int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1326 const char *name);
+
1327int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1328 uint64_t *idx);
+
1329#if FUSE_USE_VERSION < 35
+
1330int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1331 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1332 void *data);
+
1333#else
+
1334int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1335 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1336 void *data);
+
1337#endif
+
1338int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1339 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1340 unsigned *reventsp);
+
1341int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1342 off_t offset, off_t length, struct fuse_file_info *fi);
+
1343ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1344 struct fuse_file_info *fi_in, off_t off_in,
+
1345 const char *path_out,
+
1346 struct fuse_file_info *fi_out, off_t off_out,
+
1347 size_t len, int flags);
+
1348off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1349 struct fuse_file_info *fi);
+
1350void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1351 struct fuse_config *cfg);
+
1352void fuse_fs_destroy(struct fuse_fs *fs);
+
1353
+
1354int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1355
+
1369struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1370 void *private_data);
+
1371
+
1386typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1387 struct fuse_fs *fs[]);
+
+
1398#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1399 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1400
+
1402struct fuse_session *fuse_get_session(struct fuse *f);
+
1403
+
1412int fuse_open_channel(const char *mountpoint, const char *options);
+
1413
+
1414#ifdef __cplusplus
+
1415}
+
1416#endif
+
1417
+
1418#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4904
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:479
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4912
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h.html new file mode 100644 index 0000000..f93e73b --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h.html @@ -0,0 +1,723 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include <stdbool.h>
+#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + +

+Macros

#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + + + +

+Enumerations

enum  fuse_capability {
+  FUSE_CAP_ASYNC_READ = (1 << 0) +, FUSE_CAP_POSIX_LOCKS = (1 << 1) +, FUSE_CAP_ATOMIC_O_TRUNC = (1 << 3) +, FUSE_CAP_EXPORT_SUPPORT = (1 << 4) +,
+  FUSE_CAP_DONT_MASK = (1 << 6) +, FUSE_CAP_SPLICE_WRITE = (1 << 7) +, FUSE_CAP_SPLICE_MOVE = (1 << 8) +, FUSE_CAP_SPLICE_READ = (1 << 9) +,
+  FUSE_CAP_FLOCK_LOCKS = (1 << 10) +, FUSE_CAP_IOCTL_DIR = (1 << 11) +, FUSE_CAP_AUTO_INVAL_DATA = (1 << 12) +, FUSE_CAP_READDIRPLUS = (1 << 13) +,
+  FUSE_CAP_READDIRPLUS_AUTO = (1 << 14) +, FUSE_CAP_ASYNC_DIO = (1 << 15) +, FUSE_CAP_WRITEBACK_CACHE = (1 << 16) +, FUSE_CAP_NO_OPEN_SUPPORT = (1 << 17) +,
+  FUSE_CAP_PARALLEL_DIROPS = (1 << 18) +, FUSE_CAP_POSIX_ACL = (1 << 19) +, FUSE_CAP_HANDLE_KILLPRIV = (1 << 20) +, FUSE_CAP_HANDLE_KILLPRIV_V2 = (1 << 21) +,
+  FUSE_CAP_CACHE_SYMLINKS = (1 << 23) +, FUSE_CAP_NO_OPENDIR_SUPPORT = (1 << 24) +, FUSE_CAP_EXPLICIT_INVAL_DATA = (1 << 25) +, FUSE_CAP_EXPIRE_ONLY = (1 << 26) +,
+  FUSE_CAP_SETXATTR_EXT = (1 << 27) +, FUSE_CAP_DIRECT_IO_ALLOW_MMAP = (1 << 28) +, FUSE_CAP_PASSTHROUGH = (1 << 29) +, FUSE_CAP_NO_EXPORT_SUPPORT = (1 << 30) +,
+  FUSE_CAP_CURRENT_MAX +
+ }
 
enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 682 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 537 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 848 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 817 of file fuse_common.h.

+ +
+
+ +

◆ fuse_capability

+ +
+
+ + + + +
enum fuse_capability
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Enumerator
FUSE_CAP_ASYNC_READ 

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_POSIX_LOCKS 

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+
FUSE_CAP_ATOMIC_O_TRUNC 

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_EXPORT_SUPPORT 

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+
FUSE_CAP_DONT_MASK 

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+
FUSE_CAP_SPLICE_WRITE 

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+
FUSE_CAP_SPLICE_MOVE 

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+
FUSE_CAP_SPLICE_READ 

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+
FUSE_CAP_FLOCK_LOCKS 

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+
FUSE_CAP_IOCTL_DIR 

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_AUTO_INVAL_DATA 

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_READDIRPLUS 

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+
FUSE_CAP_READDIRPLUS_AUTO 

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+
FUSE_CAP_ASYNC_DIO 

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+
FUSE_CAP_WRITEBACK_CACHE 

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+
FUSE_CAP_NO_OPEN_SUPPORT 

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+
FUSE_CAP_PARALLEL_DIROPS 

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+
FUSE_CAP_POSIX_ACL 

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+
FUSE_CAP_HANDLE_KILLPRIV 

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+
FUSE_CAP_HANDLE_KILLPRIV_V2 

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+
FUSE_CAP_CACHE_SYMLINKS 

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+
FUSE_CAP_NO_OPENDIR_SUPPORT 

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+
FUSE_CAP_EXPLICIT_INVAL_DATA 

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+
FUSE_CAP_EXPIRE_ONLY 

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+
FUSE_CAP_SETXATTR_EXT 

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+
FUSE_CAP_DIRECT_IO_ALLOW_MMAP 

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+
FUSE_CAP_PASSTHROUGH 

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+
FUSE_CAP_NO_EXPORT_SUPPORT 

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+
FUSE_CAP_CURRENT_MAX 

Current maximum capability value.

+
+ +

Definition at line 177 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 416 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 463 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5218 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1906 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 180 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 158 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 138 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5213 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h_source.html new file mode 100644 index 0000000..8e3ab4d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__common_8h_source.html @@ -0,0 +1,519 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file COPYING.LIB.
+
6*/
+
7
+
10#include <stdbool.h>
+
11#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
12#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
13#endif
+
14
+
15#ifndef FUSE_COMMON_H_
+
16#define FUSE_COMMON_H_
+
17
+
18#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
19#include "fuse_config.h"
+
20#endif
+
21
+
22#include "libfuse_config.h"
+
23
+
24#include "fuse_opt.h"
+
25#include "fuse_log.h"
+
26#include <stdint.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#if (defined(__cplusplus) && __cplusplus >= 201103L) || \
+
34 (!defined(__cplusplus) && defined(__STDC_VERSION__) && \
+
35 __STDC_VERSION__ >= 201112L)
+
36#define fuse_static_assert(condition, message) static_assert(condition, message)
+
37#else
+
38#define fuse_static_assert(condition, message)
+
39#endif
+
40
+
41#ifdef __cplusplus
+
42extern "C" {
+
43#endif
+
44
+
58struct fuse_file_info {
+
60 int32_t flags;
+
61
+
68 uint32_t writepage : 1;
+
69
+
71 uint32_t direct_io : 1;
+
72
+
77 uint32_t keep_cache : 1;
+
78
+
82 uint32_t flush : 1;
+
83
+
86 uint32_t nonseekable : 1;
+
87
+
88 /* Indicates that flock locks for this file should be
+
89 released. If set, lock_owner shall contain a valid value.
+
90 May only be set in ->release(). */
+
91 uint32_t flock_release : 1;
+
92
+
97 uint32_t cache_readdir : 1;
+
98
+
101 uint32_t noflush : 1;
+
102
+
105 uint32_t parallel_direct_writes : 1;
+
106
+
108 uint32_t padding : 23;
+
109 uint32_t padding2 : 32;
+
110 uint32_t padding3 : 32;
+
111
+
115 uint64_t fh;
+
116
+
118 uint64_t lock_owner;
+
119
+
122 uint32_t poll_events;
+
123
+
127 int32_t backing_id;
+
128
+
130 uint64_t compat_flags;
+
131
+
132 uint64_t reserved[2];
+
133};
+
134fuse_static_assert(sizeof(struct fuse_file_info) == 64,
+
135 "fuse_file_info size mismatch");
+
136
+
147#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
148struct fuse_loop_config_v1; /* forward declaration */
+
149struct fuse_loop_config {
+
150#else
+
151struct fuse_loop_config_v1 {
+
152#endif
+
157 int clone_fd;
+
158
+
169 unsigned int max_idle_threads;
+
170};
+
171
+
172
+
173/**************************************************************************
+
174 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
175 **************************************************************************/
+
176
+
+ + +
189
+ +
197
+ +
206
+ +
218
+ +
226
+ +
234
+ +
242
+ +
251
+ +
264
+ +
271
+ +
293
+ +
301
+ +
329
+ +
340
+ +
349
+ +
364
+ +
372
+ +
391
+ +
400
+ +
417
+ +
430
+ +
445
+ +
468
+ +
484
+ +
491
+ +
500
+ +
512
+ +
520
+ + +
+
526
+
537#define FUSE_IOCTL_COMPAT (1 << 0)
+
538#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
539#define FUSE_IOCTL_RETRY (1 << 2)
+
540#define FUSE_IOCTL_DIR (1 << 4)
+
541
+
542#define FUSE_IOCTL_MAX_IOV 256
+
543
+
555struct fuse_conn_info {
+
559 uint32_t proto_major;
+
560
+
564 uint32_t proto_minor;
+
565
+
569 uint32_t max_write;
+
570
+
583 uint32_t max_read;
+
584
+
588 uint32_t max_readahead;
+
589
+
595 uint32_t capable;
+
596
+
607 uint32_t want;
+
608
+
637 uint32_t max_background;
+
638
+
647 uint32_t congestion_threshold;
+
648
+
664 uint32_t time_gran;
+
665
+
682#define FUSE_BACKING_STACKED_UNDER (0)
+
683#define FUSE_BACKING_STACKED_OVER (1)
+
684 uint32_t max_backing_stack_depth;
+
685
+
694 uint32_t no_interrupt : 1;
+
695
+
696 /* reserved bits for future use */
+
697 uint32_t padding : 31;
+
698
+
703 uint64_t capable_ext;
+
704
+
713 uint64_t want_ext;
+
714
+
718 uint32_t reserved[16];
+
719};
+
720fuse_static_assert(sizeof(struct fuse_conn_info) == 128,
+
721 "Size of struct fuse_conn_info must be 128 bytes");
+
722
+
723struct fuse_session;
+
724struct fuse_pollhandle;
+
725struct fuse_conn_info_opts;
+
726
+
769struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
770
+
778void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
779 struct fuse_conn_info *conn);
+
780
+
787int fuse_daemonize(int foreground);
+
788
+
794int fuse_version(void);
+
795
+
801const char *fuse_pkgversion(void);
+
802
+
808void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
809
+
810/* ----------------------------------------------------------- *
+
811 * Data buffer *
+
812 * ----------------------------------------------------------- */
+
813
+
+ +
824 FUSE_BUF_IS_FD = (1 << 1),
+
825
+ +
834
+
842 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
844
+
+ + +
859
+ +
867
+ +
876
+ + +
+
886
+
893struct fuse_buf {
+
897 size_t size;
+
898
+
902 enum fuse_buf_flags flags;
+
903
+
909 void *mem;
+
910
+
916 int fd;
+
917
+
923 off_t pos;
+
924
+
931 size_t mem_size;
+
932};
+
933
+
942struct fuse_bufvec {
+
946 size_t count;
+
947
+
951 size_t idx;
+
952
+
956 size_t off;
+
957
+
961 struct fuse_buf buf[1];
+
962};
+
963
+
968struct libfuse_version
+
969{
+
970 uint32_t major;
+
971 uint32_t minor;
+
972 uint32_t hotfix;
+
973 uint32_t padding;
+
974};
+
975
+
976/* Initialize bufvec with a single buffer of given size */
+
977#define FUSE_BUFVEC_INIT(size__) \
+
978 ((struct fuse_bufvec) { \
+
979 /* .count= */ 1, \
+
980 /* .idx = */ 0, \
+
981 /* .off = */ 0, \
+
982 /* .buf = */ { /* [0] = */ { \
+
983 /* .size = */ (size__), \
+
984 /* .flags = */ (enum fuse_buf_flags) 0, \
+
985 /* .mem = */ NULL, \
+
986 /* .fd = */ -1, \
+
987 /* .pos = */ 0, \
+
988 /* .mem_size = */ 0, \
+
989 } } \
+
990 } )
+
991
+
998size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
999
+
1008ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1009 enum fuse_buf_copy_flags flags);
+
1010
+
1011/* ----------------------------------------------------------- *
+
1012 * Signal handling *
+
1013 * ----------------------------------------------------------- */
+
1014
+
1030int fuse_set_signal_handlers(struct fuse_session *se);
+
1031
+
1047int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1048
+
1060void fuse_remove_signal_handlers(struct fuse_session *se);
+
1061
+
1067#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1073struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1074
+
1078void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1079
+
1083void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1084 unsigned int value);
+
1085
+
1089void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1090 unsigned int value);
+
1091
+
1095void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1096 unsigned int value);
+
1097
+
1104void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1105 struct fuse_loop_config_v1 *v1_conf);
+
1106#endif
+
1107
+
1108
+
1109static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
1110 uint64_t flag)
+
1111{
+
1112 if (conn->capable_ext & flag) {
+
1113 conn->want_ext |= flag;
+
1114 return true;
+
1115 }
+
1116 return false;
+
1117}
+
1118
+
1119static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
1120 uint64_t flag)
+
1121{
+
1122 conn->want_ext &= ~flag;
+
1123}
+
1124
+
1125static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
1126 uint64_t flag)
+
1127{
+
1128 return conn->capable_ext & flag ? true : false;
+
1129}
+
1130
+
1131/* ----------------------------------------------------------- *
+
1132 * Compatibility stuff *
+
1133 * ----------------------------------------------------------- */
+
1134
+
1135#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1136# error only API version 30 or greater is supported
+
1137#endif
+
1138
+
1139#ifdef __cplusplus
+
1140}
+
1141#endif
+
1142
+
1143
+
1144/*
+
1145 * This interface uses 64 bit off_t.
+
1146 *
+
1147 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1148 */
+
1149
+
1150#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1151_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1152#else
+
1153struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1154 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1155#endif
+
1156
+
1157#endif /* FUSE_COMMON_H_ */
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
fuse_capability
+
@ FUSE_CAP_CURRENT_MAX
+
@ FUSE_CAP_POSIX_ACL
+
@ FUSE_CAP_READDIRPLUS
+
@ FUSE_CAP_NO_OPENDIR_SUPPORT
+
@ FUSE_CAP_PARALLEL_DIROPS
+
@ FUSE_CAP_ASYNC_DIO
+
@ FUSE_CAP_NO_EXPORT_SUPPORT
+
@ FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_CAP_IOCTL_DIR
+
@ FUSE_CAP_AUTO_INVAL_DATA
+
@ FUSE_CAP_SPLICE_READ
+
@ FUSE_CAP_SPLICE_MOVE
+
@ FUSE_CAP_POSIX_LOCKS
+
@ FUSE_CAP_HANDLE_KILLPRIV_V2
+
@ FUSE_CAP_HANDLE_KILLPRIV
+
@ FUSE_CAP_DONT_MASK
+
@ FUSE_CAP_ATOMIC_O_TRUNC
+
@ FUSE_CAP_SPLICE_WRITE
+
@ FUSE_CAP_PASSTHROUGH
+
@ FUSE_CAP_FLOCK_LOCKS
+
@ FUSE_CAP_EXPIRE_ONLY
+
@ FUSE_CAP_EXPORT_SUPPORT
+
@ FUSE_CAP_READDIRPLUS_AUTO
+
@ FUSE_CAP_NO_OPEN_SUPPORT
+
@ FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
@ FUSE_CAP_SETXATTR_EXT
+
@ FUSE_CAP_ASYNC_READ
+
@ FUSE_CAP_CACHE_SYMLINKS
+
@ FUSE_CAP_EXPLICIT_INVAL_DATA
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:416
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:463
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5213
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ + + + + + +
uint64_t capable_ext
+
uint64_t want_ext
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t padding
+
uint32_t noflush
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__kernel_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..9ceaab2 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__kernel_8h_source.html @@ -0,0 +1,1093 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 */
+
221
+
222#ifndef _LINUX_FUSE_H
+
223#define _LINUX_FUSE_H
+
224
+
225#ifdef __KERNEL__
+
226#include <linux/types.h>
+
227#else
+
228#include <stdint.h>
+
229#endif
+
230
+
231/*
+
232 * Version negotiation:
+
233 *
+
234 * Both the kernel and userspace send the version they support in the
+
235 * INIT request and reply respectively.
+
236 *
+
237 * If the major versions match then both shall use the smallest
+
238 * of the two minor versions for communication.
+
239 *
+
240 * If the kernel supports a larger major version, then userspace shall
+
241 * reply with the major version it supports, ignore the rest of the
+
242 * INIT message and expect a new INIT message from the kernel with a
+
243 * matching major version.
+
244 *
+
245 * If the library supports a larger major version, then it shall fall
+
246 * back to the major protocol version sent by the kernel for
+
247 * communication and reply with that major version (and an arbitrary
+
248 * supported minor version).
+
249 */
+
250
+
252#define FUSE_KERNEL_VERSION 7
+
253
+
255#define FUSE_KERNEL_MINOR_VERSION 40
+
256
+
258#define FUSE_ROOT_ID 1
+
259
+
260/* Make sure all structures are padded to 64bit boundary, so 32bit
+
261 userspace works under 64bit kernels */
+
262
+
263struct fuse_attr {
+
264 uint64_t ino;
+
265 uint64_t size;
+
266 uint64_t blocks;
+
267 uint64_t atime;
+
268 uint64_t mtime;
+
269 uint64_t ctime;
+
270 uint32_t atimensec;
+
271 uint32_t mtimensec;
+
272 uint32_t ctimensec;
+
273 uint32_t mode;
+
274 uint32_t nlink;
+
275 uint32_t uid;
+
276 uint32_t gid;
+
277 uint32_t rdev;
+
278 uint32_t blksize;
+
279 uint32_t flags;
+
280};
+
281
+
282/*
+
283 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
284 * Linux.
+
285 */
+
286struct fuse_sx_time {
+
287 int64_t tv_sec;
+
288 uint32_t tv_nsec;
+
289 int32_t __reserved;
+
290};
+
291
+
292struct fuse_statx {
+
293 uint32_t mask;
+
294 uint32_t blksize;
+
295 uint64_t attributes;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint16_t mode;
+
300 uint16_t __spare0[1];
+
301 uint64_t ino;
+
302 uint64_t size;
+
303 uint64_t blocks;
+
304 uint64_t attributes_mask;
+
305 struct fuse_sx_time atime;
+
306 struct fuse_sx_time btime;
+
307 struct fuse_sx_time ctime;
+
308 struct fuse_sx_time mtime;
+
309 uint32_t rdev_major;
+
310 uint32_t rdev_minor;
+
311 uint32_t dev_major;
+
312 uint32_t dev_minor;
+
313 uint64_t __spare2[14];
+
314};
+
315
+
316struct fuse_kstatfs {
+
317 uint64_t blocks;
+
318 uint64_t bfree;
+
319 uint64_t bavail;
+
320 uint64_t files;
+
321 uint64_t ffree;
+
322 uint32_t bsize;
+
323 uint32_t namelen;
+
324 uint32_t frsize;
+
325 uint32_t padding;
+
326 uint32_t spare[6];
+
327};
+
328
+
329struct fuse_file_lock {
+
330 uint64_t start;
+
331 uint64_t end;
+
332 uint32_t type;
+
333 uint32_t pid; /* tgid */
+
334};
+
335
+
339#define FATTR_MODE (1 << 0)
+
340#define FATTR_UID (1 << 1)
+
341#define FATTR_GID (1 << 2)
+
342#define FATTR_SIZE (1 << 3)
+
343#define FATTR_ATIME (1 << 4)
+
344#define FATTR_MTIME (1 << 5)
+
345#define FATTR_FH (1 << 6)
+
346#define FATTR_ATIME_NOW (1 << 7)
+
347#define FATTR_MTIME_NOW (1 << 8)
+
348#define FATTR_LOCKOWNER (1 << 9)
+
349#define FATTR_CTIME (1 << 10)
+
350#define FATTR_KILL_SUIDGID (1 << 11)
+
351
+
364#define FOPEN_DIRECT_IO (1 << 0)
+
365#define FOPEN_KEEP_CACHE (1 << 1)
+
366#define FOPEN_NONSEEKABLE (1 << 2)
+
367#define FOPEN_CACHE_DIR (1 << 3)
+
368#define FOPEN_STREAM (1 << 4)
+
369#define FOPEN_NOFLUSH (1 << 5)
+
370#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
371#define FOPEN_PASSTHROUGH (1 << 7)
+
372
+
425#define FUSE_ASYNC_READ (1 << 0)
+
426#define FUSE_POSIX_LOCKS (1 << 1)
+
427#define FUSE_FILE_OPS (1 << 2)
+
428#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
429#define FUSE_EXPORT_SUPPORT (1 << 4)
+
430#define FUSE_BIG_WRITES (1 << 5)
+
431#define FUSE_DONT_MASK (1 << 6)
+
432#define FUSE_SPLICE_WRITE (1 << 7)
+
433#define FUSE_SPLICE_MOVE (1 << 8)
+
434#define FUSE_SPLICE_READ (1 << 9)
+
435#define FUSE_FLOCK_LOCKS (1 << 10)
+
436#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
437#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
438#define FUSE_DO_READDIRPLUS (1 << 13)
+
439#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
440#define FUSE_ASYNC_DIO (1 << 15)
+
441#define FUSE_WRITEBACK_CACHE (1 << 16)
+
442#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
443#define FUSE_PARALLEL_DIROPS (1 << 18)
+
444#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
445#define FUSE_POSIX_ACL (1 << 20)
+
446#define FUSE_ABORT_ERROR (1 << 21)
+
447#define FUSE_MAX_PAGES (1 << 22)
+
448#define FUSE_CACHE_SYMLINKS (1 << 23)
+
449#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
450#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
451#define FUSE_MAP_ALIGNMENT (1 << 26)
+
452#define FUSE_SUBMOUNTS (1 << 27)
+
453#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
454#define FUSE_SETXATTR_EXT (1 << 29)
+
455#define FUSE_INIT_EXT (1 << 30)
+
456#define FUSE_INIT_RESERVED (1 << 31)
+
457/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
458#define FUSE_SECURITY_CTX (1ULL << 32)
+
459#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
460#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
461#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
462#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
463#define FUSE_PASSTHROUGH (1ULL << 37)
+
464#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
465#define FUSE_HAS_RESEND (1ULL << 39)
+
466
+
467/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
468#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
469
+
475#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
476
+
480#define FUSE_RELEASE_FLUSH (1 << 0)
+
481#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
482
+
486#define FUSE_GETATTR_FH (1 << 0)
+
487
+
491#define FUSE_LK_FLOCK (1 << 0)
+
492
+
500#define FUSE_WRITE_CACHE (1 << 0)
+
501#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
502#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
503
+
504/* Obsolete alias; this flag implies killing suid/sgid only. */
+
505#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
506
+
510#define FUSE_READ_LOCKOWNER (1 << 1)
+
511
+
524#define FUSE_IOCTL_COMPAT (1 << 0)
+
525#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
526#define FUSE_IOCTL_RETRY (1 << 2)
+
527#define FUSE_IOCTL_32BIT (1 << 3)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
530
+
531#define FUSE_IOCTL_MAX_IOV 256
+
532
+
538#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
539
+
545#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
546
+
553#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
554#define FUSE_ATTR_DAX (1 << 1)
+
555
+
560#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
561
+
566#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
567
+
572#define FUSE_EXPIRE_ONLY (1 << 0)
+
573
+
579enum fuse_ext_type {
+
580 /* Types 0..31 are reserved for fuse_secctx_header */
+
581 FUSE_MAX_NR_SECCTX = 31,
+
582 FUSE_EXT_GROUPS = 32,
+
583};
+
584
+
585enum fuse_opcode {
+
586 FUSE_LOOKUP = 1,
+
587 FUSE_FORGET = 2, /* no reply */
+
588 FUSE_GETATTR = 3,
+
589 FUSE_SETATTR = 4,
+
590 FUSE_READLINK = 5,
+
591 FUSE_SYMLINK = 6,
+
592 FUSE_MKNOD = 8,
+
593 FUSE_MKDIR = 9,
+
594 FUSE_UNLINK = 10,
+
595 FUSE_RMDIR = 11,
+
596 FUSE_RENAME = 12,
+
597 FUSE_LINK = 13,
+
598 FUSE_OPEN = 14,
+
599 FUSE_READ = 15,
+
600 FUSE_WRITE = 16,
+
601 FUSE_STATFS = 17,
+
602 FUSE_RELEASE = 18,
+
603 FUSE_FSYNC = 20,
+
604 FUSE_SETXATTR = 21,
+
605 FUSE_GETXATTR = 22,
+
606 FUSE_LISTXATTR = 23,
+
607 FUSE_REMOVEXATTR = 24,
+
608 FUSE_FLUSH = 25,
+
609 FUSE_INIT = 26,
+
610 FUSE_OPENDIR = 27,
+
611 FUSE_READDIR = 28,
+
612 FUSE_RELEASEDIR = 29,
+
613 FUSE_FSYNCDIR = 30,
+
614 FUSE_GETLK = 31,
+
615 FUSE_SETLK = 32,
+
616 FUSE_SETLKW = 33,
+
617 FUSE_ACCESS = 34,
+
618 FUSE_CREATE = 35,
+
619 FUSE_INTERRUPT = 36,
+
620 FUSE_BMAP = 37,
+
621 FUSE_DESTROY = 38,
+
622 FUSE_IOCTL = 39,
+
623 FUSE_POLL = 40,
+
624 FUSE_NOTIFY_REPLY = 41,
+
625 FUSE_BATCH_FORGET = 42,
+
626 FUSE_FALLOCATE = 43,
+
627 FUSE_READDIRPLUS = 44,
+
628 FUSE_RENAME2 = 45,
+
629 FUSE_LSEEK = 46,
+
630 FUSE_COPY_FILE_RANGE = 47,
+
631 FUSE_SETUPMAPPING = 48,
+
632 FUSE_REMOVEMAPPING = 49,
+
633 FUSE_SYNCFS = 50,
+
634 FUSE_TMPFILE = 51,
+
635 FUSE_STATX = 52,
+
636
+
637 /* CUSE specific operations */
+
638 CUSE_INIT = 4096,
+
639
+
640 /* Reserved opcodes: helpful to detect structure endian-ness */
+
641 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
642 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
643};
+
644
+
645enum fuse_notify_code {
+
646 FUSE_NOTIFY_POLL = 1,
+
647 FUSE_NOTIFY_INVAL_INODE = 2,
+
648 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
649 FUSE_NOTIFY_STORE = 4,
+
650 FUSE_NOTIFY_RETRIEVE = 5,
+
651 FUSE_NOTIFY_DELETE = 6,
+
652 FUSE_NOTIFY_RESEND = 7,
+
653 FUSE_NOTIFY_CODE_MAX,
+
654};
+
655
+
656/* The read buffer is required to be at least 8k, but may be much larger */
+
657#define FUSE_MIN_READ_BUFFER 8192
+
658
+
659#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
660
+
661struct fuse_entry_out {
+
662 uint64_t nodeid; /* Inode ID */
+
663 uint64_t generation; /* Inode generation: nodeid:gen must
+
664 be unique for the fs's lifetime */
+
665 uint64_t entry_valid; /* Cache timeout for the name */
+
666 uint64_t attr_valid; /* Cache timeout for the attributes */
+
667 uint32_t entry_valid_nsec;
+
668 uint32_t attr_valid_nsec;
+
669 struct fuse_attr attr;
+
670};
+
671
+
672struct fuse_forget_in {
+
673 uint64_t nlookup;
+
674};
+
675
+
676struct fuse_forget_one {
+
677 uint64_t nodeid;
+
678 uint64_t nlookup;
+
679};
+
680
+
681struct fuse_batch_forget_in {
+
682 uint32_t count;
+
683 uint32_t dummy;
+
684};
+
685
+
686struct fuse_getattr_in {
+
687 uint32_t getattr_flags;
+
688 uint32_t dummy;
+
689 uint64_t fh;
+
690};
+
691
+
692#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
693
+
694struct fuse_attr_out {
+
695 uint64_t attr_valid; /* Cache timeout for the attributes */
+
696 uint32_t attr_valid_nsec;
+
697 uint32_t dummy;
+
698 struct fuse_attr attr;
+
699};
+
700
+
701struct fuse_statx_in {
+
702 uint32_t getattr_flags;
+
703 uint32_t reserved;
+
704 uint64_t fh;
+
705 uint32_t sx_flags;
+
706 uint32_t sx_mask;
+
707};
+
708
+
709struct fuse_statx_out {
+
710 uint64_t attr_valid; /* Cache timeout for the attributes */
+
711 uint32_t attr_valid_nsec;
+
712 uint32_t flags;
+
713 uint64_t spare[2];
+
714 struct fuse_statx stat;
+
715};
+
716
+
717#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
718
+
719struct fuse_mknod_in {
+
720 uint32_t mode;
+
721 uint32_t rdev;
+
722 uint32_t umask;
+
723 uint32_t padding;
+
724};
+
725
+
726struct fuse_mkdir_in {
+
727 uint32_t mode;
+
728 uint32_t umask;
+
729};
+
730
+
731struct fuse_rename_in {
+
732 uint64_t newdir;
+
733};
+
734
+
735struct fuse_rename2_in {
+
736 uint64_t newdir;
+
737 uint32_t flags;
+
738 uint32_t padding;
+
739};
+
740
+
741struct fuse_link_in {
+
742 uint64_t oldnodeid;
+
743};
+
744
+
745struct fuse_setattr_in {
+
746 uint32_t valid;
+
747 uint32_t padding;
+
748 uint64_t fh;
+
749 uint64_t size;
+
750 uint64_t lock_owner;
+
751 uint64_t atime;
+
752 uint64_t mtime;
+
753 uint64_t ctime;
+
754 uint32_t atimensec;
+
755 uint32_t mtimensec;
+
756 uint32_t ctimensec;
+
757 uint32_t mode;
+
758 uint32_t unused4;
+
759 uint32_t uid;
+
760 uint32_t gid;
+
761 uint32_t unused5;
+
762};
+
763
+
764struct fuse_open_in {
+
765 uint32_t flags;
+
766 uint32_t open_flags; /* FUSE_OPEN_... */
+
767};
+
768
+
769struct fuse_create_in {
+
770 uint32_t flags;
+
771 uint32_t mode;
+
772 uint32_t umask;
+
773 uint32_t open_flags; /* FUSE_OPEN_... */
+
774};
+
775
+
776struct fuse_open_out {
+
777 uint64_t fh;
+
778 uint32_t open_flags;
+
779 int32_t backing_id;
+
780};
+
781
+
782struct fuse_release_in {
+
783 uint64_t fh;
+
784 uint32_t flags;
+
785 uint32_t release_flags;
+
786 uint64_t lock_owner;
+
787};
+
788
+
789struct fuse_flush_in {
+
790 uint64_t fh;
+
791 uint32_t unused;
+
792 uint32_t padding;
+
793 uint64_t lock_owner;
+
794};
+
795
+
796struct fuse_read_in {
+
797 uint64_t fh;
+
798 uint64_t offset;
+
799 uint32_t size;
+
800 uint32_t read_flags;
+
801 uint64_t lock_owner;
+
802 uint32_t flags;
+
803 uint32_t padding;
+
804};
+
805
+
806#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
807
+
808struct fuse_write_in {
+
809 uint64_t fh;
+
810 uint64_t offset;
+
811 uint32_t size;
+
812 uint32_t write_flags;
+
813 uint64_t lock_owner;
+
814 uint32_t flags;
+
815 uint32_t padding;
+
816};
+
817
+
818struct fuse_write_out {
+
819 uint32_t size;
+
820 uint32_t padding;
+
821};
+
822
+
823#define FUSE_COMPAT_STATFS_SIZE 48
+
824
+
825struct fuse_statfs_out {
+
826 struct fuse_kstatfs st;
+
827};
+
828
+
829struct fuse_fsync_in {
+
830 uint64_t fh;
+
831 uint32_t fsync_flags;
+
832 uint32_t padding;
+
833};
+
834
+
835#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
836
+
837struct fuse_setxattr_in {
+
838 uint32_t size;
+
839 uint32_t flags;
+
840 uint32_t setxattr_flags;
+
841 uint32_t padding;
+
842};
+
843
+
844struct fuse_getxattr_in {
+
845 uint32_t size;
+
846 uint32_t padding;
+
847};
+
848
+
849struct fuse_getxattr_out {
+
850 uint32_t size;
+
851 uint32_t padding;
+
852};
+
853
+
854struct fuse_lk_in {
+
855 uint64_t fh;
+
856 uint64_t owner;
+
857 struct fuse_file_lock lk;
+
858 uint32_t lk_flags;
+
859 uint32_t padding;
+
860};
+
861
+
862struct fuse_lk_out {
+
863 struct fuse_file_lock lk;
+
864};
+
865
+
866struct fuse_access_in {
+
867 uint32_t mask;
+
868 uint32_t padding;
+
869};
+
870
+
871struct fuse_init_in {
+
872 uint32_t major;
+
873 uint32_t minor;
+
874 uint32_t max_readahead;
+
875 uint32_t flags;
+
876 uint32_t flags2;
+
877 uint32_t unused[11];
+
878};
+
879
+
880#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
881#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
882
+
883struct fuse_init_out {
+
884 uint32_t major;
+
885 uint32_t minor;
+
886 uint32_t max_readahead;
+
887 uint32_t flags;
+
888 uint16_t max_background;
+
889 uint16_t congestion_threshold;
+
890 uint32_t max_write;
+
891 uint32_t time_gran;
+
892 uint16_t max_pages;
+
893 uint16_t map_alignment;
+
894 uint32_t flags2;
+
895 uint32_t max_stack_depth;
+
896 uint32_t unused[6];
+
897};
+
898
+
899#define CUSE_INIT_INFO_MAX 4096
+
900
+
901struct cuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t unused;
+
905 uint32_t flags;
+
906};
+
907
+
908struct cuse_init_out {
+
909 uint32_t major;
+
910 uint32_t minor;
+
911 uint32_t unused;
+
912 uint32_t flags;
+
913 uint32_t max_read;
+
914 uint32_t max_write;
+
915 uint32_t dev_major; /* chardev major */
+
916 uint32_t dev_minor; /* chardev minor */
+
917 uint32_t spare[10];
+
918};
+
919
+
920struct fuse_interrupt_in {
+
921 uint64_t unique;
+
922};
+
923
+
924struct fuse_bmap_in {
+
925 uint64_t block;
+
926 uint32_t blocksize;
+
927 uint32_t padding;
+
928};
+
929
+
930struct fuse_bmap_out {
+
931 uint64_t block;
+
932};
+
933
+
934struct fuse_ioctl_in {
+
935 uint64_t fh;
+
936 uint32_t flags;
+
937 uint32_t cmd;
+
938 uint64_t arg;
+
939 uint32_t in_size;
+
940 uint32_t out_size;
+
941};
+
942
+
943struct fuse_ioctl_iovec {
+
944 uint64_t base;
+
945 uint64_t len;
+
946};
+
947
+
948struct fuse_ioctl_out {
+
949 int32_t result;
+
950 uint32_t flags;
+
951 uint32_t in_iovs;
+
952 uint32_t out_iovs;
+
953};
+
954
+
955struct fuse_poll_in {
+
956 uint64_t fh;
+
957 uint64_t kh;
+
958 uint32_t flags;
+
959 uint32_t events;
+
960};
+
961
+
962struct fuse_poll_out {
+
963 uint32_t revents;
+
964 uint32_t padding;
+
965};
+
966
+
967struct fuse_notify_poll_wakeup_out {
+
968 uint64_t kh;
+
969};
+
970
+
971struct fuse_fallocate_in {
+
972 uint64_t fh;
+
973 uint64_t offset;
+
974 uint64_t length;
+
975 uint32_t mode;
+
976 uint32_t padding;
+
977};
+
978
+
985#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
986
+
987struct fuse_in_header {
+
988 uint32_t len;
+
989 uint32_t opcode;
+
990 uint64_t unique;
+
991 uint64_t nodeid;
+
992 uint32_t uid;
+
993 uint32_t gid;
+
994 uint32_t pid;
+
995 uint16_t total_extlen; /* length of extensions in 8byte units */
+
996 uint16_t padding;
+
997};
+
998
+
999struct fuse_out_header {
+
1000 uint32_t len;
+
1001 int32_t error;
+
1002 uint64_t unique;
+
1003};
+
1004
+
1005struct fuse_dirent {
+
1006 uint64_t ino;
+
1007 uint64_t off;
+
1008 uint32_t namelen;
+
1009 uint32_t type;
+
1010 char name[];
+
1011};
+
1012
+
1013/* Align variable length records to 64bit boundary */
+
1014#define FUSE_REC_ALIGN(x) \
+
1015 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1016
+
1017#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1018#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1019#define FUSE_DIRENT_SIZE(d) \
+
1020 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1021
+
1022struct fuse_direntplus {
+
1023 struct fuse_entry_out entry_out;
+
1024 struct fuse_dirent dirent;
+
1025};
+
1026
+
1027#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1028 offsetof(struct fuse_direntplus, dirent.name)
+
1029#define FUSE_DIRENTPLUS_SIZE(d) \
+
1030 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1031
+
1032struct fuse_notify_inval_inode_out {
+
1033 uint64_t ino;
+
1034 int64_t off;
+
1035 int64_t len;
+
1036};
+
1037
+
1038struct fuse_notify_inval_entry_out {
+
1039 uint64_t parent;
+
1040 uint32_t namelen;
+
1041 uint32_t flags;
+
1042};
+
1043
+
1044struct fuse_notify_delete_out {
+
1045 uint64_t parent;
+
1046 uint64_t child;
+
1047 uint32_t namelen;
+
1048 uint32_t padding;
+
1049};
+
1050
+
1051struct fuse_notify_store_out {
+
1052 uint64_t nodeid;
+
1053 uint64_t offset;
+
1054 uint32_t size;
+
1055 uint32_t padding;
+
1056};
+
1057
+
1058struct fuse_notify_retrieve_out {
+
1059 uint64_t notify_unique;
+
1060 uint64_t nodeid;
+
1061 uint64_t offset;
+
1062 uint32_t size;
+
1063 uint32_t padding;
+
1064};
+
1065
+
1066/* Matches the size of fuse_write_in */
+
1067struct fuse_notify_retrieve_in {
+
1068 uint64_t dummy1;
+
1069 uint64_t offset;
+
1070 uint32_t size;
+
1071 uint32_t dummy2;
+
1072 uint64_t dummy3;
+
1073 uint64_t dummy4;
+
1074};
+
1075
+
1076struct fuse_backing_map {
+
1077 int32_t fd;
+
1078 uint32_t flags;
+
1079 uint64_t padding;
+
1080};
+
1081
+
1082/* Device ioctls: */
+
1083#define FUSE_DEV_IOC_MAGIC 229
+
1084#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1085#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1086 struct fuse_backing_map)
+
1087#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1088
+
1089struct fuse_lseek_in {
+
1090 uint64_t fh;
+
1091 uint64_t offset;
+
1092 uint32_t whence;
+
1093 uint32_t padding;
+
1094};
+
1095
+
1096struct fuse_lseek_out {
+
1097 uint64_t offset;
+
1098};
+
1099
+
1100struct fuse_copy_file_range_in {
+
1101 uint64_t fh_in;
+
1102 uint64_t off_in;
+
1103 uint64_t nodeid_out;
+
1104 uint64_t fh_out;
+
1105 uint64_t off_out;
+
1106 uint64_t len;
+
1107 uint64_t flags;
+
1108};
+
1109
+
1110#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1111#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1112struct fuse_setupmapping_in {
+
1113 /* An already open handle */
+
1114 uint64_t fh;
+
1115 /* Offset into the file to start the mapping */
+
1116 uint64_t foffset;
+
1117 /* Length of mapping required */
+
1118 uint64_t len;
+
1119 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1120 uint64_t flags;
+
1121 /* Offset in Memory Window */
+
1122 uint64_t moffset;
+
1123};
+
1124
+
1125struct fuse_removemapping_in {
+
1126 /* number of fuse_removemapping_one follows */
+
1127 uint32_t count;
+
1128};
+
1129
+
1130struct fuse_removemapping_one {
+
1131 /* Offset into the dax window start the unmapping */
+
1132 uint64_t moffset;
+
1133 /* Length of mapping required */
+
1134 uint64_t len;
+
1135};
+
1136
+
1137#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1138 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1139
+
1140struct fuse_syncfs_in {
+
1141 uint64_t padding;
+
1142};
+
1143
+
1144/*
+
1145 * For each security context, send fuse_secctx with size of security context
+
1146 * fuse_secctx will be followed by security context name and this in turn
+
1147 * will be followed by actual context label.
+
1148 * fuse_secctx, name, context
+
1149 */
+
1150struct fuse_secctx {
+
1151 uint32_t size;
+
1152 uint32_t padding;
+
1153};
+
1154
+
1155/*
+
1156 * Contains the information about how many fuse_secctx structures are being
+
1157 * sent and what's the total size of all security contexts (including
+
1158 * size of fuse_secctx_header).
+
1159 *
+
1160 */
+
1161struct fuse_secctx_header {
+
1162 uint32_t size;
+
1163 uint32_t nr_secctx;
+
1164};
+
1165
+
1174struct fuse_ext_header {
+
1175 uint32_t size;
+
1176 uint32_t type;
+
1177};
+
1178
+
1184struct fuse_supp_groups {
+
1185 uint32_t nr_groups;
+
1186 uint32_t groups[];
+
1187};
+
1188
+
1189#endif /* _LINUX_FUSE_H */
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h.html new file mode 100644 index 0000000..69a9be5 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...)
 
void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 77 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h_source.html new file mode 100644 index 0000000..ff3b3b1 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__log_8h_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...);
+
77
+
83void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
84
+
88void fuse_log_close_syslog(void);
+
89
+
90#ifdef __cplusplus
+
91}
+
92#endif
+
93
+
94#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..7caa7d9 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h.html @@ -0,0 +1,2373 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1948 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 148 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 289 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 379 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 2957 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2519 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2506 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2500 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2432 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2415 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2625 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2545 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2950 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+

Setup passthrough backing file for open reply

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 483 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 463 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 976 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 527 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 447 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 915 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 431 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 334 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1074 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1095 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1004 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 268 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 959 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1129 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 339 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 508 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1119 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 478 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 937 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 517 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 949 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2675 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3553 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2680 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2693 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2670 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 2967 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3474 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3419 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2787 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3234 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3479 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..3e15b10 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,673 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
57struct fuse_session;
+
58
+
60struct fuse_entry_param {
+ +
69
+
80 uint64_t generation;
+
81
+
89 struct stat attr;
+
90
+
95 double attr_timeout;
+
96
+
101 double entry_timeout;
+
102};
+
103
+
112struct fuse_ctx {
+
114 uid_t uid;
+
115
+
117 gid_t gid;
+
118
+
120 pid_t pid;
+
121
+
123 mode_t umask;
+
124};
+
125
+
126struct fuse_forget_data {
+
127 fuse_ino_t ino;
+
128 uint64_t nlookup;
+
129};
+
130
+
131struct fuse_custom_io {
+
132 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
133 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
134 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
135 off_t *offout, size_t len,
+
136 unsigned int flags, void *userdata);
+
137 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 int (*clone_fd)(int master_fd);
+
141};
+
142
+
+ +
149 FUSE_LL_INVALIDATE = 0,
+
150 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
151};
+
+
152
+
153/* 'to_set' flags in setattr */
+
154#define FUSE_SET_ATTR_MODE (1 << 0)
+
155#define FUSE_SET_ATTR_UID (1 << 1)
+
156#define FUSE_SET_ATTR_GID (1 << 2)
+
157#define FUSE_SET_ATTR_SIZE (1 << 3)
+
158#define FUSE_SET_ATTR_ATIME (1 << 4)
+
159#define FUSE_SET_ATTR_MTIME (1 << 5)
+
160#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
161#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
162#define FUSE_SET_ATTR_FORCE (1 << 9)
+
163#define FUSE_SET_ATTR_CTIME (1 << 10)
+
164#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
165#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
166#define FUSE_SET_ATTR_FILE (1 << 13)
+
167#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
168#define FUSE_SET_ATTR_OPEN (1 << 15)
+
169#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
170#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
171
+
172/* ----------------------------------------------------------- *
+
173 * Request methods and replies *
+
174 * ----------------------------------------------------------- */
+
175
+
206struct fuse_lowlevel_ops {
+
223 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
224
+
236 void (*destroy) (void *userdata);
+
237
+
249 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
250
+
287 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
288
+
308 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
309 struct fuse_file_info *fi);
+
310
+
345 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
346 int to_set, struct fuse_file_info *fi);
+
347
+
358 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
359
+
376 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
377 mode_t mode, dev_t rdev);
+
378
+
391 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
392 mode_t mode);
+
393
+
409 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
410
+
426 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
427
+
440 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
441 const char *name);
+
442
+
472 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
473 fuse_ino_t newparent, const char *newname,
+
474 unsigned int flags);
+
475
+
488 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
489 const char *newname);
+
490
+
554 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
555 struct fuse_file_info *fi);
+
556
+
582 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
583 struct fuse_file_info *fi);
+
584
+
611 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
612 size_t size, off_t off, struct fuse_file_info *fi);
+
613
+
652 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
653 struct fuse_file_info *fi);
+
654
+
680 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
681 struct fuse_file_info *fi);
+
682
+
702 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
703 struct fuse_file_info *fi);
+
704
+
734 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
735 struct fuse_file_info *fi);
+
736
+
780 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
781 struct fuse_file_info *fi);
+
782
+
799 void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+
800 struct fuse_file_info *fi);
+
801
+
824 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
825 struct fuse_file_info *fi);
+
826
+
837 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
838
+
850 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
851 const char *value, size_t size, int flags);
+
852
+
881 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
882 size_t size);
+
883
+
912 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
913
+
929 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
930
+
951 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
952
+
980 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
981 mode_t mode, struct fuse_file_info *fi);
+
982
+
995 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
996 struct fuse_file_info *fi, struct flock *lock);
+
997
+
1020 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1021 struct fuse_file_info *fi,
+
1022 struct flock *lock, int sleep);
+
1023
+
1044 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1045 uint64_t idx);
+
1046
+
1047#if FUSE_USE_VERSION < 35
+
1048 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1049 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1050 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1051#else
+
1080 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1081 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1082 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1083#endif
+
1084
+
1117 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1118 struct fuse_pollhandle *ph);
+
1119
+
1147 void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+
1148 struct fuse_bufvec *bufv, off_t off,
+
1149 struct fuse_file_info *fi);
+
1150
+
1163 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1164 off_t offset, struct fuse_bufvec *bufv);
+
1165
+
1177 void (*forget_multi) (fuse_req_t req, size_t count,
+
1178 struct fuse_forget_data *forgets);
+
1179
+
1195 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1196 struct fuse_file_info *fi, int op);
+
1197
+
1218 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1219 off_t offset, off_t length, struct fuse_file_info *fi);
+
1220
+
1246 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1247 struct fuse_file_info *fi);
+
1248
+
1279 void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in,
+
1280 off_t off_in, struct fuse_file_info *fi_in,
+
1281 fuse_ino_t ino_out, off_t off_out,
+
1282 struct fuse_file_info *fi_out, size_t len,
+
1283 int flags);
+
1284
+
1303 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1304 struct fuse_file_info *fi);
+
1305
+
1306
+
1325 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1326 mode_t mode, struct fuse_file_info *fi);
+
1327
+
1328};
+
1329
+
1351int fuse_reply_err(fuse_req_t req, int err);
+
1352
+
1363void fuse_reply_none(fuse_req_t req);
+
1364
+
1378int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1379
+
1398int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1399 const struct fuse_file_info *fi);
+
1400
+
1412int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1413 double attr_timeout);
+
1414
+
1425int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1426
+
1439int fuse_passthrough_open(fuse_req_t req, int fd);
+
1440int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1441
+
1456int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1457
+
1468int fuse_reply_write(fuse_req_t req, size_t count);
+
1469
+
1481int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1482
+
1526int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1528
+
1540int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1541
+
1552int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1553
+
1564int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1565
+
1576int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1577
+
1588int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1589
+
1590/* ----------------------------------------------------------- *
+
1591 * Filling a buffer in readdir *
+
1592 * ----------------------------------------------------------- */
+
1593
+
1621size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1622 const char *name, const struct stat *stbuf,
+
1623 off_t off);
+
1624
+
1638size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1639 const char *name,
+
1640 const struct fuse_entry_param *e, off_t off);
+
1641
+ +
1658 const struct iovec *in_iov, size_t in_count,
+
1659 const struct iovec *out_iov, size_t out_count);
+
1660
+
1672int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1673
+
1685int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1686 int count);
+
1687
+
1694int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1695
+
1706int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1707
+
1708/* ----------------------------------------------------------- *
+
1709 * Notification *
+
1710 * ----------------------------------------------------------- */
+
1711
+
1719int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1720
+
1744int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1745 off_t off, off_t len);
+
1746
+
1771int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1772 const char *name, size_t namelen);
+
1773
+
1802int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1803 const char *name, size_t namelen);
+
1804
+
1833int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1834 fuse_ino_t parent, fuse_ino_t child,
+
1835 const char *name, size_t namelen);
+
1836
+
1862int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1863 off_t offset, struct fuse_bufvec *bufv,
+ +
1894int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1895 size_t size, off_t offset, void *cookie);
+
1896
+
1897
+
1898/* ----------------------------------------------------------- *
+
1899 * Utility functions *
+
1900 * ----------------------------------------------------------- */
+
1901
+
1908void *fuse_req_userdata(fuse_req_t req);
+
1909
+
1919const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1920
+
1940int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1941
+
1948typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1949
+ +
1962 void *data);
+
1963
+ +
1971
+
1972
+
1973/* ----------------------------------------------------------- *
+
1974 * Inquiry functions *
+
1975 * ----------------------------------------------------------- */
+
1976
+
1980void fuse_lowlevel_version(void);
+
1981
+
1987void fuse_lowlevel_help(void);
+
1988
+
1992void fuse_cmdline_help(void);
+
1993
+
1994/* ----------------------------------------------------------- *
+
1995 * Filesystem setup & teardown *
+
1996 * ----------------------------------------------------------- */
+
1997
+
2003struct fuse_cmdline_opts {
+
2004 int singlethread;
+
2005 int foreground;
+
2006 int debug;
+
2007 int nodefault_subtype;
+
2008 char *mountpoint;
+
2009 int show_version;
+
2010 int show_help;
+
2011 int clone_fd;
+
2012 unsigned int max_idle_threads; /* discouraged, due to thread
+
2013 * destruct overhead */
+
2014
+
2015 /* Added in libfuse-3.12 */
+
2016 unsigned int max_threads;
+
2017};
+
2018
+
2037#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2038int fuse_parse_cmdline(struct fuse_args *args,
+
2039 struct fuse_cmdline_opts *opts);
+
2040#else
+
2041#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2042int fuse_parse_cmdline_30(struct fuse_args *args,
+
2043 struct fuse_cmdline_opts *opts);
+
2044#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2045#else
+
2046int fuse_parse_cmdline_312(struct fuse_args *args,
+
2047 struct fuse_cmdline_opts *opts);
+
2048#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2049#endif
+
2050#endif
+
2051
+
2052/* Do not call this directly, use fuse_session_new() instead */
+
2053struct fuse_session *
+
2054fuse_session_new_versioned(struct fuse_args *args,
+
2055 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2056 struct libfuse_version *version, void *userdata);
+
2057
+
2086static inline struct fuse_session *
+
2087fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2088 size_t op_size, void *userdata)
+
2089{
+
2090 struct libfuse_version version = {
+
2091 .major = FUSE_MAJOR_VERSION,
+
2092 .minor = FUSE_MINOR_VERSION,
+
2093 .hotfix = FUSE_HOTFIX_VERSION,
+
2094 .padding = 0
+
2095 };
+
2096
+
2097 return fuse_session_new_versioned(args, op, op_size, &version,
+
2098 userdata);
+
2099}
+
2100#define fuse_session_new(args, op, op_size, userdata) \
+
2101 fuse_session_new_fn(args, op, op_size, userdata)
+
2102
+
2103/*
+
2104 * This should mostly not be called directly, but instead the
+
2105 * fuse_session_custom_io() should be used.
+
2106 */
+
2107int fuse_session_custom_io_317(struct fuse_session *se,
+
2108 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2109
+
2137#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2138static inline int fuse_session_custom_io(struct fuse_session *se,
+
2139 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2140{
+
2141 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2142}
+
2143#else
+
2144static inline int fuse_session_custom_io(struct fuse_session *se,
+
2145 const struct fuse_custom_io *io, int fd)
+
2146{
+
2147 return fuse_session_custom_io_317(se, io,
+
2148 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2149}
+
2150#endif
+
2151
+
2160int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2161
+
2184int fuse_session_loop(struct fuse_session *se);
+
2185
+
2186#if FUSE_USE_VERSION < 32
+
2187 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2188 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2189#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2190 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2191 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2192#else
+
2193 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2205 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2206 #else
+
2207 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2208 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2209 #endif
+
2210#endif
+
2211
+
2224void fuse_session_exit(struct fuse_session *se);
+
2225
+
2231void fuse_session_reset(struct fuse_session *se);
+
2232
+
2239int fuse_session_exited(struct fuse_session *se);
+
2240
+
2265void fuse_session_unmount(struct fuse_session *se);
+
2266
+
2272void fuse_session_destroy(struct fuse_session *se);
+
2273
+
2274/* ----------------------------------------------------------- *
+
2275 * Custom event loop support *
+
2276 * ----------------------------------------------------------- */
+
2277
+
2292int fuse_session_fd(struct fuse_session *se);
+
2293
+
2302void fuse_session_process_buf(struct fuse_session *se,
+
2303 const struct fuse_buf *buf);
+
2304
+
2316int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2317
+
2318#ifdef __cplusplus
+
2319}
+
2320#endif
+
2321
+
2322#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ + + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__mount__compat_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..abe6033 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h.html new file mode 100644 index 0000000..0afd1bb --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h_source.html b/doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..9ad9e4a --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2include_2fuse__opt_8h_source.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+
117 int allocated;
+
118};
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2buffer_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2buffer_8c_source.html new file mode 100644 index 0000000..4e48496 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2buffer_8c_source.html @@ -0,0 +1,406 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2compat_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2compat_8c_source.html new file mode 100644 index 0000000..38027e0 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2cuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..fe7ae9a --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,451 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
196{
+
197 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
198 struct cuse_init_out outarg;
+
199 struct fuse_session *se = req->se;
+
200 struct cuse_data *cd = se->cuse_data;
+
201 size_t bufsize = se->bufsize;
+
202 struct cuse_lowlevel_ops *clop = req_clop(req);
+
203
+
204 (void) nodeid;
+
205 if (se->debug) {
+
206 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
207 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
208 }
+
209 se->conn.proto_major = arg->major;
+
210 se->conn.proto_minor = arg->minor;
+
211
+
212 /* XXX This is not right.*/
+
213 se->conn.capable_ext = 0;
+
214 se->conn.want_ext = 0;
+
215
+
216 if (arg->major < 7) {
+
217 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
218 arg->major, arg->minor);
+
219 fuse_reply_err(req, EPROTO);
+
220 return;
+
221 }
+
222
+
223 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
224 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
225 bufsize);
+
226 bufsize = FUSE_MIN_READ_BUFFER;
+
227 }
+
228
+
229 bufsize -= 4096;
+
230 if (bufsize < se->conn.max_write)
+
231 se->conn.max_write = bufsize;
+
232
+
233 se->got_init = 1;
+
234 if (se->op.init)
+
235 se->op.init(se->userdata, &se->conn);
+
236
+
237 memset(&outarg, 0, sizeof(outarg));
+
238 outarg.major = FUSE_KERNEL_VERSION;
+
239 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
240 outarg.flags = cd->flags;
+
241 outarg.max_read = cd->max_read;
+
242 outarg.max_write = se->conn.max_write;
+
243 outarg.dev_major = cd->dev_major;
+
244 outarg.dev_minor = cd->dev_minor;
+
245
+
246 if (se->debug) {
+
247 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
248 outarg.major, outarg.minor);
+
249 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
250 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
251 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
252 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
253 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
255 cd->dev_info);
+
256 }
+
257
+
258 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
259
+
260 if (clop->init_done)
+
261 clop->init_done(se->userdata);
+
262
+
263 fuse_free_req(req);
+
264}
+
265
+
266struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
267 const struct cuse_info *ci,
+
268 const struct cuse_lowlevel_ops *clop,
+
269 int *multithreaded, void *userdata)
+
270{
+
271 const char *devname = "/dev/cuse";
+
272 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
275 };
+
276 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
277 struct fuse_session *se;
+
278 struct fuse_cmdline_opts opts;
+
279 int fd;
+
280 int res;
+
281
+
282 if (fuse_parse_cmdline(&args, &opts) == -1)
+
283 return NULL;
+
284 *multithreaded = !opts.singlethread;
+
285
+
286 /* Remove subtype= option */
+
287 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
288 if (res == -1)
+
289 goto out1;
+
290
+
291 /*
+
292 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
293 * would ensue.
+
294 */
+
295 do {
+
296 fd = open("/dev/null", O_RDWR);
+
297 if (fd > 2)
+
298 close(fd);
+
299 } while (fd >= 0 && fd <= 2);
+
300
+
301 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
302 if (se == NULL)
+
303 goto out1;
+
304
+
305 fd = open(devname, O_RDWR);
+
306 if (fd == -1) {
+
307 if (errno == ENODEV || errno == ENOENT)
+
308 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
309 else
+
310 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
311 devname, strerror(errno));
+
312 goto err_se;
+
313 }
+
314 se->fd = fd;
+
315
+
316 res = fuse_set_signal_handlers(se);
+
317 if (res == -1)
+
318 goto err_se;
+
319
+
320 res = fuse_daemonize(opts.foreground);
+
321 if (res == -1)
+
322 goto err_sig;
+
323
+
324 fuse_opt_free_args(&args);
+
325 return se;
+
326
+
327err_sig:
+ +
329err_se:
+ +
331out1:
+
332 free(opts.mountpoint);
+
333 fuse_opt_free_args(&args);
+
334 return NULL;
+
335}
+
336
+
337void cuse_lowlevel_teardown(struct fuse_session *se)
+
338{
+ + +
341}
+
342
+
343int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
344 const struct cuse_lowlevel_ops *clop, void *userdata)
+
345{
+
346 struct fuse_session *se;
+
347 int multithreaded;
+
348 int res;
+
349
+
350 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
351 userdata);
+
352 if (se == NULL)
+
353 return 1;
+
354
+
355 if (multithreaded) {
+
356 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
357 res = fuse_session_loop_mt(se, config);
+
358 fuse_loop_cfg_destroy(config);
+
359 }
+
360 else
+
361 res = fuse_session_loop(se);
+
362
+
363 cuse_lowlevel_teardown(se);
+
364 if (res == -1)
+
365 return 1;
+
366
+
367 return 0;
+
368}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse_8c_source.html new file mode 100644 index 0000000..7abbf82 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse_8c_source.html @@ -0,0 +1,5393 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "fuse_kernel.h"
+
20
+
21#include <stdio.h>
+
22#include <string.h>
+
23#include <stdlib.h>
+
24#include <stddef.h>
+
25#include <stdbool.h>
+
26#include <unistd.h>
+
27#include <time.h>
+
28#include <fcntl.h>
+
29#include <limits.h>
+
30#include <errno.h>
+
31#include <signal.h>
+
32#include <dlfcn.h>
+
33#include <assert.h>
+
34#include <poll.h>
+
35#include <sys/param.h>
+
36#include <sys/uio.h>
+
37#include <sys/time.h>
+
38#include <sys/mman.h>
+
39#include <sys/file.h>
+
40
+
41#define FUSE_NODE_SLAB 1
+
42
+
43#ifndef MAP_ANONYMOUS
+
44#undef FUSE_NODE_SLAB
+
45#endif
+
46
+
47#ifndef RENAME_EXCHANGE
+
48#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
49#endif
+
50
+
51#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
52
+
53#define FUSE_UNKNOWN_INO 0xffffffff
+
54#define OFFSET_MAX 0x7fffffffffffffffLL
+
55
+
56#define NODE_TABLE_MIN_SIZE 8192
+
57
+
58struct fuse_fs {
+
59 struct fuse_operations op;
+
60 void *user_data;
+
61 int debug;
+
62};
+
63
+
64struct fusemod_so {
+
65 void *handle;
+
66 int ctr;
+
67};
+
68
+
69struct lock_queue_element {
+
70 struct lock_queue_element *next;
+
71 pthread_cond_t cond;
+
72 fuse_ino_t nodeid1;
+
73 const char *name1;
+
74 char **path1;
+
75 struct node **wnode1;
+
76 fuse_ino_t nodeid2;
+
77 const char *name2;
+
78 char **path2;
+
79 struct node **wnode2;
+
80 int err;
+
81 bool done : 1;
+
82};
+
83
+
84struct node_table {
+
85 struct node **array;
+
86 size_t use;
+
87 size_t size;
+
88 size_t split;
+
89};
+
90
+
91#define container_of(ptr, type, member) ({ \
+
92 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
93 (type *)( (char *)__mptr - offsetof(type,member) );})
+
94
+
95#define list_entry(ptr, type, member) \
+
96 container_of(ptr, type, member)
+
97
+
98struct list_head {
+
99 struct list_head *next;
+
100 struct list_head *prev;
+
101};
+
102
+
103struct node_slab {
+
104 struct list_head list; /* must be the first member */
+
105 struct list_head freelist;
+
106 int used;
+
107};
+
108
+
109struct fuse {
+
110 struct fuse_session *se;
+
111 struct node_table name_table;
+
112 struct node_table id_table;
+
113 struct list_head lru_table;
+
114 fuse_ino_t ctr;
+
115 unsigned int generation;
+
116 unsigned int hidectr;
+
117 pthread_mutex_t lock;
+
118 struct fuse_config conf;
+
119 int intr_installed;
+
120 struct fuse_fs *fs;
+
121 struct lock_queue_element *lockq;
+
122 int pagesize;
+
123 struct list_head partial_slabs;
+
124 struct list_head full_slabs;
+
125 pthread_t prune_thread;
+
126};
+
127
+
128struct lock {
+
129 int type;
+
130 off_t start;
+
131 off_t end;
+
132 pid_t pid;
+
133 uint64_t owner;
+
134 struct lock *next;
+
135};
+
136
+
137struct node {
+
138 struct node *name_next;
+
139 struct node *id_next;
+
140 fuse_ino_t nodeid;
+
141 unsigned int generation;
+
142 int refctr;
+
143 struct node *parent;
+
144 char *name;
+
145 uint64_t nlookup;
+
146 int open_count;
+
147 struct timespec stat_updated;
+
148 struct timespec mtime;
+
149 off_t size;
+
150 struct lock *locks;
+
151 unsigned int is_hidden : 1;
+
152 unsigned int cache_valid : 1;
+
153 int treelock;
+
154 char inline_name[32];
+
155};
+
156
+
157#define TREELOCK_WRITE -1
+
158#define TREELOCK_WAIT_OFFSET INT_MIN
+
159
+
160struct node_lru {
+
161 struct node node;
+
162 struct list_head lru;
+
163 struct timespec forget_time;
+
164};
+
165
+
166struct fuse_direntry {
+
167 struct stat stat;
+
168 enum fuse_fill_dir_flags flags;
+
169 char *name;
+
170 struct fuse_direntry *next;
+
171};
+
172
+
173struct fuse_dh {
+
174 pthread_mutex_t lock;
+
175 struct fuse *fuse;
+
176 fuse_req_t req;
+
177 char *contents;
+
178 struct fuse_direntry *first;
+
179 struct fuse_direntry **last;
+
180 unsigned len;
+
181 unsigned size;
+
182 unsigned needlen;
+
183 int filled;
+
184 uint64_t fh;
+
185 int error;
+
186 fuse_ino_t nodeid;
+
187};
+
188
+
189struct fuse_context_i {
+
190 struct fuse_context ctx;
+
191 fuse_req_t req;
+
192};
+
193
+
194/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
195extern fuse_module_factory_t fuse_module_subdir_factory;
+
196#ifdef HAVE_ICONV
+
197extern fuse_module_factory_t fuse_module_iconv_factory;
+
198#endif
+
199
+
200static pthread_key_t fuse_context_key;
+
201static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
202static int fuse_context_ref;
+
203static struct fuse_module *fuse_modules = NULL;
+
204
+
205static int fuse_register_module(const char *name,
+
206 fuse_module_factory_t factory,
+
207 struct fusemod_so *so)
+
208{
+
209 struct fuse_module *mod;
+
210
+
211 mod = calloc(1, sizeof(struct fuse_module));
+
212 if (!mod) {
+
213 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
214 return -1;
+
215 }
+
216 mod->name = strdup(name);
+
217 if (!mod->name) {
+
218 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
219 free(mod);
+
220 return -1;
+
221 }
+
222 mod->factory = factory;
+
223 mod->ctr = 0;
+
224 mod->so = so;
+
225 if (mod->so)
+
226 mod->so->ctr++;
+
227 mod->next = fuse_modules;
+
228 fuse_modules = mod;
+
229
+
230 return 0;
+
231}
+
232
+
233static void fuse_unregister_module(struct fuse_module *m)
+
234{
+
235 struct fuse_module **mp;
+
236 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
237 if (*mp == m) {
+
238 *mp = (*mp)->next;
+
239 break;
+
240 }
+
241 }
+
242 free(m->name);
+
243 free(m);
+
244}
+
245
+
246static int fuse_load_so_module(const char *module)
+
247{
+
248 int ret = -1;
+
249 char *tmp;
+
250 struct fusemod_so *so;
+
251 fuse_module_factory_t *factory;
+
252
+
253 tmp = malloc(strlen(module) + 64);
+
254 if (!tmp) {
+
255 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
256 return -1;
+
257 }
+
258 sprintf(tmp, "libfusemod_%s.so", module);
+
259 so = calloc(1, sizeof(struct fusemod_so));
+
260 if (!so) {
+
261 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
262 goto out;
+
263 }
+
264
+
265 so->handle = dlopen(tmp, RTLD_NOW);
+
266 if (so->handle == NULL) {
+
267 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
268 tmp, dlerror());
+
269 goto out_free_so;
+
270 }
+
271
+
272 sprintf(tmp, "fuse_module_%s_factory", module);
+
273 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
274 if (factory == NULL) {
+
275 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
276 tmp, dlerror());
+
277 goto out_dlclose;
+
278 }
+
279 ret = fuse_register_module(module, *factory, so);
+
280 if (ret)
+
281 goto out_dlclose;
+
282
+
283out:
+
284 free(tmp);
+
285 return ret;
+
286
+
287out_dlclose:
+
288 dlclose(so->handle);
+
289out_free_so:
+
290 free(so);
+
291 goto out;
+
292}
+
293
+
294static struct fuse_module *fuse_find_module(const char *module)
+
295{
+
296 struct fuse_module *m;
+
297 for (m = fuse_modules; m; m = m->next) {
+
298 if (strcmp(module, m->name) == 0) {
+
299 m->ctr++;
+
300 break;
+
301 }
+
302 }
+
303 return m;
+
304}
+
305
+
306static struct fuse_module *fuse_get_module(const char *module)
+
307{
+
308 struct fuse_module *m;
+
309
+
310 pthread_mutex_lock(&fuse_context_lock);
+
311 m = fuse_find_module(module);
+
312 if (!m) {
+
313 int err = fuse_load_so_module(module);
+
314 if (!err)
+
315 m = fuse_find_module(module);
+
316 }
+
317 pthread_mutex_unlock(&fuse_context_lock);
+
318 return m;
+
319}
+
320
+
321static void fuse_put_module(struct fuse_module *m)
+
322{
+
323 pthread_mutex_lock(&fuse_context_lock);
+
324 if (m->so)
+
325 assert(m->ctr > 0);
+
326 /* Builtin modules may already have m->ctr == 0 */
+
327 if (m->ctr > 0)
+
328 m->ctr--;
+
329 if (!m->ctr && m->so) {
+
330 struct fusemod_so *so = m->so;
+
331 assert(so->ctr > 0);
+
332 so->ctr--;
+
333 if (!so->ctr) {
+
334 struct fuse_module **mp;
+
335 for (mp = &fuse_modules; *mp;) {
+
336 if ((*mp)->so == so)
+
337 fuse_unregister_module(*mp);
+
338 else
+
339 mp = &(*mp)->next;
+
340 }
+
341 dlclose(so->handle);
+
342 free(so);
+
343 }
+
344 } else if (!m->ctr) {
+
345 fuse_unregister_module(m);
+
346 }
+
347 pthread_mutex_unlock(&fuse_context_lock);
+
348}
+
349
+
350static void init_list_head(struct list_head *list)
+
351{
+
352 list->next = list;
+
353 list->prev = list;
+
354}
+
355
+
356static int list_empty(const struct list_head *head)
+
357{
+
358 return head->next == head;
+
359}
+
360
+
361static void list_add(struct list_head *new, struct list_head *prev,
+
362 struct list_head *next)
+
363{
+
364 next->prev = new;
+
365 new->next = next;
+
366 new->prev = prev;
+
367 prev->next = new;
+
368}
+
369
+
370static inline void list_add_head(struct list_head *new, struct list_head *head)
+
371{
+
372 list_add(new, head, head->next);
+
373}
+
374
+
375static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
376{
+
377 list_add(new, head->prev, head);
+
378}
+
379
+
380static inline void list_del(struct list_head *entry)
+
381{
+
382 struct list_head *prev = entry->prev;
+
383 struct list_head *next = entry->next;
+
384
+
385 next->prev = prev;
+
386 prev->next = next;
+
387}
+
388
+
389static inline int lru_enabled(struct fuse *f)
+
390{
+
391 return f->conf.remember > 0;
+
392}
+
393
+
394static struct node_lru *node_lru(struct node *node)
+
395{
+
396 return (struct node_lru *) node;
+
397}
+
398
+
399static size_t get_node_size(struct fuse *f)
+
400{
+
401 if (lru_enabled(f))
+
402 return sizeof(struct node_lru);
+
403 else
+
404 return sizeof(struct node);
+
405}
+
406
+
407#ifdef FUSE_NODE_SLAB
+
408static struct node_slab *list_to_slab(struct list_head *head)
+
409{
+
410 return (struct node_slab *) head;
+
411}
+
412
+
413static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
414{
+
415 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
416}
+
417
+
418static int alloc_slab(struct fuse *f)
+
419{
+
420 void *mem;
+
421 struct node_slab *slab;
+
422 char *start;
+
423 size_t num;
+
424 size_t i;
+
425 size_t node_size = get_node_size(f);
+
426
+
427 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
428 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
429
+
430 if (mem == MAP_FAILED)
+
431 return -1;
+
432
+
433 slab = mem;
+
434 init_list_head(&slab->freelist);
+
435 slab->used = 0;
+
436 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
437
+
438 start = (char *) mem + f->pagesize - num * node_size;
+
439 for (i = 0; i < num; i++) {
+
440 struct list_head *n;
+
441
+
442 n = (struct list_head *) (start + i * node_size);
+
443 list_add_tail(n, &slab->freelist);
+
444 }
+
445 list_add_tail(&slab->list, &f->partial_slabs);
+
446
+
447 return 0;
+
448}
+
449
+
450static struct node *alloc_node(struct fuse *f)
+
451{
+
452 struct node_slab *slab;
+
453 struct list_head *node;
+
454
+
455 if (list_empty(&f->partial_slabs)) {
+
456 int res = alloc_slab(f);
+
457 if (res != 0)
+
458 return NULL;
+
459 }
+
460 slab = list_to_slab(f->partial_slabs.next);
+
461 slab->used++;
+
462 node = slab->freelist.next;
+
463 list_del(node);
+
464 if (list_empty(&slab->freelist)) {
+
465 list_del(&slab->list);
+
466 list_add_tail(&slab->list, &f->full_slabs);
+
467 }
+
468 memset(node, 0, sizeof(struct node));
+
469
+
470 return (struct node *) node;
+
471}
+
472
+
473static void free_slab(struct fuse *f, struct node_slab *slab)
+
474{
+
475 int res;
+
476
+
477 list_del(&slab->list);
+
478 res = munmap(slab, f->pagesize);
+
479 if (res == -1)
+
480 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
481 slab);
+
482}
+
483
+
484static void free_node_mem(struct fuse *f, struct node *node)
+
485{
+
486 struct node_slab *slab = node_to_slab(f, node);
+
487 struct list_head *n = (struct list_head *) node;
+
488
+
489 slab->used--;
+
490 if (slab->used) {
+
491 if (list_empty(&slab->freelist)) {
+
492 list_del(&slab->list);
+
493 list_add_tail(&slab->list, &f->partial_slabs);
+
494 }
+
495 list_add_head(n, &slab->freelist);
+
496 } else {
+
497 free_slab(f, slab);
+
498 }
+
499}
+
500#else
+
501static struct node *alloc_node(struct fuse *f)
+
502{
+
503 return (struct node *) calloc(1, get_node_size(f));
+
504}
+
505
+
506static void free_node_mem(struct fuse *f, struct node *node)
+
507{
+
508 (void) f;
+
509 free(node);
+
510}
+
511#endif
+
512
+
513static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
514{
+
515 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
516 uint64_t oldhash = hash % (f->id_table.size / 2);
+
517
+
518 if (oldhash >= f->id_table.split)
+
519 return oldhash;
+
520 else
+
521 return hash;
+
522}
+
523
+
524static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
525{
+
526 size_t hash = id_hash(f, nodeid);
+
527 struct node *node;
+
528
+
529 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
530 if (node->nodeid == nodeid)
+
531 return node;
+
532
+
533 return NULL;
+
534}
+
535
+
536static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
537{
+
538 struct node *node = get_node_nocheck(f, nodeid);
+
539 if (!node) {
+
540 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
541 (unsigned long long) nodeid);
+
542 abort();
+
543 }
+
544 return node;
+
545}
+
546
+
547static void curr_time(struct timespec *now);
+
548static double diff_timespec(const struct timespec *t1,
+
549 const struct timespec *t2);
+
550
+
551static void remove_node_lru(struct node *node)
+
552{
+
553 struct node_lru *lnode = node_lru(node);
+
554 list_del(&lnode->lru);
+
555 init_list_head(&lnode->lru);
+
556}
+
557
+
558static void set_forget_time(struct fuse *f, struct node *node)
+
559{
+
560 struct node_lru *lnode = node_lru(node);
+
561
+
562 list_del(&lnode->lru);
+
563 list_add_tail(&lnode->lru, &f->lru_table);
+
564 curr_time(&lnode->forget_time);
+
565}
+
566
+
567static void free_node(struct fuse *f, struct node *node)
+
568{
+
569 if (node->name != node->inline_name)
+
570 free(node->name);
+
571 free_node_mem(f, node);
+
572}
+
573
+
574static void node_table_reduce(struct node_table *t)
+
575{
+
576 size_t newsize = t->size / 2;
+
577 void *newarray;
+
578
+
579 if (newsize < NODE_TABLE_MIN_SIZE)
+
580 return;
+
581
+
582 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
583 if (newarray != NULL)
+
584 t->array = newarray;
+
585
+
586 t->size = newsize;
+
587 t->split = t->size / 2;
+
588}
+
589
+
590static void remerge_id(struct fuse *f)
+
591{
+
592 struct node_table *t = &f->id_table;
+
593 int iter;
+
594
+
595 if (t->split == 0)
+
596 node_table_reduce(t);
+
597
+
598 for (iter = 8; t->split > 0 && iter; iter--) {
+
599 struct node **upper;
+
600
+
601 t->split--;
+
602 upper = &t->array[t->split + t->size / 2];
+
603 if (*upper) {
+
604 struct node **nodep;
+
605
+
606 for (nodep = &t->array[t->split]; *nodep;
+
607 nodep = &(*nodep)->id_next);
+
608
+
609 *nodep = *upper;
+
610 *upper = NULL;
+
611 break;
+
612 }
+
613 }
+
614}
+
615
+
616static void unhash_id(struct fuse *f, struct node *node)
+
617{
+
618 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
619
+
620 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
621 if (*nodep == node) {
+
622 *nodep = node->id_next;
+
623 f->id_table.use--;
+
624
+
625 if(f->id_table.use < f->id_table.size / 4)
+
626 remerge_id(f);
+
627 return;
+
628 }
+
629}
+
630
+
631static int node_table_resize(struct node_table *t)
+
632{
+
633 size_t newsize = t->size * 2;
+
634 void *newarray;
+
635
+
636 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
637 if (newarray == NULL)
+
638 return -1;
+
639
+
640 t->array = newarray;
+
641 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
642 t->size = newsize;
+
643 t->split = 0;
+
644
+
645 return 0;
+
646}
+
647
+
648static void rehash_id(struct fuse *f)
+
649{
+
650 struct node_table *t = &f->id_table;
+
651 struct node **nodep;
+
652 struct node **next;
+
653 size_t hash;
+
654
+
655 if (t->split == t->size / 2)
+
656 return;
+
657
+
658 hash = t->split;
+
659 t->split++;
+
660 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
661 struct node *node = *nodep;
+
662 size_t newhash = id_hash(f, node->nodeid);
+
663
+
664 if (newhash != hash) {
+
665 next = nodep;
+
666 *nodep = node->id_next;
+
667 node->id_next = t->array[newhash];
+
668 t->array[newhash] = node;
+
669 } else {
+
670 next = &node->id_next;
+
671 }
+
672 }
+
673 if (t->split == t->size / 2)
+
674 node_table_resize(t);
+
675}
+
676
+
677static void hash_id(struct fuse *f, struct node *node)
+
678{
+
679 size_t hash = id_hash(f, node->nodeid);
+
680 node->id_next = f->id_table.array[hash];
+
681 f->id_table.array[hash] = node;
+
682 f->id_table.use++;
+
683
+
684 if (f->id_table.use >= f->id_table.size / 2)
+
685 rehash_id(f);
+
686}
+
687
+
688static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
689 const char *name)
+
690{
+
691 uint64_t hash = parent;
+
692 uint64_t oldhash;
+
693
+
694 for (; *name; name++)
+
695 hash = hash * 31 + (unsigned char) *name;
+
696
+
697 hash %= f->name_table.size;
+
698 oldhash = hash % (f->name_table.size / 2);
+
699 if (oldhash >= f->name_table.split)
+
700 return oldhash;
+
701 else
+
702 return hash;
+
703}
+
704
+
705static void unref_node(struct fuse *f, struct node *node);
+
706
+
707static void remerge_name(struct fuse *f)
+
708{
+
709 struct node_table *t = &f->name_table;
+
710 int iter;
+
711
+
712 if (t->split == 0)
+
713 node_table_reduce(t);
+
714
+
715 for (iter = 8; t->split > 0 && iter; iter--) {
+
716 struct node **upper;
+
717
+
718 t->split--;
+
719 upper = &t->array[t->split + t->size / 2];
+
720 if (*upper) {
+
721 struct node **nodep;
+
722
+
723 for (nodep = &t->array[t->split]; *nodep;
+
724 nodep = &(*nodep)->name_next);
+
725
+
726 *nodep = *upper;
+
727 *upper = NULL;
+
728 break;
+
729 }
+
730 }
+
731}
+
732
+
733static void unhash_name(struct fuse *f, struct node *node)
+
734{
+
735 if (node->name) {
+
736 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
737 struct node **nodep = &f->name_table.array[hash];
+
738
+
739 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
740 if (*nodep == node) {
+
741 *nodep = node->name_next;
+
742 node->name_next = NULL;
+
743 unref_node(f, node->parent);
+
744 if (node->name != node->inline_name)
+
745 free(node->name);
+
746 node->name = NULL;
+
747 node->parent = NULL;
+
748 f->name_table.use--;
+
749
+
750 if (f->name_table.use < f->name_table.size / 4)
+
751 remerge_name(f);
+
752 return;
+
753 }
+
754 fuse_log(FUSE_LOG_ERR,
+
755 "fuse internal error: unable to unhash node: %llu\n",
+
756 (unsigned long long) node->nodeid);
+
757 abort();
+
758 }
+
759}
+
760
+
761static void rehash_name(struct fuse *f)
+
762{
+
763 struct node_table *t = &f->name_table;
+
764 struct node **nodep;
+
765 struct node **next;
+
766 size_t hash;
+
767
+
768 if (t->split == t->size / 2)
+
769 return;
+
770
+
771 hash = t->split;
+
772 t->split++;
+
773 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
774 struct node *node = *nodep;
+
775 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
776
+
777 if (newhash != hash) {
+
778 next = nodep;
+
779 *nodep = node->name_next;
+
780 node->name_next = t->array[newhash];
+
781 t->array[newhash] = node;
+
782 } else {
+
783 next = &node->name_next;
+
784 }
+
785 }
+
786 if (t->split == t->size / 2)
+
787 node_table_resize(t);
+
788}
+
789
+
790static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
791 const char *name)
+
792{
+
793 size_t hash = name_hash(f, parentid, name);
+
794 struct node *parent = get_node(f, parentid);
+
795 if (strlen(name) < sizeof(node->inline_name)) {
+
796 strcpy(node->inline_name, name);
+
797 node->name = node->inline_name;
+
798 } else {
+
799 node->name = strdup(name);
+
800 if (node->name == NULL)
+
801 return -1;
+
802 }
+
803
+
804 parent->refctr ++;
+
805 node->parent = parent;
+
806 node->name_next = f->name_table.array[hash];
+
807 f->name_table.array[hash] = node;
+
808 f->name_table.use++;
+
809
+
810 if (f->name_table.use >= f->name_table.size / 2)
+
811 rehash_name(f);
+
812
+
813 return 0;
+
814}
+
815
+
816static void delete_node(struct fuse *f, struct node *node)
+
817{
+
818 if (f->conf.debug)
+
819 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
820 (unsigned long long) node->nodeid);
+
821
+
822 assert(node->treelock == 0);
+
823 unhash_name(f, node);
+
824 if (lru_enabled(f))
+
825 remove_node_lru(node);
+
826 unhash_id(f, node);
+
827 free_node(f, node);
+
828}
+
829
+
830static void unref_node(struct fuse *f, struct node *node)
+
831{
+
832 assert(node->refctr > 0);
+
833 node->refctr --;
+
834 if (!node->refctr)
+
835 delete_node(f, node);
+
836}
+
837
+
838static fuse_ino_t next_id(struct fuse *f)
+
839{
+
840 do {
+
841 f->ctr = (f->ctr + 1) & 0xffffffff;
+
842 if (!f->ctr)
+
843 f->generation ++;
+
844 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
845 get_node_nocheck(f, f->ctr) != NULL);
+
846 return f->ctr;
+
847}
+
848
+
849static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
850 const char *name)
+
851{
+
852 size_t hash = name_hash(f, parent, name);
+
853 struct node *node;
+
854
+
855 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
856 if (node->parent->nodeid == parent &&
+
857 strcmp(node->name, name) == 0)
+
858 return node;
+
859
+
860 return NULL;
+
861}
+
862
+
863static void inc_nlookup(struct node *node)
+
864{
+
865 if (!node->nlookup)
+
866 node->refctr++;
+
867 node->nlookup++;
+
868}
+
869
+
870static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
871 const char *name)
+
872{
+
873 struct node *node;
+
874
+
875 pthread_mutex_lock(&f->lock);
+
876 if (!name)
+
877 node = get_node(f, parent);
+
878 else
+
879 node = lookup_node(f, parent, name);
+
880 if (node == NULL) {
+
881 node = alloc_node(f);
+
882 if (node == NULL)
+
883 goto out_err;
+
884
+
885 node->nodeid = next_id(f);
+
886 node->generation = f->generation;
+
887 if (f->conf.remember)
+
888 inc_nlookup(node);
+
889
+
890 if (hash_name(f, node, parent, name) == -1) {
+
891 free_node(f, node);
+
892 node = NULL;
+
893 goto out_err;
+
894 }
+
895 hash_id(f, node);
+
896 if (lru_enabled(f)) {
+
897 struct node_lru *lnode = node_lru(node);
+
898 init_list_head(&lnode->lru);
+
899 }
+
900 } else if (lru_enabled(f) && node->nlookup == 1) {
+
901 remove_node_lru(node);
+
902 }
+
903 inc_nlookup(node);
+
904out_err:
+
905 pthread_mutex_unlock(&f->lock);
+
906 return node;
+
907}
+
908
+
909static int lookup_path_in_cache(struct fuse *f,
+
910 const char *path, fuse_ino_t *inop)
+
911{
+
912 char *tmp = strdup(path);
+
913 if (!tmp)
+
914 return -ENOMEM;
+
915
+
916 pthread_mutex_lock(&f->lock);
+
917 fuse_ino_t ino = FUSE_ROOT_ID;
+
918
+
919 int err = 0;
+
920 char *save_ptr;
+
921 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
922 while (path_element != NULL) {
+
923 struct node *node = lookup_node(f, ino, path_element);
+
924 if (node == NULL) {
+
925 err = -ENOENT;
+
926 break;
+
927 }
+
928 ino = node->nodeid;
+
929 path_element = strtok_r(NULL, "/", &save_ptr);
+
930 }
+
931 pthread_mutex_unlock(&f->lock);
+
932 free(tmp);
+
933
+
934 if (!err)
+
935 *inop = ino;
+
936 return err;
+
937}
+
938
+
939static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
940{
+
941 size_t len = strlen(name);
+
942
+
943 if (s - len <= *buf) {
+
944 unsigned pathlen = *bufsize - (s - *buf);
+
945 unsigned newbufsize = *bufsize;
+
946 char *newbuf;
+
947
+
948 while (newbufsize < pathlen + len + 1) {
+
949 if (newbufsize >= 0x80000000)
+
950 newbufsize = 0xffffffff;
+
951 else
+
952 newbufsize *= 2;
+
953 }
+
954
+
955 newbuf = realloc(*buf, newbufsize);
+
956 if (newbuf == NULL)
+
957 return NULL;
+
958
+
959 *buf = newbuf;
+
960 s = newbuf + newbufsize - pathlen;
+
961 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
962 *bufsize = newbufsize;
+
963 }
+
964 s -= len;
+
965 memcpy(s, name, len);
+
966 s--;
+
967 *s = '/';
+
968
+
969 return s;
+
970}
+
971
+
972static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
973 struct node *end)
+
974{
+
975 struct node *node;
+
976
+
977 if (wnode) {
+
978 assert(wnode->treelock == TREELOCK_WRITE);
+
979 wnode->treelock = 0;
+
980 }
+
981
+
982 for (node = get_node(f, nodeid);
+
983 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
984 assert(node->treelock != 0);
+
985 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
986 assert(node->treelock != TREELOCK_WRITE);
+
987 node->treelock--;
+
988 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
989 node->treelock = 0;
+
990 }
+
991}
+
992
+
993static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
994 char **path, struct node **wnodep, bool need_lock)
+
995{
+
996 unsigned bufsize = 256;
+
997 char *buf;
+
998 char *s;
+
999 struct node *node;
+
1000 struct node *wnode = NULL;
+
1001 int err;
+
1002
+
1003 *path = NULL;
+
1004
+
1005 err = -ENOMEM;
+
1006 buf = malloc(bufsize);
+
1007 if (buf == NULL)
+
1008 goto out_err;
+
1009
+
1010 s = buf + bufsize - 1;
+
1011 *s = '\0';
+
1012
+
1013 if (name != NULL) {
+
1014 s = add_name(&buf, &bufsize, s, name);
+
1015 err = -ENOMEM;
+
1016 if (s == NULL)
+
1017 goto out_free;
+
1018 }
+
1019
+
1020 if (wnodep) {
+
1021 assert(need_lock);
+
1022 wnode = lookup_node(f, nodeid, name);
+
1023 if (wnode) {
+
1024 if (wnode->treelock != 0) {
+
1025 if (wnode->treelock > 0)
+
1026 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1027 err = -EAGAIN;
+
1028 goto out_free;
+
1029 }
+
1030 wnode->treelock = TREELOCK_WRITE;
+
1031 }
+
1032 }
+
1033
+
1034 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1035 node = node->parent) {
+
1036 err = -ESTALE;
+
1037 if (node->name == NULL || node->parent == NULL)
+
1038 goto out_unlock;
+
1039
+
1040 err = -ENOMEM;
+
1041 s = add_name(&buf, &bufsize, s, node->name);
+
1042 if (s == NULL)
+
1043 goto out_unlock;
+
1044
+
1045 if (need_lock) {
+
1046 err = -EAGAIN;
+
1047 if (node->treelock < 0)
+
1048 goto out_unlock;
+
1049
+
1050 node->treelock++;
+
1051 }
+
1052 }
+
1053
+
1054 if (s[0])
+
1055 memmove(buf, s, bufsize - (s - buf));
+
1056 else
+
1057 strcpy(buf, "/");
+
1058
+
1059 *path = buf;
+
1060 if (wnodep)
+
1061 *wnodep = wnode;
+
1062
+
1063 return 0;
+
1064
+
1065 out_unlock:
+
1066 if (need_lock)
+
1067 unlock_path(f, nodeid, wnode, node);
+
1068 out_free:
+
1069 free(buf);
+
1070
+
1071 out_err:
+
1072 return err;
+
1073}
+
1074
+
1075static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1076 fuse_ino_t nodeid2, const char *name2,
+
1077 char **path1, char **path2,
+
1078 struct node **wnode1, struct node **wnode2)
+
1079{
+
1080 int err;
+
1081
+
1082 /* FIXME: locking two paths needs deadlock checking */
+
1083 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1084 if (!err) {
+
1085 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1086 if (err) {
+
1087 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1088
+
1089 unlock_path(f, nodeid1, wn1, NULL);
+
1090 free(*path1);
+
1091 }
+
1092 }
+
1093 return err;
+
1094}
+
1095
+
1096static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1097{
+
1098 int err;
+
1099
+
1100 if (!qe->path1) {
+
1101 /* Just waiting for it to be unlocked */
+
1102 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1103 pthread_cond_signal(&qe->cond);
+
1104
+
1105 return;
+
1106 }
+
1107
+
1108 if (qe->done)
+
1109 return; // Don't try to double-lock the element
+
1110
+
1111 if (!qe->path2) {
+
1112 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1113 qe->wnode1, true);
+
1114 } else {
+
1115 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1116 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1117 qe->wnode2);
+
1118 }
+
1119
+
1120 if (err == -EAGAIN)
+
1121 return; /* keep trying */
+
1122
+
1123 qe->err = err;
+
1124 qe->done = true;
+
1125 pthread_cond_signal(&qe->cond);
+
1126}
+
1127
+
1128static void wake_up_queued(struct fuse *f)
+
1129{
+
1130 struct lock_queue_element *qe;
+
1131
+
1132 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1133 queue_element_wakeup(f, qe);
+
1134}
+
1135
+
1136static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1137 const char *name, bool wr)
+
1138{
+
1139 if (f->conf.debug) {
+
1140 struct node *wnode = NULL;
+
1141
+
1142 if (wr)
+
1143 wnode = lookup_node(f, nodeid, name);
+
1144
+
1145 if (wnode) {
+
1146 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1147 msg, (unsigned long long) wnode->nodeid);
+
1148 } else {
+
1149 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1150 msg, (unsigned long long) nodeid);
+
1151 }
+
1152 }
+
1153}
+
1154
+
1155static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1156{
+
1157 struct lock_queue_element **qp;
+
1158
+
1159 qe->done = false;
+
1160 pthread_cond_init(&qe->cond, NULL);
+
1161 qe->next = NULL;
+
1162 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1163 *qp = qe;
+
1164}
+
1165
+
1166static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1167{
+
1168 struct lock_queue_element **qp;
+
1169
+
1170 pthread_cond_destroy(&qe->cond);
+
1171 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1172 *qp = qe->next;
+
1173}
+
1174
+
1175static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1176{
+
1177 queue_path(f, qe);
+
1178
+
1179 do {
+
1180 pthread_cond_wait(&qe->cond, &f->lock);
+
1181 } while (!qe->done);
+
1182
+
1183 dequeue_path(f, qe);
+
1184
+
1185 return qe->err;
+
1186}
+
1187
+
1188static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1189 char **path, struct node **wnode)
+
1190{
+
1191 int err;
+
1192
+
1193 pthread_mutex_lock(&f->lock);
+
1194 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1195 if (err == -EAGAIN) {
+
1196 struct lock_queue_element qe = {
+
1197 .nodeid1 = nodeid,
+
1198 .name1 = name,
+
1199 .path1 = path,
+
1200 .wnode1 = wnode,
+
1201 };
+
1202 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1203 err = wait_path(f, &qe);
+
1204 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1205 }
+
1206 pthread_mutex_unlock(&f->lock);
+
1207
+
1208 return err;
+
1209}
+
1210
+
1211static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1212{
+
1213 return get_path_common(f, nodeid, NULL, path, NULL);
+
1214}
+
1215
+
1216static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1217{
+
1218 int err = 0;
+
1219
+
1220 if (f->conf.nullpath_ok) {
+
1221 *path = NULL;
+
1222 } else {
+
1223 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1224 if (err == -ESTALE)
+
1225 err = 0;
+
1226 }
+
1227
+
1228 return err;
+
1229}
+
1230
+
1231static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1232 char **path)
+
1233{
+
1234 return get_path_common(f, nodeid, name, path, NULL);
+
1235}
+
1236
+
1237static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1238 char **path, struct node **wnode)
+
1239{
+
1240 return get_path_common(f, nodeid, name, path, wnode);
+
1241}
+
1242
+
1243#if defined(__FreeBSD__)
+
1244#define CHECK_DIR_LOOP
+
1245#endif
+
1246
+
1247#if defined(CHECK_DIR_LOOP)
+
1248static int check_dir_loop(struct fuse *f,
+
1249 fuse_ino_t nodeid1, const char *name1,
+
1250 fuse_ino_t nodeid2, const char *name2)
+
1251{
+
1252 struct node *node, *node1, *node2;
+
1253 fuse_ino_t id1, id2;
+
1254
+
1255 node1 = lookup_node(f, nodeid1, name1);
+
1256 id1 = node1 ? node1->nodeid : nodeid1;
+
1257
+
1258 node2 = lookup_node(f, nodeid2, name2);
+
1259 id2 = node2 ? node2->nodeid : nodeid2;
+
1260
+
1261 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1262 node = node->parent) {
+
1263 if (node->name == NULL || node->parent == NULL)
+
1264 break;
+
1265
+
1266 if (node->nodeid != id2 && node->nodeid == id1)
+
1267 return -EINVAL;
+
1268 }
+
1269
+
1270 if (node2)
+
1271 {
+
1272 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1273 node = node->parent) {
+
1274 if (node->name == NULL || node->parent == NULL)
+
1275 break;
+
1276
+
1277 if (node->nodeid != id1 && node->nodeid == id2)
+
1278 return -ENOTEMPTY;
+
1279 }
+
1280 }
+
1281
+
1282 return 0;
+
1283}
+
1284#endif
+
1285
+
1286static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1287 fuse_ino_t nodeid2, const char *name2,
+
1288 char **path1, char **path2,
+
1289 struct node **wnode1, struct node **wnode2)
+
1290{
+
1291 int err;
+
1292
+
1293 pthread_mutex_lock(&f->lock);
+
1294
+
1295#if defined(CHECK_DIR_LOOP)
+
1296 if (name1)
+
1297 {
+
1298 // called during rename; perform dir loop check
+
1299 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1300 if (err)
+
1301 goto out_unlock;
+
1302 }
+
1303#endif
+
1304
+
1305 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1306 path1, path2, wnode1, wnode2);
+
1307 if (err == -EAGAIN) {
+
1308 struct lock_queue_element qe = {
+
1309 .nodeid1 = nodeid1,
+
1310 .name1 = name1,
+
1311 .path1 = path1,
+
1312 .wnode1 = wnode1,
+
1313 .nodeid2 = nodeid2,
+
1314 .name2 = name2,
+
1315 .path2 = path2,
+
1316 .wnode2 = wnode2,
+
1317 };
+
1318
+
1319 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1320 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1321 err = wait_path(f, &qe);
+
1322 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1323 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1324 }
+
1325
+
1326#if defined(CHECK_DIR_LOOP)
+
1327out_unlock:
+
1328#endif
+
1329 pthread_mutex_unlock(&f->lock);
+
1330
+
1331 return err;
+
1332}
+
1333
+
1334static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1335 struct node *wnode, char *path)
+
1336{
+
1337 pthread_mutex_lock(&f->lock);
+
1338 unlock_path(f, nodeid, wnode, NULL);
+
1339 if (f->lockq)
+
1340 wake_up_queued(f);
+
1341 pthread_mutex_unlock(&f->lock);
+
1342 free(path);
+
1343}
+
1344
+
1345static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1346{
+
1347 if (path)
+
1348 free_path_wrlock(f, nodeid, NULL, path);
+
1349}
+
1350
+
1351static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1352 struct node *wnode1, struct node *wnode2,
+
1353 char *path1, char *path2)
+
1354{
+
1355 pthread_mutex_lock(&f->lock);
+
1356 unlock_path(f, nodeid1, wnode1, NULL);
+
1357 unlock_path(f, nodeid2, wnode2, NULL);
+
1358 wake_up_queued(f);
+
1359 pthread_mutex_unlock(&f->lock);
+
1360 free(path1);
+
1361 free(path2);
+
1362}
+
1363
+
1364static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1365{
+
1366 struct node *node;
+
1367 if (nodeid == FUSE_ROOT_ID)
+
1368 return;
+
1369 pthread_mutex_lock(&f->lock);
+
1370 node = get_node(f, nodeid);
+
1371
+
1372 /*
+
1373 * Node may still be locked due to interrupt idiocy in open,
+
1374 * create and opendir
+
1375 */
+
1376 while (node->nlookup == nlookup && node->treelock) {
+
1377 struct lock_queue_element qe = {
+
1378 .nodeid1 = nodeid,
+
1379 };
+
1380
+
1381 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1382 queue_path(f, &qe);
+
1383
+
1384 do {
+
1385 pthread_cond_wait(&qe.cond, &f->lock);
+
1386 } while (node->nlookup == nlookup && node->treelock);
+
1387
+
1388 dequeue_path(f, &qe);
+
1389 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1390 }
+
1391
+
1392 assert(node->nlookup >= nlookup);
+
1393 node->nlookup -= nlookup;
+
1394 if (!node->nlookup) {
+
1395 unref_node(f, node);
+
1396 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1397 set_forget_time(f, node);
+
1398 }
+
1399 pthread_mutex_unlock(&f->lock);
+
1400}
+
1401
+
1402static void unlink_node(struct fuse *f, struct node *node)
+
1403{
+
1404 if (f->conf.remember) {
+
1405 assert(node->nlookup > 1);
+
1406 node->nlookup--;
+
1407 }
+
1408 unhash_name(f, node);
+
1409}
+
1410
+
1411static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1412{
+
1413 struct node *node;
+
1414
+
1415 pthread_mutex_lock(&f->lock);
+
1416 node = lookup_node(f, dir, name);
+
1417 if (node != NULL)
+
1418 unlink_node(f, node);
+
1419 pthread_mutex_unlock(&f->lock);
+
1420}
+
1421
+
1422static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1423 fuse_ino_t newdir, const char *newname, int hide)
+
1424{
+
1425 struct node *node;
+
1426 struct node *newnode;
+
1427 int err = 0;
+
1428
+
1429 pthread_mutex_lock(&f->lock);
+
1430 node = lookup_node(f, olddir, oldname);
+
1431 newnode = lookup_node(f, newdir, newname);
+
1432 if (node == NULL)
+
1433 goto out;
+
1434
+
1435 if (newnode != NULL) {
+
1436 if (hide) {
+
1437 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1438 err = -EBUSY;
+
1439 goto out;
+
1440 }
+
1441 unlink_node(f, newnode);
+
1442 }
+
1443
+
1444 unhash_name(f, node);
+
1445 if (hash_name(f, node, newdir, newname) == -1) {
+
1446 err = -ENOMEM;
+
1447 goto out;
+
1448 }
+
1449
+
1450 if (hide)
+
1451 node->is_hidden = 1;
+
1452
+
1453out:
+
1454 pthread_mutex_unlock(&f->lock);
+
1455 return err;
+
1456}
+
1457
+
1458static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1459 fuse_ino_t newdir, const char *newname)
+
1460{
+
1461 struct node *oldnode;
+
1462 struct node *newnode;
+
1463 int err;
+
1464
+
1465 pthread_mutex_lock(&f->lock);
+
1466 oldnode = lookup_node(f, olddir, oldname);
+
1467 newnode = lookup_node(f, newdir, newname);
+
1468
+
1469 if (oldnode)
+
1470 unhash_name(f, oldnode);
+
1471 if (newnode)
+
1472 unhash_name(f, newnode);
+
1473
+
1474 err = -ENOMEM;
+
1475 if (oldnode) {
+
1476 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1477 goto out;
+
1478 }
+
1479 if (newnode) {
+
1480 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1481 goto out;
+
1482 }
+
1483 err = 0;
+
1484out:
+
1485 pthread_mutex_unlock(&f->lock);
+
1486 return err;
+
1487}
+
1488
+
1489static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1490{
+
1491 if (!f->conf.use_ino)
+
1492 stbuf->st_ino = nodeid;
+
1493 if (f->conf.set_mode) {
+
1494 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1495 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1496 (0777 & ~f->conf.dmask);
+
1497 else if (f->conf.fmask)
+
1498 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1499 (0777 & ~f->conf.fmask);
+
1500 else
+
1501 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1502 (0777 & ~f->conf.umask);
+
1503 }
+
1504 if (f->conf.set_uid)
+
1505 stbuf->st_uid = f->conf.uid;
+
1506 if (f->conf.set_gid)
+
1507 stbuf->st_gid = f->conf.gid;
+
1508}
+
1509
+
1510static struct fuse *req_fuse(fuse_req_t req)
+
1511{
+
1512 return (struct fuse *) fuse_req_userdata(req);
+
1513}
+
1514
+
1515static void fuse_intr_sighandler(int sig)
+
1516{
+
1517 (void) sig;
+
1518 /* Nothing to do */
+
1519}
+
1520
+
1521struct fuse_intr_data {
+
1522 pthread_t id;
+
1523 pthread_cond_t cond;
+
1524 int finished;
+
1525};
+
1526
+
1527static void fuse_interrupt(fuse_req_t req, void *d_)
+
1528{
+
1529 struct fuse_intr_data *d = d_;
+
1530 struct fuse *f = req_fuse(req);
+
1531
+
1532 if (d->id == pthread_self())
+
1533 return;
+
1534
+
1535 pthread_mutex_lock(&f->lock);
+
1536 while (!d->finished) {
+
1537 struct timeval now;
+
1538 struct timespec timeout;
+
1539
+
1540 pthread_kill(d->id, f->conf.intr_signal);
+
1541 gettimeofday(&now, NULL);
+
1542 timeout.tv_sec = now.tv_sec + 1;
+
1543 timeout.tv_nsec = now.tv_usec * 1000;
+
1544 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1545 }
+
1546 pthread_mutex_unlock(&f->lock);
+
1547}
+
1548
+
1549static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1550 struct fuse_intr_data *d)
+
1551{
+
1552 pthread_mutex_lock(&f->lock);
+
1553 d->finished = 1;
+
1554 pthread_cond_broadcast(&d->cond);
+
1555 pthread_mutex_unlock(&f->lock);
+
1556 fuse_req_interrupt_func(req, NULL, NULL);
+
1557 pthread_cond_destroy(&d->cond);
+
1558}
+
1559
+
1560static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1561{
+
1562 d->id = pthread_self();
+
1563 pthread_cond_init(&d->cond, NULL);
+
1564 d->finished = 0;
+
1565 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1566}
+
1567
+
1568static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1569 struct fuse_intr_data *d)
+
1570{
+
1571 if (f->conf.intr)
+
1572 fuse_do_finish_interrupt(f, req, d);
+
1573}
+
1574
+
1575static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1576 struct fuse_intr_data *d)
+
1577{
+
1578 if (f->conf.intr)
+
1579 fuse_do_prepare_interrupt(req, d);
+
1580}
+
1581
+
1582static const char* file_info_string(struct fuse_file_info *fi,
+
1583 char* buf, size_t len)
+
1584{
+
1585 if(fi == NULL)
+
1586 return "NULL";
+
1587 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1588 return buf;
+
1589}
+
1590
+
1591int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1592 struct fuse_file_info *fi)
+
1593{
+
1594 fuse_get_context()->private_data = fs->user_data;
+
1595 if (fs->op.getattr) {
+
1596 if (fs->debug) {
+
1597 char buf[10];
+
1598 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1599 file_info_string(fi, buf, sizeof(buf)),
+
1600 path);
+
1601 }
+
1602 return fs->op.getattr(path, buf, fi);
+
1603 } else {
+
1604 return -ENOSYS;
+
1605 }
+
1606}
+
1607
+
1608int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1609 const char *newpath, unsigned int flags)
+
1610{
+
1611 fuse_get_context()->private_data = fs->user_data;
+
1612 if (fs->op.rename) {
+
1613 if (fs->debug)
+
1614 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1615 flags);
+
1616
+
1617 return fs->op.rename(oldpath, newpath, flags);
+
1618 } else {
+
1619 return -ENOSYS;
+
1620 }
+
1621}
+
1622
+
1623int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1624{
+
1625 fuse_get_context()->private_data = fs->user_data;
+
1626 if (fs->op.unlink) {
+
1627 if (fs->debug)
+
1628 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1629
+
1630 return fs->op.unlink(path);
+
1631 } else {
+
1632 return -ENOSYS;
+
1633 }
+
1634}
+
1635
+
1636int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1637{
+
1638 fuse_get_context()->private_data = fs->user_data;
+
1639 if (fs->op.rmdir) {
+
1640 if (fs->debug)
+
1641 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1642
+
1643 return fs->op.rmdir(path);
+
1644 } else {
+
1645 return -ENOSYS;
+
1646 }
+
1647}
+
1648
+
1649int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1650{
+
1651 fuse_get_context()->private_data = fs->user_data;
+
1652 if (fs->op.symlink) {
+
1653 if (fs->debug)
+
1654 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1655
+
1656 return fs->op.symlink(linkname, path);
+
1657 } else {
+
1658 return -ENOSYS;
+
1659 }
+
1660}
+
1661
+
1662int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1663{
+
1664 fuse_get_context()->private_data = fs->user_data;
+
1665 if (fs->op.link) {
+
1666 if (fs->debug)
+
1667 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1668
+
1669 return fs->op.link(oldpath, newpath);
+
1670 } else {
+
1671 return -ENOSYS;
+
1672 }
+
1673}
+
1674
+
1675int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1676 struct fuse_file_info *fi)
+
1677{
+
1678 fuse_get_context()->private_data = fs->user_data;
+
1679 if (fs->op.release) {
+
1680 if (fs->debug)
+
1681 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1682 fi->flush ? "+flush" : "",
+
1683 (unsigned long long) fi->fh, fi->flags);
+
1684
+
1685 return fs->op.release(path, fi);
+
1686 } else {
+
1687 return 0;
+
1688 }
+
1689}
+
1690
+
1691int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1692 struct fuse_file_info *fi)
+
1693{
+
1694 fuse_get_context()->private_data = fs->user_data;
+
1695 if (fs->op.opendir) {
+
1696 int err;
+
1697
+
1698 if (fs->debug)
+
1699 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1700 path);
+
1701
+
1702 err = fs->op.opendir(path, fi);
+
1703
+
1704 if (fs->debug && !err)
+
1705 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1706 (unsigned long long) fi->fh, fi->flags, path);
+
1707
+
1708 return err;
+
1709 } else {
+
1710 return 0;
+
1711 }
+
1712}
+
1713
+
1714int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1715 struct fuse_file_info *fi)
+
1716{
+
1717 fuse_get_context()->private_data = fs->user_data;
+
1718 if (fs->op.open) {
+
1719 int err;
+
1720
+
1721 if (fs->debug)
+
1722 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1723 path);
+
1724
+
1725 err = fs->op.open(path, fi);
+
1726
+
1727 if (fs->debug && !err)
+
1728 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1729 (unsigned long long) fi->fh, fi->flags, path);
+
1730
+
1731 return err;
+
1732 } else {
+
1733 return 0;
+
1734 }
+
1735}
+
1736
+
1737static void fuse_free_buf(struct fuse_bufvec *buf)
+
1738{
+
1739 if (buf != NULL) {
+
1740 size_t i;
+
1741
+
1742 for (i = 0; i < buf->count; i++)
+
1743 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1744 free(buf->buf[i].mem);
+
1745 free(buf);
+
1746 }
+
1747}
+
1748
+
1749int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1750 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1751 struct fuse_file_info *fi)
+
1752{
+
1753 fuse_get_context()->private_data = fs->user_data;
+
1754 if (fs->op.read || fs->op.read_buf) {
+
1755 int res;
+
1756
+
1757 if (fs->debug)
+
1758 fuse_log(FUSE_LOG_DEBUG,
+
1759 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1760 (unsigned long long) fi->fh,
+
1761 size, (unsigned long long) off, fi->flags);
+
1762
+
1763 if (fs->op.read_buf) {
+
1764 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1765 } else {
+
1766 struct fuse_bufvec *buf;
+
1767 void *mem;
+
1768
+
1769 buf = malloc(sizeof(struct fuse_bufvec));
+
1770 if (buf == NULL)
+
1771 return -ENOMEM;
+
1772
+
1773 mem = malloc(size);
+
1774 if (mem == NULL) {
+
1775 free(buf);
+
1776 return -ENOMEM;
+
1777 }
+
1778 *buf = FUSE_BUFVEC_INIT(size);
+
1779 buf->buf[0].mem = mem;
+
1780 *bufp = buf;
+
1781
+
1782 res = fs->op.read(path, mem, size, off, fi);
+
1783 if (res >= 0)
+
1784 buf->buf[0].size = res;
+
1785 }
+
1786
+
1787 if (fs->debug && res >= 0)
+
1788 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1789 (unsigned long long) fi->fh,
+
1790 fuse_buf_size(*bufp),
+
1791 (unsigned long long) off);
+
1792 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1793 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1794
+
1795 if (res < 0)
+
1796 return res;
+
1797
+
1798 return 0;
+
1799 } else {
+
1800 return -ENOSYS;
+
1801 }
+
1802}
+
1803
+
1804int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1805 off_t off, struct fuse_file_info *fi)
+
1806{
+
1807 fuse_get_context()->private_data = fs->user_data;
+
1808 if (fs->op.read || fs->op.read_buf) {
+
1809 int res;
+
1810
+
1811 if (fs->debug)
+
1812 fuse_log(FUSE_LOG_DEBUG,
+
1813 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1814 (unsigned long long) fi->fh,
+
1815 size, (unsigned long long) off, fi->flags);
+
1816
+
1817 if (fs->op.read_buf) {
+
1818 struct fuse_bufvec *buf = NULL;
+
1819
+
1820 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1821 if (res == 0) {
+
1822 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1823
+
1824 dst.buf[0].mem = mem;
+
1825 res = fuse_buf_copy(&dst, buf, 0);
+
1826 }
+
1827 fuse_free_buf(buf);
+
1828 } else {
+
1829 res = fs->op.read(path, mem, size, off, fi);
+
1830 }
+
1831
+
1832 if (fs->debug && res >= 0)
+
1833 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1834 (unsigned long long) fi->fh,
+
1835 res,
+
1836 (unsigned long long) off);
+
1837 if (res >= 0 && res > (int) size)
+
1838 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1839
+
1840 return res;
+
1841 } else {
+
1842 return -ENOSYS;
+
1843 }
+
1844}
+
1845
+
1846int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1847 struct fuse_bufvec *buf, off_t off,
+
1848 struct fuse_file_info *fi)
+
1849{
+
1850 fuse_get_context()->private_data = fs->user_data;
+
1851 if (fs->op.write_buf || fs->op.write) {
+
1852 int res;
+
1853 size_t size = fuse_buf_size(buf);
+
1854
+
1855 assert(buf->idx == 0 && buf->off == 0);
+
1856 if (fs->debug)
+
1857 fuse_log(FUSE_LOG_DEBUG,
+
1858 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1859 fi->writepage ? "page" : "",
+
1860 (unsigned long long) fi->fh,
+
1861 size,
+
1862 (unsigned long long) off,
+
1863 fi->flags);
+
1864
+
1865 if (fs->op.write_buf) {
+
1866 res = fs->op.write_buf(path, buf, off, fi);
+
1867 } else {
+
1868 void *mem = NULL;
+
1869 struct fuse_buf *flatbuf;
+
1870 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1871
+
1872 if (buf->count == 1 &&
+
1873 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1874 flatbuf = &buf->buf[0];
+
1875 } else {
+
1876 res = -ENOMEM;
+
1877 mem = malloc(size);
+
1878 if (mem == NULL)
+
1879 goto out;
+
1880
+
1881 tmp.buf[0].mem = mem;
+
1882 res = fuse_buf_copy(&tmp, buf, 0);
+
1883 if (res <= 0)
+
1884 goto out_free;
+
1885
+
1886 tmp.buf[0].size = res;
+
1887 flatbuf = &tmp.buf[0];
+
1888 }
+
1889
+
1890 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1891 off, fi);
+
1892out_free:
+
1893 free(mem);
+
1894 }
+
1895out:
+
1896 if (fs->debug && res >= 0)
+
1897 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1898 fi->writepage ? "page" : "",
+
1899 (unsigned long long) fi->fh, res,
+
1900 (unsigned long long) off);
+
1901 if (res > (int) size)
+
1902 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1903
+
1904 return res;
+
1905 } else {
+
1906 return -ENOSYS;
+
1907 }
+
1908}
+
1909
+
1910int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1911 size_t size, off_t off, struct fuse_file_info *fi)
+
1912{
+
1913 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1914
+
1915 bufv.buf[0].mem = (void *) mem;
+
1916
+
1917 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1918}
+
1919
+
1920int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1921 struct fuse_file_info *fi)
+
1922{
+
1923 fuse_get_context()->private_data = fs->user_data;
+
1924 if (fs->op.fsync) {
+
1925 if (fs->debug)
+
1926 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1927 (unsigned long long) fi->fh, datasync);
+
1928
+
1929 return fs->op.fsync(path, datasync, fi);
+
1930 } else {
+
1931 return -ENOSYS;
+
1932 }
+
1933}
+
1934
+
1935int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1936 struct fuse_file_info *fi)
+
1937{
+
1938 fuse_get_context()->private_data = fs->user_data;
+
1939 if (fs->op.fsyncdir) {
+
1940 if (fs->debug)
+
1941 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1942 (unsigned long long) fi->fh, datasync);
+
1943
+
1944 return fs->op.fsyncdir(path, datasync, fi);
+
1945 } else {
+
1946 return -ENOSYS;
+
1947 }
+
1948}
+
1949
+
1950int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1951 struct fuse_file_info *fi)
+
1952{
+
1953 fuse_get_context()->private_data = fs->user_data;
+
1954 if (fs->op.flush) {
+
1955 if (fs->debug)
+
1956 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1957 (unsigned long long) fi->fh);
+
1958
+
1959 return fs->op.flush(path, fi);
+
1960 } else {
+
1961 return -ENOSYS;
+
1962 }
+
1963}
+
1964
+
1965int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1966{
+
1967 fuse_get_context()->private_data = fs->user_data;
+
1968 if (fs->op.statfs) {
+
1969 if (fs->debug)
+
1970 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1971
+
1972 return fs->op.statfs(path, buf);
+
1973 } else {
+
1974 buf->f_namemax = 255;
+
1975 buf->f_bsize = 512;
+
1976 return 0;
+
1977 }
+
1978}
+
1979
+
1980int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1981 struct fuse_file_info *fi)
+
1982{
+
1983 fuse_get_context()->private_data = fs->user_data;
+
1984 if (fs->op.releasedir) {
+
1985 if (fs->debug)
+
1986 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1987 (unsigned long long) fi->fh, fi->flags);
+
1988
+
1989 return fs->op.releasedir(path, fi);
+
1990 } else {
+
1991 return 0;
+
1992 }
+
1993}
+
1994
+
1995int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1996 fuse_fill_dir_t filler, off_t off,
+
1997 struct fuse_file_info *fi,
+
1998 enum fuse_readdir_flags flags)
+
1999{
+
2000 fuse_get_context()->private_data = fs->user_data;
+
2001 if (fs->op.readdir) {
+
2002 if (fs->debug) {
+
2003 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2004 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2005 (unsigned long long) fi->fh,
+
2006 (unsigned long long) off);
+
2007 }
+
2008
+
2009 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2010 } else {
+
2011 return -ENOSYS;
+
2012 }
+
2013}
+
2014
+
2015int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2016 struct fuse_file_info *fi)
+
2017{
+
2018 fuse_get_context()->private_data = fs->user_data;
+
2019 if (fs->op.create) {
+
2020 int err;
+
2021
+
2022 if (fs->debug)
+
2023 fuse_log(FUSE_LOG_DEBUG,
+
2024 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2025 fi->flags, path, mode,
+
2026 fuse_get_context()->umask);
+
2027
+
2028 err = fs->op.create(path, mode, fi);
+
2029
+
2030 if (fs->debug && !err)
+
2031 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2032 (unsigned long long) fi->fh, fi->flags, path);
+
2033
+
2034 return err;
+
2035 } else {
+
2036 return -ENOSYS;
+
2037 }
+
2038}
+
2039
+
2040int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2041 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2042{
+
2043 fuse_get_context()->private_data = fs->user_data;
+
2044 if (fs->op.lock) {
+
2045 if (fs->debug)
+
2046 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2047 (unsigned long long) fi->fh,
+
2048 (cmd == F_GETLK ? "F_GETLK" :
+
2049 (cmd == F_SETLK ? "F_SETLK" :
+
2050 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2051 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2052 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2053 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2054 "???"))),
+
2055 (unsigned long long) lock->l_start,
+
2056 (unsigned long long) lock->l_len,
+
2057 (unsigned long long) lock->l_pid);
+
2058
+
2059 return fs->op.lock(path, fi, cmd, lock);
+
2060 } else {
+
2061 return -ENOSYS;
+
2062 }
+
2063}
+
2064
+
2065int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2066 struct fuse_file_info *fi, int op)
+
2067{
+
2068 fuse_get_context()->private_data = fs->user_data;
+
2069 if (fs->op.flock) {
+
2070 if (fs->debug) {
+
2071 int xop = op & ~LOCK_NB;
+
2072
+
2073 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2074 (unsigned long long) fi->fh,
+
2075 xop == LOCK_SH ? "LOCK_SH" :
+
2076 (xop == LOCK_EX ? "LOCK_EX" :
+
2077 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2078 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2079 }
+
2080 return fs->op.flock(path, fi, op);
+
2081 } else {
+
2082 return -ENOSYS;
+
2083 }
+
2084}
+
2085
+
2086int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2087 gid_t gid, struct fuse_file_info *fi)
+
2088{
+
2089 fuse_get_context()->private_data = fs->user_data;
+
2090 if (fs->op.chown) {
+
2091 if (fs->debug) {
+
2092 char buf[10];
+
2093 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2094 file_info_string(fi, buf, sizeof(buf)),
+
2095 path, (unsigned long) uid, (unsigned long) gid);
+
2096 }
+
2097 return fs->op.chown(path, uid, gid, fi);
+
2098 } else {
+
2099 return -ENOSYS;
+
2100 }
+
2101}
+
2102
+
2103int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2104 struct fuse_file_info *fi)
+
2105{
+
2106 fuse_get_context()->private_data = fs->user_data;
+
2107 if (fs->op.truncate) {
+
2108 if (fs->debug) {
+
2109 char buf[10];
+
2110 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2111 file_info_string(fi, buf, sizeof(buf)),
+
2112 (unsigned long long) size);
+
2113 }
+
2114 return fs->op.truncate(path, size, fi);
+
2115 } else {
+
2116 return -ENOSYS;
+
2117 }
+
2118}
+
2119
+
2120int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2121 const struct timespec tv[2], struct fuse_file_info *fi)
+
2122{
+
2123 fuse_get_context()->private_data = fs->user_data;
+
2124 if (fs->op.utimens) {
+
2125 if (fs->debug) {
+
2126 char buf[10];
+
2127 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n",
+
2128 file_info_string(fi, buf, sizeof(buf)),
+
2129 path, tv[0].tv_sec, tv[0].tv_nsec,
+
2130 tv[1].tv_sec, tv[1].tv_nsec);
+
2131 }
+
2132 return fs->op.utimens(path, tv, fi);
+
2133 } else {
+
2134 return -ENOSYS;
+
2135 }
+
2136}
+
2137
+
2138int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2139{
+
2140 fuse_get_context()->private_data = fs->user_data;
+
2141 if (fs->op.access) {
+
2142 if (fs->debug)
+
2143 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2144
+
2145 return fs->op.access(path, mask);
+
2146 } else {
+
2147 return -ENOSYS;
+
2148 }
+
2149}
+
2150
+
2151int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2152 size_t len)
+
2153{
+
2154 fuse_get_context()->private_data = fs->user_data;
+
2155 if (fs->op.readlink) {
+
2156 if (fs->debug)
+
2157 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2158 (unsigned long) len);
+
2159
+
2160 return fs->op.readlink(path, buf, len);
+
2161 } else {
+
2162 return -ENOSYS;
+
2163 }
+
2164}
+
2165
+
2166int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2167 dev_t rdev)
+
2168{
+
2169 fuse_get_context()->private_data = fs->user_data;
+
2170 if (fs->op.mknod) {
+
2171 if (fs->debug)
+
2172 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2173 path, mode, (unsigned long long) rdev,
+
2174 fuse_get_context()->umask);
+
2175
+
2176 return fs->op.mknod(path, mode, rdev);
+
2177 } else {
+
2178 return -ENOSYS;
+
2179 }
+
2180}
+
2181
+
2182int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2183{
+
2184 fuse_get_context()->private_data = fs->user_data;
+
2185 if (fs->op.mkdir) {
+
2186 if (fs->debug)
+
2187 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2188 path, mode, fuse_get_context()->umask);
+
2189
+
2190 return fs->op.mkdir(path, mode);
+
2191 } else {
+
2192 return -ENOSYS;
+
2193 }
+
2194}
+
2195
+
2196int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2197 const char *value, size_t size, int flags)
+
2198{
+
2199 fuse_get_context()->private_data = fs->user_data;
+
2200 if (fs->op.setxattr) {
+
2201 if (fs->debug)
+
2202 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2203 path, name, (unsigned long) size, flags);
+
2204
+
2205 return fs->op.setxattr(path, name, value, size, flags);
+
2206 } else {
+
2207 return -ENOSYS;
+
2208 }
+
2209}
+
2210
+
2211int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2212 char *value, size_t size)
+
2213{
+
2214 fuse_get_context()->private_data = fs->user_data;
+
2215 if (fs->op.getxattr) {
+
2216 if (fs->debug)
+
2217 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2218 path, name, (unsigned long) size);
+
2219
+
2220 return fs->op.getxattr(path, name, value, size);
+
2221 } else {
+
2222 return -ENOSYS;
+
2223 }
+
2224}
+
2225
+
2226int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2227 size_t size)
+
2228{
+
2229 fuse_get_context()->private_data = fs->user_data;
+
2230 if (fs->op.listxattr) {
+
2231 if (fs->debug)
+
2232 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2233 path, (unsigned long) size);
+
2234
+
2235 return fs->op.listxattr(path, list, size);
+
2236 } else {
+
2237 return -ENOSYS;
+
2238 }
+
2239}
+
2240
+
2241int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2242 uint64_t *idx)
+
2243{
+
2244 fuse_get_context()->private_data = fs->user_data;
+
2245 if (fs->op.bmap) {
+
2246 if (fs->debug)
+
2247 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2248 path, (unsigned long) blocksize,
+
2249 (unsigned long long) *idx);
+
2250
+
2251 return fs->op.bmap(path, blocksize, idx);
+
2252 } else {
+
2253 return -ENOSYS;
+
2254 }
+
2255}
+
2256
+
2257int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2258{
+
2259 fuse_get_context()->private_data = fs->user_data;
+
2260 if (fs->op.removexattr) {
+
2261 if (fs->debug)
+
2262 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2263
+
2264 return fs->op.removexattr(path, name);
+
2265 } else {
+
2266 return -ENOSYS;
+
2267 }
+
2268}
+
2269
+
2270int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2271 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2272 void *data)
+
2273{
+
2274 fuse_get_context()->private_data = fs->user_data;
+
2275 if (fs->op.ioctl) {
+
2276 if (fs->debug)
+
2277 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2278 (unsigned long long) fi->fh, cmd, flags);
+
2279
+
2280 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2281 } else
+
2282 return -ENOSYS;
+
2283}
+
2284
+
2285int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2286 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2287 unsigned *reventsp)
+
2288{
+
2289 fuse_get_context()->private_data = fs->user_data;
+
2290 if (fs->op.poll) {
+
2291 int res;
+
2292
+
2293 if (fs->debug)
+
2294 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2295 (unsigned long long) fi->fh, ph,
+
2296 fi->poll_events);
+
2297
+
2298 res = fs->op.poll(path, fi, ph, reventsp);
+
2299
+
2300 if (fs->debug && !res)
+
2301 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2302 (unsigned long long) fi->fh, *reventsp);
+
2303
+
2304 return res;
+
2305 } else
+
2306 return -ENOSYS;
+
2307}
+
2308
+
2309int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2310 off_t offset, off_t length, struct fuse_file_info *fi)
+
2311{
+
2312 fuse_get_context()->private_data = fs->user_data;
+
2313 if (fs->op.fallocate) {
+
2314 if (fs->debug)
+
2315 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2316 path,
+
2317 mode,
+
2318 (unsigned long long) offset,
+
2319 (unsigned long long) length);
+
2320
+
2321 return fs->op.fallocate(path, mode, offset, length, fi);
+
2322 } else
+
2323 return -ENOSYS;
+
2324}
+
2325
+
2326ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2327 struct fuse_file_info *fi_in, off_t off_in,
+
2328 const char *path_out,
+
2329 struct fuse_file_info *fi_out, off_t off_out,
+
2330 size_t len, int flags)
+
2331{
+
2332 fuse_get_context()->private_data = fs->user_data;
+
2333 if (fs->op.copy_file_range) {
+
2334 if (fs->debug)
+
2335 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2336 "%s:%llu, length: %llu\n",
+
2337 path_in,
+
2338 (unsigned long long) off_in,
+
2339 path_out,
+
2340 (unsigned long long) off_out,
+
2341 (unsigned long long) len);
+
2342
+
2343 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2344 fi_out, off_out, len, flags);
+
2345 } else
+
2346 return -ENOSYS;
+
2347}
+
2348
+
2349off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2350 struct fuse_file_info *fi)
+
2351{
+
2352 fuse_get_context()->private_data = fs->user_data;
+
2353 if (fs->op.lseek) {
+
2354 if (fs->debug) {
+
2355 char buf[10];
+
2356 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2357 file_info_string(fi, buf, sizeof(buf)),
+
2358 (unsigned long long) off, whence);
+
2359 }
+
2360 return fs->op.lseek(path, off, whence, fi);
+
2361 } else {
+
2362 return -ENOSYS;
+
2363 }
+
2364}
+
2365
+
2366static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2367{
+
2368 struct node *node;
+
2369 int isopen = 0;
+
2370 pthread_mutex_lock(&f->lock);
+
2371 node = lookup_node(f, dir, name);
+
2372 if (node && node->open_count > 0)
+
2373 isopen = 1;
+
2374 pthread_mutex_unlock(&f->lock);
+
2375 return isopen;
+
2376}
+
2377
+
2378static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2379 char *newname, size_t bufsize)
+
2380{
+
2381 struct stat buf;
+
2382 struct node *node;
+
2383 struct node *newnode;
+
2384 char *newpath;
+
2385 int res;
+
2386 int failctr = 10;
+
2387
+
2388 do {
+
2389 pthread_mutex_lock(&f->lock);
+
2390 node = lookup_node(f, dir, oldname);
+
2391 if (node == NULL) {
+
2392 pthread_mutex_unlock(&f->lock);
+
2393 return NULL;
+
2394 }
+
2395 do {
+
2396 f->hidectr ++;
+
2397 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2398 (unsigned int) node->nodeid, f->hidectr);
+
2399 newnode = lookup_node(f, dir, newname);
+
2400 } while(newnode);
+
2401
+
2402 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2403 pthread_mutex_unlock(&f->lock);
+
2404 if (res)
+
2405 break;
+
2406
+
2407 memset(&buf, 0, sizeof(buf));
+
2408 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2409 if (res == -ENOENT)
+
2410 break;
+
2411 free(newpath);
+
2412 newpath = NULL;
+
2413 } while(res == 0 && --failctr);
+
2414
+
2415 return newpath;
+
2416}
+
2417
+
2418static int hide_node(struct fuse *f, const char *oldpath,
+
2419 fuse_ino_t dir, const char *oldname)
+
2420{
+
2421 char newname[64];
+
2422 char *newpath;
+
2423 int err = -EBUSY;
+
2424
+
2425 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2426 if (newpath) {
+
2427 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2428 if (!err)
+
2429 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2430 free(newpath);
+
2431 }
+
2432 return err;
+
2433}
+
2434
+
2435static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2436{
+
2437 return stbuf->st_mtime == ts->tv_sec &&
+
2438 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2439}
+
2440
+
2441#ifndef CLOCK_MONOTONIC
+
2442#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2443#endif
+
2444
+
2445static void curr_time(struct timespec *now)
+
2446{
+
2447 static clockid_t clockid = CLOCK_MONOTONIC;
+
2448 int res = clock_gettime(clockid, now);
+
2449 if (res == -1 && errno == EINVAL) {
+
2450 clockid = CLOCK_REALTIME;
+
2451 res = clock_gettime(clockid, now);
+
2452 }
+
2453 if (res == -1) {
+
2454 perror("fuse: clock_gettime");
+
2455 abort();
+
2456 }
+
2457}
+
2458
+
2459static void update_stat(struct node *node, const struct stat *stbuf)
+
2460{
+
2461 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2462 stbuf->st_size != node->size))
+
2463 node->cache_valid = 0;
+
2464 node->mtime.tv_sec = stbuf->st_mtime;
+
2465 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2466 node->size = stbuf->st_size;
+
2467 curr_time(&node->stat_updated);
+
2468}
+
2469
+
2470static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2471 struct fuse_entry_param *e)
+
2472{
+
2473 struct node *node;
+
2474
+
2475 node = find_node(f, nodeid, name);
+
2476 if (node == NULL)
+
2477 return -ENOMEM;
+
2478
+
2479 e->ino = node->nodeid;
+
2480 e->generation = node->generation;
+
2481 e->entry_timeout = f->conf.entry_timeout;
+
2482 e->attr_timeout = f->conf.attr_timeout;
+
2483 if (f->conf.auto_cache) {
+
2484 pthread_mutex_lock(&f->lock);
+
2485 update_stat(node, &e->attr);
+
2486 pthread_mutex_unlock(&f->lock);
+
2487 }
+
2488 set_stat(f, e->ino, &e->attr);
+
2489 return 0;
+
2490}
+
2491
+
2492static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2493 const char *name, const char *path,
+
2494 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2495{
+
2496 int res;
+
2497
+
2498 memset(e, 0, sizeof(struct fuse_entry_param));
+
2499 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2500 if (res == 0) {
+
2501 res = do_lookup(f, nodeid, name, e);
+
2502 if (res == 0 && f->conf.debug) {
+
2503 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2504 (unsigned long long) e->ino);
+
2505 }
+
2506 }
+
2507 return res;
+
2508}
+
2509
+
2510static struct fuse_context_i *fuse_get_context_internal(void)
+
2511{
+
2512 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2513}
+
2514
+
2515static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2516{
+
2517 struct fuse_context_i *c = fuse_get_context_internal();
+
2518 if (c == NULL) {
+
2519 c = (struct fuse_context_i *)
+
2520 calloc(1, sizeof(struct fuse_context_i));
+
2521 if (c == NULL) {
+
2522 /* This is hard to deal with properly, so just
+
2523 abort. If memory is so low that the
+
2524 context cannot be allocated, there's not
+
2525 much hope for the filesystem anyway */
+
2526 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2527 abort();
+
2528 }
+
2529 pthread_setspecific(fuse_context_key, c);
+
2530 } else {
+
2531 memset(c, 0, sizeof(*c));
+
2532 }
+
2533 c->ctx.fuse = f;
+
2534
+
2535 return c;
+
2536}
+
2537
+
2538static void fuse_freecontext(void *data)
+
2539{
+
2540 free(data);
+
2541}
+
2542
+
2543static int fuse_create_context_key(void)
+
2544{
+
2545 int err = 0;
+
2546 pthread_mutex_lock(&fuse_context_lock);
+
2547 if (!fuse_context_ref) {
+
2548 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2549 if (err) {
+
2550 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2551 strerror(err));
+
2552 pthread_mutex_unlock(&fuse_context_lock);
+
2553 return -1;
+
2554 }
+
2555 }
+
2556 fuse_context_ref++;
+
2557 pthread_mutex_unlock(&fuse_context_lock);
+
2558 return 0;
+
2559}
+
2560
+
2561static void fuse_delete_context_key(void)
+
2562{
+
2563 pthread_mutex_lock(&fuse_context_lock);
+
2564 fuse_context_ref--;
+
2565 if (!fuse_context_ref) {
+
2566 free(pthread_getspecific(fuse_context_key));
+
2567 pthread_key_delete(fuse_context_key);
+
2568 }
+
2569 pthread_mutex_unlock(&fuse_context_lock);
+
2570}
+
2571
+
2572static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2573{
+
2574 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2575 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2576 c->req = req;
+
2577 c->ctx.uid = ctx->uid;
+
2578 c->ctx.gid = ctx->gid;
+
2579 c->ctx.pid = ctx->pid;
+
2580 c->ctx.umask = ctx->umask;
+
2581 return c->ctx.fuse;
+
2582}
+
2583
+
2584static inline void reply_err(fuse_req_t req, int err)
+
2585{
+
2586 /* fuse_reply_err() uses non-negated errno values */
+
2587 fuse_reply_err(req, -err);
+
2588}
+
2589
+
2590static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2591 int err)
+
2592{
+
2593 if (!err) {
+
2594 struct fuse *f = req_fuse(req);
+
2595 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2596 /* Skip forget for negative result */
+
2597 if (e->ino != 0)
+
2598 forget_node(f, e->ino, 1);
+
2599 }
+
2600 } else
+
2601 reply_err(req, err);
+
2602}
+
2603
+
2604void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2605 struct fuse_config *cfg)
+
2606{
+
2607 fuse_get_context()->private_data = fs->user_data;
+
2608 if (!fs->op.write_buf)
+
2609 conn->want &= ~FUSE_CAP_SPLICE_READ;
+
2610 if (!fs->op.lock)
+
2611 conn->want &= ~FUSE_CAP_POSIX_LOCKS;
+
2612 if (!fs->op.flock)
+
2613 conn->want &= ~FUSE_CAP_FLOCK_LOCKS;
+
2614 if (fs->op.init)
+
2615 fs->user_data = fs->op.init(conn, cfg);
+
2616}
+
2617
+
2618static int fuse_init_intr_signal(int signum, int *installed);
+
2619
+
2620static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2621{
+
2622 struct fuse *f = (struct fuse *) data;
+
2623
+
2624 fuse_create_context(f);
+
2625 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
+
2626 fuse_fs_init(f->fs, conn, &f->conf);
+
2627
+
2628 if (f->conf.intr) {
+
2629 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2630 &f->intr_installed) == -1)
+
2631 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2632 } else {
+
2633 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2634 conn->no_interrupt = 1;
+
2635 }
+
2636}
+
2637
+
2638void fuse_fs_destroy(struct fuse_fs *fs)
+
2639{
+
2640 fuse_get_context()->private_data = fs->user_data;
+
2641 if (fs->op.destroy)
+
2642 fs->op.destroy(fs->user_data);
+
2643}
+
2644
+
2645static void fuse_lib_destroy(void *data)
+
2646{
+
2647 struct fuse *f = (struct fuse *) data;
+
2648
+
2649 fuse_create_context(f);
+
2650 fuse_fs_destroy(f->fs);
+
2651}
+
2652
+
2653static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2654 const char *name)
+
2655{
+
2656 struct fuse *f = req_fuse_prepare(req);
+
2657 struct fuse_entry_param e;
+
2658 char *path;
+
2659 int err;
+
2660 struct node *dot = NULL;
+
2661
+
2662 if (name[0] == '.') {
+
2663 int len = strlen(name);
+
2664
+
2665 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2666 pthread_mutex_lock(&f->lock);
+
2667 if (len == 1) {
+
2668 if (f->conf.debug)
+
2669 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2670 dot = get_node_nocheck(f, parent);
+
2671 if (dot == NULL) {
+
2672 pthread_mutex_unlock(&f->lock);
+
2673 reply_entry(req, &e, -ESTALE);
+
2674 return;
+
2675 }
+
2676 dot->refctr++;
+
2677 } else {
+
2678 if (f->conf.debug)
+
2679 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2680 parent = get_node(f, parent)->parent->nodeid;
+
2681 }
+
2682 pthread_mutex_unlock(&f->lock);
+
2683 name = NULL;
+
2684 }
+
2685 }
+
2686
+
2687 err = get_path_name(f, parent, name, &path);
+
2688 if (!err) {
+
2689 struct fuse_intr_data d;
+
2690 if (f->conf.debug)
+
2691 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2692 fuse_prepare_interrupt(f, req, &d);
+
2693 err = lookup_path(f, parent, name, path, &e, NULL);
+
2694 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2695 e.ino = 0;
+
2696 e.entry_timeout = f->conf.negative_timeout;
+
2697 err = 0;
+
2698 }
+
2699 fuse_finish_interrupt(f, req, &d);
+
2700 free_path(f, parent, path);
+
2701 }
+
2702 if (dot) {
+
2703 pthread_mutex_lock(&f->lock);
+
2704 unref_node(f, dot);
+
2705 pthread_mutex_unlock(&f->lock);
+
2706 }
+
2707 reply_entry(req, &e, err);
+
2708}
+
2709
+
2710static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2711{
+
2712 if (f->conf.debug)
+
2713 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2714 (unsigned long long) nlookup);
+
2715 forget_node(f, ino, nlookup);
+
2716}
+
2717
+
2718static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2719{
+
2720 do_forget(req_fuse(req), ino, nlookup);
+
2721 fuse_reply_none(req);
+
2722}
+
2723
+
2724static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2725 struct fuse_forget_data *forgets)
+
2726{
+
2727 struct fuse *f = req_fuse(req);
+
2728 size_t i;
+
2729
+
2730 for (i = 0; i < count; i++)
+
2731 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2732
+
2733 fuse_reply_none(req);
+
2734}
+
2735
+
2736
+
2737static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2738 struct fuse_file_info *fi)
+
2739{
+
2740 struct fuse *f = req_fuse_prepare(req);
+
2741 struct stat buf;
+
2742 char *path;
+
2743 int err;
+
2744
+
2745 memset(&buf, 0, sizeof(buf));
+
2746
+
2747 if (fi != NULL)
+
2748 err = get_path_nullok(f, ino, &path);
+
2749 else
+
2750 err = get_path(f, ino, &path);
+
2751 if (!err) {
+
2752 struct fuse_intr_data d;
+
2753 fuse_prepare_interrupt(f, req, &d);
+
2754 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2755 fuse_finish_interrupt(f, req, &d);
+
2756 free_path(f, ino, path);
+
2757 }
+
2758 if (!err) {
+
2759 struct node *node;
+
2760
+
2761 pthread_mutex_lock(&f->lock);
+
2762 node = get_node(f, ino);
+
2763 if (node->is_hidden && buf.st_nlink > 0)
+
2764 buf.st_nlink--;
+
2765 if (f->conf.auto_cache)
+
2766 update_stat(node, &buf);
+
2767 pthread_mutex_unlock(&f->lock);
+
2768 set_stat(f, ino, &buf);
+
2769 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2770 } else
+
2771 reply_err(req, err);
+
2772}
+
2773
+
2774int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2775 struct fuse_file_info *fi)
+
2776{
+
2777 fuse_get_context()->private_data = fs->user_data;
+
2778 if (fs->op.chmod) {
+
2779 if (fs->debug) {
+
2780 char buf[10];
+
2781 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2782 file_info_string(fi, buf, sizeof(buf)),
+
2783 path, (unsigned long long) mode);
+
2784 }
+
2785 return fs->op.chmod(path, mode, fi);
+
2786 }
+
2787 else
+
2788 return -ENOSYS;
+
2789}
+
2790
+
2791static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2792 int valid, struct fuse_file_info *fi)
+
2793{
+
2794 struct fuse *f = req_fuse_prepare(req);
+
2795 struct stat buf;
+
2796 char *path;
+
2797 int err;
+
2798
+
2799 memset(&buf, 0, sizeof(buf));
+
2800 if (fi != NULL)
+
2801 err = get_path_nullok(f, ino, &path);
+
2802 else
+
2803 err = get_path(f, ino, &path);
+
2804 if (!err) {
+
2805 struct fuse_intr_data d;
+
2806 fuse_prepare_interrupt(f, req, &d);
+
2807 err = 0;
+
2808 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2809 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2810 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2811 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2812 attr->st_uid : (uid_t) -1;
+
2813 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2814 attr->st_gid : (gid_t) -1;
+
2815 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2816 }
+
2817 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2818 err = fuse_fs_truncate(f->fs, path,
+
2819 attr->st_size, fi);
+
2820 }
+
2821#ifdef HAVE_UTIMENSAT
+
2822 if (!err &&
+
2823 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2824 struct timespec tv[2];
+
2825
+
2826 tv[0].tv_sec = 0;
+
2827 tv[1].tv_sec = 0;
+
2828 tv[0].tv_nsec = UTIME_OMIT;
+
2829 tv[1].tv_nsec = UTIME_OMIT;
+
2830
+
2831 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2832 tv[0].tv_nsec = UTIME_NOW;
+
2833 else if (valid & FUSE_SET_ATTR_ATIME)
+
2834 tv[0] = attr->st_atim;
+
2835
+
2836 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2837 tv[1].tv_nsec = UTIME_NOW;
+
2838 else if (valid & FUSE_SET_ATTR_MTIME)
+
2839 tv[1] = attr->st_mtim;
+
2840
+
2841 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2842 } else
+
2843#endif
+
2844 if (!err &&
+
2845 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2846 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2847 struct timespec tv[2];
+
2848 tv[0].tv_sec = attr->st_atime;
+
2849 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2850 tv[1].tv_sec = attr->st_mtime;
+
2851 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2852 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2853 }
+
2854 if (!err) {
+
2855 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2856 }
+
2857 fuse_finish_interrupt(f, req, &d);
+
2858 free_path(f, ino, path);
+
2859 }
+
2860 if (!err) {
+
2861 if (f->conf.auto_cache) {
+
2862 pthread_mutex_lock(&f->lock);
+
2863 update_stat(get_node(f, ino), &buf);
+
2864 pthread_mutex_unlock(&f->lock);
+
2865 }
+
2866 set_stat(f, ino, &buf);
+
2867 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2868 } else
+
2869 reply_err(req, err);
+
2870}
+
2871
+
2872static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2873{
+
2874 struct fuse *f = req_fuse_prepare(req);
+
2875 char *path;
+
2876 int err;
+
2877
+
2878 err = get_path(f, ino, &path);
+
2879 if (!err) {
+
2880 struct fuse_intr_data d;
+
2881
+
2882 fuse_prepare_interrupt(f, req, &d);
+
2883 err = fuse_fs_access(f->fs, path, mask);
+
2884 fuse_finish_interrupt(f, req, &d);
+
2885 free_path(f, ino, path);
+
2886 }
+
2887 reply_err(req, err);
+
2888}
+
2889
+
2890static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2891{
+
2892 struct fuse *f = req_fuse_prepare(req);
+
2893 char linkname[PATH_MAX + 1];
+
2894 char *path;
+
2895 int err;
+
2896
+
2897 err = get_path(f, ino, &path);
+
2898 if (!err) {
+
2899 struct fuse_intr_data d;
+
2900 fuse_prepare_interrupt(f, req, &d);
+
2901 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2902 fuse_finish_interrupt(f, req, &d);
+
2903 free_path(f, ino, path);
+
2904 }
+
2905 if (!err) {
+
2906 linkname[PATH_MAX] = '\0';
+
2907 fuse_reply_readlink(req, linkname);
+
2908 } else
+
2909 reply_err(req, err);
+
2910}
+
2911
+
2912static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2913 mode_t mode, dev_t rdev)
+
2914{
+
2915 struct fuse *f = req_fuse_prepare(req);
+
2916 struct fuse_entry_param e;
+
2917 char *path;
+
2918 int err;
+
2919
+
2920 err = get_path_name(f, parent, name, &path);
+
2921 if (!err) {
+
2922 struct fuse_intr_data d;
+
2923
+
2924 fuse_prepare_interrupt(f, req, &d);
+
2925 err = -ENOSYS;
+
2926 if (S_ISREG(mode)) {
+
2927 struct fuse_file_info fi;
+
2928
+
2929 memset(&fi, 0, sizeof(fi));
+
2930 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2931 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2932 if (!err) {
+
2933 err = lookup_path(f, parent, name, path, &e,
+
2934 &fi);
+
2935 fuse_fs_release(f->fs, path, &fi);
+
2936 }
+
2937 }
+
2938 if (err == -ENOSYS) {
+
2939 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2940 if (!err)
+
2941 err = lookup_path(f, parent, name, path, &e,
+
2942 NULL);
+
2943 }
+
2944 fuse_finish_interrupt(f, req, &d);
+
2945 free_path(f, parent, path);
+
2946 }
+
2947 reply_entry(req, &e, err);
+
2948}
+
2949
+
2950static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2951 mode_t mode)
+
2952{
+
2953 struct fuse *f = req_fuse_prepare(req);
+
2954 struct fuse_entry_param e;
+
2955 char *path;
+
2956 int err;
+
2957
+
2958 err = get_path_name(f, parent, name, &path);
+
2959 if (!err) {
+
2960 struct fuse_intr_data d;
+
2961
+
2962 fuse_prepare_interrupt(f, req, &d);
+
2963 err = fuse_fs_mkdir(f->fs, path, mode);
+
2964 if (!err)
+
2965 err = lookup_path(f, parent, name, path, &e, NULL);
+
2966 fuse_finish_interrupt(f, req, &d);
+
2967 free_path(f, parent, path);
+
2968 }
+
2969 reply_entry(req, &e, err);
+
2970}
+
2971
+
2972static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2973 const char *name)
+
2974{
+
2975 struct fuse *f = req_fuse_prepare(req);
+
2976 struct node *wnode;
+
2977 char *path;
+
2978 int err;
+
2979
+
2980 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
2981 if (!err) {
+
2982 struct fuse_intr_data d;
+
2983
+
2984 fuse_prepare_interrupt(f, req, &d);
+
2985 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
2986 err = hide_node(f, path, parent, name);
+
2987 if (!err) {
+
2988 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
2989 if (!is_open(f, parent, wnode->name)) {
+
2990 char *unlinkpath;
+
2991
+
2992 /* get the hidden file path, to unlink it */
+
2993 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
2994 err = fuse_fs_unlink(f->fs, unlinkpath);
+
2995 if (!err)
+
2996 remove_node(f, parent, wnode->name);
+
2997 free(unlinkpath);
+
2998 }
+
2999 }
+
3000 }
+
3001 } else {
+
3002 err = fuse_fs_unlink(f->fs, path);
+
3003 if (!err)
+
3004 remove_node(f, parent, name);
+
3005 }
+
3006 fuse_finish_interrupt(f, req, &d);
+
3007 free_path_wrlock(f, parent, wnode, path);
+
3008 }
+
3009 reply_err(req, err);
+
3010}
+
3011
+
3012static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3013{
+
3014 struct fuse *f = req_fuse_prepare(req);
+
3015 struct node *wnode;
+
3016 char *path;
+
3017 int err;
+
3018
+
3019 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3020 if (!err) {
+
3021 struct fuse_intr_data d;
+
3022
+
3023 fuse_prepare_interrupt(f, req, &d);
+
3024 err = fuse_fs_rmdir(f->fs, path);
+
3025 fuse_finish_interrupt(f, req, &d);
+
3026 if (!err)
+
3027 remove_node(f, parent, name);
+
3028 free_path_wrlock(f, parent, wnode, path);
+
3029 }
+
3030 reply_err(req, err);
+
3031}
+
3032
+
3033static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3034 fuse_ino_t parent, const char *name)
+
3035{
+
3036 struct fuse *f = req_fuse_prepare(req);
+
3037 struct fuse_entry_param e;
+
3038 char *path;
+
3039 int err;
+
3040
+
3041 err = get_path_name(f, parent, name, &path);
+
3042 if (!err) {
+
3043 struct fuse_intr_data d;
+
3044
+
3045 fuse_prepare_interrupt(f, req, &d);
+
3046 err = fuse_fs_symlink(f->fs, linkname, path);
+
3047 if (!err)
+
3048 err = lookup_path(f, parent, name, path, &e, NULL);
+
3049 fuse_finish_interrupt(f, req, &d);
+
3050 free_path(f, parent, path);
+
3051 }
+
3052 reply_entry(req, &e, err);
+
3053}
+
3054
+
3055static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3056 const char *oldname, fuse_ino_t newdir,
+
3057 const char *newname, unsigned int flags)
+
3058{
+
3059 struct fuse *f = req_fuse_prepare(req);
+
3060 char *oldpath;
+
3061 char *newpath;
+
3062 struct node *wnode1;
+
3063 struct node *wnode2;
+
3064 int err;
+
3065
+
3066 err = get_path2(f, olddir, oldname, newdir, newname,
+
3067 &oldpath, &newpath, &wnode1, &wnode2);
+
3068 if (!err) {
+
3069 struct fuse_intr_data d;
+
3070 err = 0;
+
3071 fuse_prepare_interrupt(f, req, &d);
+
3072 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3073 is_open(f, newdir, newname))
+
3074 err = hide_node(f, newpath, newdir, newname);
+
3075 if (!err) {
+
3076 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3077 if (!err) {
+
3078 if (flags & RENAME_EXCHANGE) {
+
3079 err = exchange_node(f, olddir, oldname,
+
3080 newdir, newname);
+
3081 } else {
+
3082 err = rename_node(f, olddir, oldname,
+
3083 newdir, newname, 0);
+
3084 }
+
3085 }
+
3086 }
+
3087 fuse_finish_interrupt(f, req, &d);
+
3088 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3089 }
+
3090 reply_err(req, err);
+
3091}
+
3092
+
3093static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3094 const char *newname)
+
3095{
+
3096 struct fuse *f = req_fuse_prepare(req);
+
3097 struct fuse_entry_param e;
+
3098 char *oldpath;
+
3099 char *newpath;
+
3100 int err;
+
3101
+
3102 err = get_path2(f, ino, NULL, newparent, newname,
+
3103 &oldpath, &newpath, NULL, NULL);
+
3104 if (!err) {
+
3105 struct fuse_intr_data d;
+
3106
+
3107 fuse_prepare_interrupt(f, req, &d);
+
3108 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3109 if (!err)
+
3110 err = lookup_path(f, newparent, newname, newpath,
+
3111 &e, NULL);
+
3112 fuse_finish_interrupt(f, req, &d);
+
3113 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3114 }
+
3115 reply_entry(req, &e, err);
+
3116}
+
3117
+
3118static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3119 struct fuse_file_info *fi)
+
3120{
+
3121 struct node *node;
+
3122 int unlink_hidden = 0;
+
3123
+
3124 fuse_fs_release(f->fs, path, fi);
+
3125
+
3126 pthread_mutex_lock(&f->lock);
+
3127 node = get_node(f, ino);
+
3128 assert(node->open_count > 0);
+
3129 --node->open_count;
+
3130 if (node->is_hidden && !node->open_count) {
+
3131 unlink_hidden = 1;
+
3132 node->is_hidden = 0;
+
3133 }
+
3134 pthread_mutex_unlock(&f->lock);
+
3135
+
3136 if(unlink_hidden) {
+
3137 if (path) {
+
3138 fuse_fs_unlink(f->fs, path);
+
3139 } else if (f->conf.nullpath_ok) {
+
3140 char *unlinkpath;
+
3141
+
3142 if (get_path(f, ino, &unlinkpath) == 0)
+
3143 fuse_fs_unlink(f->fs, unlinkpath);
+
3144
+
3145 free_path(f, ino, unlinkpath);
+
3146 }
+
3147 }
+
3148}
+
3149
+
3150static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3151 const char *name, mode_t mode,
+
3152 struct fuse_file_info *fi)
+
3153{
+
3154 struct fuse *f = req_fuse_prepare(req);
+
3155 struct fuse_intr_data d;
+
3156 struct fuse_entry_param e;
+
3157 char *path;
+
3158 int err;
+
3159
+
3160 err = get_path_name(f, parent, name, &path);
+
3161 if (!err) {
+
3162 fuse_prepare_interrupt(f, req, &d);
+
3163 err = fuse_fs_create(f->fs, path, mode, fi);
+
3164 if (!err) {
+
3165 err = lookup_path(f, parent, name, path, &e, fi);
+
3166 if (err)
+
3167 fuse_fs_release(f->fs, path, fi);
+
3168 else if (!S_ISREG(e.attr.st_mode)) {
+
3169 err = -EIO;
+
3170 fuse_fs_release(f->fs, path, fi);
+
3171 forget_node(f, e.ino, 1);
+
3172 } else {
+
3173 if (f->conf.direct_io)
+
3174 fi->direct_io = 1;
+
3175 if (f->conf.kernel_cache)
+
3176 fi->keep_cache = 1;
+
3177 if (fi->direct_io &&
+
3178 f->conf.parallel_direct_writes)
+
3179 fi->parallel_direct_writes = 1;
+
3180 }
+
3181 }
+
3182 fuse_finish_interrupt(f, req, &d);
+
3183 }
+
3184 if (!err) {
+
3185 pthread_mutex_lock(&f->lock);
+
3186 get_node(f, e.ino)->open_count++;
+
3187 pthread_mutex_unlock(&f->lock);
+
3188 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3189 /* The open syscall was interrupted, so it
+
3190 must be cancelled */
+
3191 fuse_do_release(f, e.ino, path, fi);
+
3192 forget_node(f, e.ino, 1);
+
3193 }
+
3194 } else {
+
3195 reply_err(req, err);
+
3196 }
+
3197
+
3198 free_path(f, parent, path);
+
3199}
+
3200
+
3201static double diff_timespec(const struct timespec *t1,
+
3202 const struct timespec *t2)
+
3203{
+
3204 return (t1->tv_sec - t2->tv_sec) +
+
3205 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3206}
+
3207
+
3208static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3209 struct fuse_file_info *fi)
+
3210{
+
3211 struct node *node;
+
3212
+
3213 pthread_mutex_lock(&f->lock);
+
3214 node = get_node(f, ino);
+
3215 if (node->cache_valid) {
+
3216 struct timespec now;
+
3217
+
3218 curr_time(&now);
+
3219 if (diff_timespec(&now, &node->stat_updated) >
+
3220 f->conf.ac_attr_timeout) {
+
3221 struct stat stbuf;
+
3222 int err;
+
3223 pthread_mutex_unlock(&f->lock);
+
3224 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3225 pthread_mutex_lock(&f->lock);
+
3226 if (!err)
+
3227 update_stat(node, &stbuf);
+
3228 else
+
3229 node->cache_valid = 0;
+
3230 }
+
3231 }
+
3232 if (node->cache_valid)
+
3233 fi->keep_cache = 1;
+
3234
+
3235 node->cache_valid = 1;
+
3236 pthread_mutex_unlock(&f->lock);
+
3237}
+
3238
+
3239static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3240 struct fuse_file_info *fi)
+
3241{
+
3242 struct fuse *f = req_fuse_prepare(req);
+
3243 struct fuse_intr_data d;
+
3244 char *path;
+
3245 int err;
+
3246
+
3247 err = get_path(f, ino, &path);
+
3248 if (!err) {
+
3249 fuse_prepare_interrupt(f, req, &d);
+
3250 err = fuse_fs_open(f->fs, path, fi);
+
3251 if (!err) {
+
3252 if (f->conf.direct_io)
+
3253 fi->direct_io = 1;
+
3254 if (f->conf.kernel_cache)
+
3255 fi->keep_cache = 1;
+
3256
+
3257 if (f->conf.auto_cache)
+
3258 open_auto_cache(f, ino, path, fi);
+
3259
+
3260 if (f->conf.no_rofd_flush &&
+
3261 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3262 fi->noflush = 1;
+
3263
+
3264 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3265 fi->parallel_direct_writes = 1;
+
3266
+
3267 }
+
3268 fuse_finish_interrupt(f, req, &d);
+
3269 }
+
3270 if (!err) {
+
3271 pthread_mutex_lock(&f->lock);
+
3272 get_node(f, ino)->open_count++;
+
3273 pthread_mutex_unlock(&f->lock);
+
3274 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3275 /* The open syscall was interrupted, so it
+
3276 must be cancelled */
+
3277 fuse_do_release(f, ino, path, fi);
+
3278 }
+
3279 } else
+
3280 reply_err(req, err);
+
3281
+
3282 free_path(f, ino, path);
+
3283}
+
3284
+
3285static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3286 off_t off, struct fuse_file_info *fi)
+
3287{
+
3288 struct fuse *f = req_fuse_prepare(req);
+
3289 struct fuse_bufvec *buf = NULL;
+
3290 char *path;
+
3291 int res;
+
3292
+
3293 res = get_path_nullok(f, ino, &path);
+
3294 if (res == 0) {
+
3295 struct fuse_intr_data d;
+
3296
+
3297 fuse_prepare_interrupt(f, req, &d);
+
3298 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3299 fuse_finish_interrupt(f, req, &d);
+
3300 free_path(f, ino, path);
+
3301 }
+
3302
+
3303 if (res == 0)
+ +
3305 else
+
3306 reply_err(req, res);
+
3307
+
3308 fuse_free_buf(buf);
+
3309}
+
3310
+
3311static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3312 struct fuse_bufvec *buf, off_t off,
+
3313 struct fuse_file_info *fi)
+
3314{
+
3315 struct fuse *f = req_fuse_prepare(req);
+
3316 char *path;
+
3317 int res;
+
3318
+
3319 res = get_path_nullok(f, ino, &path);
+
3320 if (res == 0) {
+
3321 struct fuse_intr_data d;
+
3322
+
3323 fuse_prepare_interrupt(f, req, &d);
+
3324 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3325 fuse_finish_interrupt(f, req, &d);
+
3326 free_path(f, ino, path);
+
3327 }
+
3328
+
3329 if (res >= 0)
+
3330 fuse_reply_write(req, res);
+
3331 else
+
3332 reply_err(req, res);
+
3333}
+
3334
+
3335static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3336 struct fuse_file_info *fi)
+
3337{
+
3338 struct fuse *f = req_fuse_prepare(req);
+
3339 char *path;
+
3340 int err;
+
3341
+
3342 err = get_path_nullok(f, ino, &path);
+
3343 if (!err) {
+
3344 struct fuse_intr_data d;
+
3345
+
3346 fuse_prepare_interrupt(f, req, &d);
+
3347 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3348 fuse_finish_interrupt(f, req, &d);
+
3349 free_path(f, ino, path);
+
3350 }
+
3351 reply_err(req, err);
+
3352}
+
3353
+
3354static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3355 struct fuse_file_info *fi)
+
3356{
+
3357 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3358 memset(fi, 0, sizeof(struct fuse_file_info));
+
3359 fi->fh = dh->fh;
+
3360 return dh;
+
3361}
+
3362
+
3363static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3364 struct fuse_file_info *llfi)
+
3365{
+
3366 struct fuse *f = req_fuse_prepare(req);
+
3367 struct fuse_intr_data d;
+
3368 struct fuse_dh *dh;
+
3369 struct fuse_file_info fi;
+
3370 char *path;
+
3371 int err;
+
3372
+
3373 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3374 if (dh == NULL) {
+
3375 reply_err(req, -ENOMEM);
+
3376 return;
+
3377 }
+
3378 memset(dh, 0, sizeof(struct fuse_dh));
+
3379 dh->fuse = f;
+
3380 dh->contents = NULL;
+
3381 dh->first = NULL;
+
3382 dh->len = 0;
+
3383 dh->filled = 0;
+
3384 dh->nodeid = ino;
+
3385 pthread_mutex_init(&dh->lock, NULL);
+
3386
+
3387 llfi->fh = (uintptr_t) dh;
+
3388
+
3389 memset(&fi, 0, sizeof(fi));
+
3390 fi.flags = llfi->flags;
+
3391
+
3392 err = get_path(f, ino, &path);
+
3393 if (!err) {
+
3394 fuse_prepare_interrupt(f, req, &d);
+
3395 err = fuse_fs_opendir(f->fs, path, &fi);
+
3396 fuse_finish_interrupt(f, req, &d);
+
3397 dh->fh = fi.fh;
+
3398 llfi->cache_readdir = fi.cache_readdir;
+
3399 llfi->keep_cache = fi.keep_cache;
+
3400 }
+
3401 if (!err) {
+
3402 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3403 /* The opendir syscall was interrupted, so it
+
3404 must be cancelled */
+
3405 fuse_fs_releasedir(f->fs, path, &fi);
+
3406 pthread_mutex_destroy(&dh->lock);
+
3407 free(dh);
+
3408 }
+
3409 } else {
+
3410 reply_err(req, err);
+
3411 pthread_mutex_destroy(&dh->lock);
+
3412 free(dh);
+
3413 }
+
3414 free_path(f, ino, path);
+
3415}
+
3416
+
3417static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3418{
+
3419 if (minsize > dh->size) {
+
3420 char *newptr;
+
3421 unsigned newsize = dh->size;
+
3422 if (!newsize)
+
3423 newsize = 1024;
+
3424 while (newsize < minsize) {
+
3425 if (newsize >= 0x80000000)
+
3426 newsize = 0xffffffff;
+
3427 else
+
3428 newsize *= 2;
+
3429 }
+
3430
+
3431 newptr = (char *) realloc(dh->contents, newsize);
+
3432 if (!newptr) {
+
3433 dh->error = -ENOMEM;
+
3434 return -1;
+
3435 }
+
3436 dh->contents = newptr;
+
3437 dh->size = newsize;
+
3438 }
+
3439 return 0;
+
3440}
+
3441
+
3442static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3443 struct stat *st, enum fuse_fill_dir_flags flags)
+
3444{
+
3445 struct fuse_direntry *de;
+
3446
+
3447 de = malloc(sizeof(struct fuse_direntry));
+
3448 if (!de) {
+
3449 dh->error = -ENOMEM;
+
3450 return -1;
+
3451 }
+
3452 de->name = strdup(name);
+
3453 if (!de->name) {
+
3454 dh->error = -ENOMEM;
+
3455 free(de);
+
3456 return -1;
+
3457 }
+
3458 de->flags = flags;
+
3459 de->stat = *st;
+
3460 de->next = NULL;
+
3461
+
3462 *dh->last = de;
+
3463 dh->last = &de->next;
+
3464
+
3465 return 0;
+
3466}
+
3467
+
3468static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3469 const char *name)
+
3470{
+
3471 struct node *node;
+
3472 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3473
+
3474 pthread_mutex_lock(&f->lock);
+
3475 node = lookup_node(f, parent, name);
+
3476 if (node)
+
3477 res = node->nodeid;
+
3478 pthread_mutex_unlock(&f->lock);
+
3479
+
3480 return res;
+
3481}
+
3482
+
3483static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3484 off_t off, enum fuse_fill_dir_flags flags)
+
3485{
+
3486 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3487 struct stat stbuf;
+
3488
+
3489 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3490 dh->error = -EIO;
+
3491 return 1;
+
3492 }
+
3493
+
3494 if (statp)
+
3495 stbuf = *statp;
+
3496 else {
+
3497 memset(&stbuf, 0, sizeof(stbuf));
+
3498 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3499 }
+
3500
+
3501 if (!dh->fuse->conf.use_ino) {
+
3502 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3503 if (dh->fuse->conf.readdir_ino) {
+
3504 stbuf.st_ino = (ino_t)
+
3505 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3506 }
+
3507 }
+
3508
+
3509 if (off) {
+
3510 size_t newlen;
+
3511
+
3512 if (dh->filled) {
+
3513 dh->error = -EIO;
+
3514 return 1;
+
3515 }
+
3516
+
3517 if (dh->first) {
+
3518 dh->error = -EIO;
+
3519 return 1;
+
3520 }
+
3521
+
3522 if (extend_contents(dh, dh->needlen) == -1)
+
3523 return 1;
+
3524
+
3525 newlen = dh->len +
+
3526 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3527 dh->needlen - dh->len, name,
+
3528 &stbuf, off);
+
3529 if (newlen > dh->needlen)
+
3530 return 1;
+
3531
+
3532 dh->len = newlen;
+
3533 } else {
+
3534 dh->filled = 1;
+
3535
+
3536 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3537 return 1;
+
3538 }
+
3539 return 0;
+
3540}
+
3541
+
3542static int is_dot_or_dotdot(const char *name)
+
3543{
+
3544 return name[0] == '.' && (name[1] == '\0' ||
+
3545 (name[1] == '.' && name[2] == '\0'));
+
3546}
+
3547
+
3548static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3549 off_t off, enum fuse_fill_dir_flags flags)
+
3550{
+
3551 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3552 struct fuse_entry_param e = {
+
3553 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3554 .ino = 0,
+
3555 };
+
3556 struct fuse *f = dh->fuse;
+
3557 int res;
+
3558
+
3559 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3560 dh->error = -EIO;
+
3561 return 1;
+
3562 }
+
3563
+
3564 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3565 e.attr = *statp;
+
3566 } else {
+
3567 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3568 if (statp) {
+
3569 e.attr.st_mode = statp->st_mode;
+
3570 if (f->conf.use_ino)
+
3571 e.attr.st_ino = statp->st_ino;
+
3572 }
+
3573 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3574 e.attr.st_ino = (ino_t)
+
3575 lookup_nodeid(f, dh->nodeid, name);
+
3576 }
+
3577 }
+
3578
+
3579 if (off) {
+
3580 size_t newlen;
+
3581
+
3582 if (dh->filled) {
+
3583 dh->error = -EIO;
+
3584 return 1;
+
3585 }
+
3586
+
3587 if (dh->first) {
+
3588 dh->error = -EIO;
+
3589 return 1;
+
3590 }
+
3591 if (extend_contents(dh, dh->needlen) == -1)
+
3592 return 1;
+
3593
+
3594 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3595 if (!is_dot_or_dotdot(name)) {
+
3596 res = do_lookup(f, dh->nodeid, name, &e);
+
3597 if (res) {
+
3598 dh->error = res;
+
3599 return 1;
+
3600 }
+
3601 }
+
3602 }
+
3603
+
3604 newlen = dh->len +
+
3605 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3606 dh->needlen - dh->len, name,
+
3607 &e, off);
+
3608 if (newlen > dh->needlen)
+
3609 return 1;
+
3610 dh->len = newlen;
+
3611 } else {
+
3612 dh->filled = 1;
+
3613
+
3614 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3615 return 1;
+
3616 }
+
3617
+
3618 return 0;
+
3619}
+
3620
+
3621static void free_direntries(struct fuse_direntry *de)
+
3622{
+
3623 while (de) {
+
3624 struct fuse_direntry *next = de->next;
+
3625 free(de->name);
+
3626 free(de);
+
3627 de = next;
+
3628 }
+
3629}
+
3630
+
3631static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3632 size_t size, off_t off, struct fuse_dh *dh,
+
3633 struct fuse_file_info *fi,
+
3634 enum fuse_readdir_flags flags)
+
3635{
+
3636 char *path;
+
3637 int err;
+
3638
+
3639 if (f->fs->op.readdir)
+
3640 err = get_path_nullok(f, ino, &path);
+
3641 else
+
3642 err = get_path(f, ino, &path);
+
3643 if (!err) {
+
3644 struct fuse_intr_data d;
+
3645 fuse_fill_dir_t filler = fill_dir;
+
3646
+
3647 if (flags & FUSE_READDIR_PLUS)
+
3648 filler = fill_dir_plus;
+
3649
+
3650 free_direntries(dh->first);
+
3651 dh->first = NULL;
+
3652 dh->last = &dh->first;
+
3653 dh->len = 0;
+
3654 dh->error = 0;
+
3655 dh->needlen = size;
+
3656 dh->filled = 0;
+
3657 dh->req = req;
+
3658 fuse_prepare_interrupt(f, req, &d);
+
3659 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3660 fuse_finish_interrupt(f, req, &d);
+
3661 dh->req = NULL;
+
3662 if (!err)
+
3663 err = dh->error;
+
3664 if (err)
+
3665 dh->filled = 0;
+
3666 free_path(f, ino, path);
+
3667 }
+
3668 return err;
+
3669}
+
3670
+
3671static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3672 off_t off, enum fuse_readdir_flags flags)
+
3673{
+
3674 off_t pos;
+
3675 struct fuse_direntry *de = dh->first;
+
3676 int res;
+
3677
+
3678 dh->len = 0;
+
3679
+
3680 if (extend_contents(dh, dh->needlen) == -1)
+
3681 return dh->error;
+
3682
+
3683 for (pos = 0; pos < off; pos++) {
+
3684 if (!de)
+
3685 break;
+
3686
+
3687 de = de->next;
+
3688 }
+
3689 while (de) {
+
3690 char *p = dh->contents + dh->len;
+
3691 unsigned rem = dh->needlen - dh->len;
+
3692 unsigned thislen;
+
3693 unsigned newlen;
+
3694 pos++;
+
3695
+
3696 if (flags & FUSE_READDIR_PLUS) {
+
3697 struct fuse_entry_param e = {
+
3698 .ino = 0,
+
3699 .attr = de->stat,
+
3700 };
+
3701
+
3702 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3703 !is_dot_or_dotdot(de->name)) {
+
3704 res = do_lookup(dh->fuse, dh->nodeid,
+
3705 de->name, &e);
+
3706 if (res) {
+
3707 dh->error = res;
+
3708 return 1;
+
3709 }
+
3710 }
+
3711
+
3712 thislen = fuse_add_direntry_plus(req, p, rem,
+
3713 de->name, &e, pos);
+
3714 } else {
+
3715 thislen = fuse_add_direntry(req, p, rem,
+
3716 de->name, &de->stat, pos);
+
3717 }
+
3718 newlen = dh->len + thislen;
+
3719 if (newlen > dh->needlen)
+
3720 break;
+
3721 dh->len = newlen;
+
3722 de = de->next;
+
3723 }
+
3724 return 0;
+
3725}
+
3726
+
3727static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3728 off_t off, struct fuse_file_info *llfi,
+
3729 enum fuse_readdir_flags flags)
+
3730{
+
3731 struct fuse *f = req_fuse_prepare(req);
+
3732 struct fuse_file_info fi;
+
3733 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3734 int err;
+
3735
+
3736 pthread_mutex_lock(&dh->lock);
+
3737 /* According to SUS, directory contents need to be refreshed on
+
3738 rewinddir() */
+
3739 if (!off)
+
3740 dh->filled = 0;
+
3741
+
3742 if (!dh->filled) {
+
3743 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3744 if (err) {
+
3745 reply_err(req, err);
+
3746 goto out;
+
3747 }
+
3748 }
+
3749 if (dh->filled) {
+
3750 dh->needlen = size;
+
3751 err = readdir_fill_from_list(req, dh, off, flags);
+
3752 if (err) {
+
3753 reply_err(req, err);
+
3754 goto out;
+
3755 }
+
3756 }
+
3757 fuse_reply_buf(req, dh->contents, dh->len);
+
3758out:
+
3759 pthread_mutex_unlock(&dh->lock);
+
3760}
+
3761
+
3762static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3763 off_t off, struct fuse_file_info *llfi)
+
3764{
+
3765 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3766}
+
3767
+
3768static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3769 off_t off, struct fuse_file_info *llfi)
+
3770{
+
3771 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3772}
+
3773
+
3774static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3775 struct fuse_file_info *llfi)
+
3776{
+
3777 struct fuse *f = req_fuse_prepare(req);
+
3778 struct fuse_intr_data d;
+
3779 struct fuse_file_info fi;
+
3780 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3781 char *path;
+
3782
+
3783 get_path_nullok(f, ino, &path);
+
3784
+
3785 fuse_prepare_interrupt(f, req, &d);
+
3786 fuse_fs_releasedir(f->fs, path, &fi);
+
3787 fuse_finish_interrupt(f, req, &d);
+
3788 free_path(f, ino, path);
+
3789
+
3790 pthread_mutex_lock(&dh->lock);
+
3791 pthread_mutex_unlock(&dh->lock);
+
3792 pthread_mutex_destroy(&dh->lock);
+
3793 free_direntries(dh->first);
+
3794 free(dh->contents);
+
3795 free(dh);
+
3796 reply_err(req, 0);
+
3797}
+
3798
+
3799static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3800 struct fuse_file_info *llfi)
+
3801{
+
3802 struct fuse *f = req_fuse_prepare(req);
+
3803 struct fuse_file_info fi;
+
3804 char *path;
+
3805 int err;
+
3806
+
3807 get_dirhandle(llfi, &fi);
+
3808
+
3809 err = get_path_nullok(f, ino, &path);
+
3810 if (!err) {
+
3811 struct fuse_intr_data d;
+
3812 fuse_prepare_interrupt(f, req, &d);
+
3813 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3814 fuse_finish_interrupt(f, req, &d);
+
3815 free_path(f, ino, path);
+
3816 }
+
3817 reply_err(req, err);
+
3818}
+
3819
+
3820static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3821{
+
3822 struct fuse *f = req_fuse_prepare(req);
+
3823 struct statvfs buf;
+
3824 char *path = NULL;
+
3825 int err = 0;
+
3826
+
3827 memset(&buf, 0, sizeof(buf));
+
3828 if (ino)
+
3829 err = get_path(f, ino, &path);
+
3830
+
3831 if (!err) {
+
3832 struct fuse_intr_data d;
+
3833 fuse_prepare_interrupt(f, req, &d);
+
3834 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3835 fuse_finish_interrupt(f, req, &d);
+
3836 free_path(f, ino, path);
+
3837 }
+
3838
+
3839 if (!err)
+
3840 fuse_reply_statfs(req, &buf);
+
3841 else
+
3842 reply_err(req, err);
+
3843}
+
3844
+
3845static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3846 const char *value, size_t size, int flags)
+
3847{
+
3848 struct fuse *f = req_fuse_prepare(req);
+
3849 char *path;
+
3850 int err;
+
3851
+
3852 err = get_path(f, ino, &path);
+
3853 if (!err) {
+
3854 struct fuse_intr_data d;
+
3855 fuse_prepare_interrupt(f, req, &d);
+
3856 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3857 fuse_finish_interrupt(f, req, &d);
+
3858 free_path(f, ino, path);
+
3859 }
+
3860 reply_err(req, err);
+
3861}
+
3862
+
3863static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3864 const char *name, char *value, size_t size)
+
3865{
+
3866 int err;
+
3867 char *path;
+
3868
+
3869 err = get_path(f, ino, &path);
+
3870 if (!err) {
+
3871 struct fuse_intr_data d;
+
3872 fuse_prepare_interrupt(f, req, &d);
+
3873 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3874 fuse_finish_interrupt(f, req, &d);
+
3875 free_path(f, ino, path);
+
3876 }
+
3877 return err;
+
3878}
+
3879
+
3880static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3881 size_t size)
+
3882{
+
3883 struct fuse *f = req_fuse_prepare(req);
+
3884 int res;
+
3885
+
3886 if (size) {
+
3887 char *value = (char *) malloc(size);
+
3888 if (value == NULL) {
+
3889 reply_err(req, -ENOMEM);
+
3890 return;
+
3891 }
+
3892 res = common_getxattr(f, req, ino, name, value, size);
+
3893 if (res > 0)
+
3894 fuse_reply_buf(req, value, res);
+
3895 else
+
3896 reply_err(req, res);
+
3897 free(value);
+
3898 } else {
+
3899 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3900 if (res >= 0)
+
3901 fuse_reply_xattr(req, res);
+
3902 else
+
3903 reply_err(req, res);
+
3904 }
+
3905}
+
3906
+
3907static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3908 char *list, size_t size)
+
3909{
+
3910 char *path;
+
3911 int err;
+
3912
+
3913 err = get_path(f, ino, &path);
+
3914 if (!err) {
+
3915 struct fuse_intr_data d;
+
3916 fuse_prepare_interrupt(f, req, &d);
+
3917 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3918 fuse_finish_interrupt(f, req, &d);
+
3919 free_path(f, ino, path);
+
3920 }
+
3921 return err;
+
3922}
+
3923
+
3924static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3925{
+
3926 struct fuse *f = req_fuse_prepare(req);
+
3927 int res;
+
3928
+
3929 if (size) {
+
3930 char *list = (char *) malloc(size);
+
3931 if (list == NULL) {
+
3932 reply_err(req, -ENOMEM);
+
3933 return;
+
3934 }
+
3935 res = common_listxattr(f, req, ino, list, size);
+
3936 if (res > 0)
+
3937 fuse_reply_buf(req, list, res);
+
3938 else
+
3939 reply_err(req, res);
+
3940 free(list);
+
3941 } else {
+
3942 res = common_listxattr(f, req, ino, NULL, 0);
+
3943 if (res >= 0)
+
3944 fuse_reply_xattr(req, res);
+
3945 else
+
3946 reply_err(req, res);
+
3947 }
+
3948}
+
3949
+
3950static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3951 const char *name)
+
3952{
+
3953 struct fuse *f = req_fuse_prepare(req);
+
3954 char *path;
+
3955 int err;
+
3956
+
3957 err = get_path(f, ino, &path);
+
3958 if (!err) {
+
3959 struct fuse_intr_data d;
+
3960 fuse_prepare_interrupt(f, req, &d);
+
3961 err = fuse_fs_removexattr(f->fs, path, name);
+
3962 fuse_finish_interrupt(f, req, &d);
+
3963 free_path(f, ino, path);
+
3964 }
+
3965 reply_err(req, err);
+
3966}
+
3967
+
3968static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3969{
+
3970 struct lock *l;
+
3971
+
3972 for (l = node->locks; l; l = l->next)
+
3973 if (l->owner != lock->owner &&
+
3974 lock->start <= l->end && l->start <= lock->end &&
+
3975 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
3976 break;
+
3977
+
3978 return l;
+
3979}
+
3980
+
3981static void delete_lock(struct lock **lockp)
+
3982{
+
3983 struct lock *l = *lockp;
+
3984 *lockp = l->next;
+
3985 free(l);
+
3986}
+
3987
+
3988static void insert_lock(struct lock **pos, struct lock *lock)
+
3989{
+
3990 lock->next = *pos;
+
3991 *pos = lock;
+
3992}
+
3993
+
3994static int locks_insert(struct node *node, struct lock *lock)
+
3995{
+
3996 struct lock **lp;
+
3997 struct lock *newl1 = NULL;
+
3998 struct lock *newl2 = NULL;
+
3999
+
4000 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4001 lock->end != OFFSET_MAX) {
+
4002 newl1 = malloc(sizeof(struct lock));
+
4003 newl2 = malloc(sizeof(struct lock));
+
4004
+
4005 if (!newl1 || !newl2) {
+
4006 free(newl1);
+
4007 free(newl2);
+
4008 return -ENOLCK;
+
4009 }
+
4010 }
+
4011
+
4012 for (lp = &node->locks; *lp;) {
+
4013 struct lock *l = *lp;
+
4014 if (l->owner != lock->owner)
+
4015 goto skip;
+
4016
+
4017 if (lock->type == l->type) {
+
4018 if (l->end < lock->start - 1)
+
4019 goto skip;
+
4020 if (lock->end < l->start - 1)
+
4021 break;
+
4022 if (l->start <= lock->start && lock->end <= l->end)
+
4023 goto out;
+
4024 if (l->start < lock->start)
+
4025 lock->start = l->start;
+
4026 if (lock->end < l->end)
+
4027 lock->end = l->end;
+
4028 goto delete;
+
4029 } else {
+
4030 if (l->end < lock->start)
+
4031 goto skip;
+
4032 if (lock->end < l->start)
+
4033 break;
+
4034 if (lock->start <= l->start && l->end <= lock->end)
+
4035 goto delete;
+
4036 if (l->end <= lock->end) {
+
4037 l->end = lock->start - 1;
+
4038 goto skip;
+
4039 }
+
4040 if (lock->start <= l->start) {
+
4041 l->start = lock->end + 1;
+
4042 break;
+
4043 }
+
4044 *newl2 = *l;
+
4045 newl2->start = lock->end + 1;
+
4046 l->end = lock->start - 1;
+
4047 insert_lock(&l->next, newl2);
+
4048 newl2 = NULL;
+
4049 }
+
4050 skip:
+
4051 lp = &l->next;
+
4052 continue;
+
4053
+
4054 delete:
+
4055 delete_lock(lp);
+
4056 }
+
4057 if (lock->type != F_UNLCK) {
+
4058 *newl1 = *lock;
+
4059 insert_lock(lp, newl1);
+
4060 newl1 = NULL;
+
4061 }
+
4062out:
+
4063 free(newl1);
+
4064 free(newl2);
+
4065 return 0;
+
4066}
+
4067
+
4068static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4069{
+
4070 memset(lock, 0, sizeof(struct lock));
+
4071 lock->type = flock->l_type;
+
4072 lock->start = flock->l_start;
+
4073 lock->end =
+
4074 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4075 lock->pid = flock->l_pid;
+
4076}
+
4077
+
4078static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4079{
+
4080 flock->l_type = lock->type;
+
4081 flock->l_start = lock->start;
+
4082 flock->l_len =
+
4083 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4084 flock->l_pid = lock->pid;
+
4085}
+
4086
+
4087static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4088 const char *path, struct fuse_file_info *fi)
+
4089{
+
4090 struct fuse_intr_data d;
+
4091 struct flock lock;
+
4092 struct lock l;
+
4093 int err;
+
4094 int errlock;
+
4095
+
4096 fuse_prepare_interrupt(f, req, &d);
+
4097 memset(&lock, 0, sizeof(lock));
+
4098 lock.l_type = F_UNLCK;
+
4099 lock.l_whence = SEEK_SET;
+
4100 err = fuse_fs_flush(f->fs, path, fi);
+
4101 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4102 fuse_finish_interrupt(f, req, &d);
+
4103
+
4104 if (errlock != -ENOSYS) {
+
4105 flock_to_lock(&lock, &l);
+
4106 l.owner = fi->lock_owner;
+
4107 pthread_mutex_lock(&f->lock);
+
4108 locks_insert(get_node(f, ino), &l);
+
4109 pthread_mutex_unlock(&f->lock);
+
4110
+
4111 /* if op.lock() is defined FLUSH is needed regardless
+
4112 of op.flush() */
+
4113 if (err == -ENOSYS)
+
4114 err = 0;
+
4115 }
+
4116 return err;
+
4117}
+
4118
+
4119static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4120 struct fuse_file_info *fi)
+
4121{
+
4122 struct fuse *f = req_fuse_prepare(req);
+
4123 struct fuse_intr_data d;
+
4124 char *path;
+
4125 int err = 0;
+
4126
+
4127 get_path_nullok(f, ino, &path);
+
4128 if (fi->flush) {
+
4129 err = fuse_flush_common(f, req, ino, path, fi);
+
4130 if (err == -ENOSYS)
+
4131 err = 0;
+
4132 }
+
4133
+
4134 fuse_prepare_interrupt(f, req, &d);
+
4135 fuse_do_release(f, ino, path, fi);
+
4136 fuse_finish_interrupt(f, req, &d);
+
4137 free_path(f, ino, path);
+
4138
+
4139 reply_err(req, err);
+
4140}
+
4141
+
4142static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4143 struct fuse_file_info *fi)
+
4144{
+
4145 struct fuse *f = req_fuse_prepare(req);
+
4146 char *path;
+
4147 int err;
+
4148
+
4149 get_path_nullok(f, ino, &path);
+
4150 err = fuse_flush_common(f, req, ino, path, fi);
+
4151 free_path(f, ino, path);
+
4152
+
4153 reply_err(req, err);
+
4154}
+
4155
+
4156static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4157 struct fuse_file_info *fi, struct flock *lock,
+
4158 int cmd)
+
4159{
+
4160 struct fuse *f = req_fuse_prepare(req);
+
4161 char *path;
+
4162 int err;
+
4163
+
4164 err = get_path_nullok(f, ino, &path);
+
4165 if (!err) {
+
4166 struct fuse_intr_data d;
+
4167 fuse_prepare_interrupt(f, req, &d);
+
4168 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4169 fuse_finish_interrupt(f, req, &d);
+
4170 free_path(f, ino, path);
+
4171 }
+
4172 return err;
+
4173}
+
4174
+
4175static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4176 struct fuse_file_info *fi, struct flock *lock)
+
4177{
+
4178 int err;
+
4179 struct lock l;
+
4180 struct lock *conflict;
+
4181 struct fuse *f = req_fuse(req);
+
4182
+
4183 flock_to_lock(lock, &l);
+
4184 l.owner = fi->lock_owner;
+
4185 pthread_mutex_lock(&f->lock);
+
4186 conflict = locks_conflict(get_node(f, ino), &l);
+
4187 if (conflict)
+
4188 lock_to_flock(conflict, lock);
+
4189 pthread_mutex_unlock(&f->lock);
+
4190 if (!conflict)
+
4191 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4192 else
+
4193 err = 0;
+
4194
+
4195 if (!err)
+
4196 fuse_reply_lock(req, lock);
+
4197 else
+
4198 reply_err(req, err);
+
4199}
+
4200
+
4201static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4202 struct fuse_file_info *fi, struct flock *lock,
+
4203 int sleep)
+
4204{
+
4205 int err = fuse_lock_common(req, ino, fi, lock,
+
4206 sleep ? F_SETLKW : F_SETLK);
+
4207 if (!err) {
+
4208 struct fuse *f = req_fuse(req);
+
4209 struct lock l;
+
4210 flock_to_lock(lock, &l);
+
4211 l.owner = fi->lock_owner;
+
4212 pthread_mutex_lock(&f->lock);
+
4213 locks_insert(get_node(f, ino), &l);
+
4214 pthread_mutex_unlock(&f->lock);
+
4215 }
+
4216 reply_err(req, err);
+
4217}
+
4218
+
4219static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4220 struct fuse_file_info *fi, int op)
+
4221{
+
4222 struct fuse *f = req_fuse_prepare(req);
+
4223 char *path;
+
4224 int err;
+
4225
+
4226 err = get_path_nullok(f, ino, &path);
+
4227 if (err == 0) {
+
4228 struct fuse_intr_data d;
+
4229 fuse_prepare_interrupt(f, req, &d);
+
4230 err = fuse_fs_flock(f->fs, path, fi, op);
+
4231 fuse_finish_interrupt(f, req, &d);
+
4232 free_path(f, ino, path);
+
4233 }
+
4234 reply_err(req, err);
+
4235}
+
4236
+
4237static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4238 uint64_t idx)
+
4239{
+
4240 struct fuse *f = req_fuse_prepare(req);
+
4241 struct fuse_intr_data d;
+
4242 char *path;
+
4243 int err;
+
4244
+
4245 err = get_path(f, ino, &path);
+
4246 if (!err) {
+
4247 fuse_prepare_interrupt(f, req, &d);
+
4248 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4249 fuse_finish_interrupt(f, req, &d);
+
4250 free_path(f, ino, path);
+
4251 }
+
4252 if (!err)
+
4253 fuse_reply_bmap(req, idx);
+
4254 else
+
4255 reply_err(req, err);
+
4256}
+
4257
+
4258static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4259 void *arg, struct fuse_file_info *llfi,
+
4260 unsigned int flags, const void *in_buf,
+
4261 size_t in_bufsz, size_t out_bufsz)
+
4262{
+
4263 struct fuse *f = req_fuse_prepare(req);
+
4264 struct fuse_intr_data d;
+
4265 struct fuse_file_info fi;
+
4266 char *path, *out_buf = NULL;
+
4267 int err;
+
4268
+
4269 err = -EPERM;
+
4270 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4271 goto err;
+
4272
+
4273 if (flags & FUSE_IOCTL_DIR)
+
4274 get_dirhandle(llfi, &fi);
+
4275 else
+
4276 fi = *llfi;
+
4277
+
4278 if (out_bufsz) {
+
4279 err = -ENOMEM;
+
4280 out_buf = malloc(out_bufsz);
+
4281 if (!out_buf)
+
4282 goto err;
+
4283 }
+
4284
+
4285 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4286 if (out_buf && in_bufsz)
+
4287 memcpy(out_buf, in_buf, in_bufsz);
+
4288
+
4289 err = get_path_nullok(f, ino, &path);
+
4290 if (err)
+
4291 goto err;
+
4292
+
4293 fuse_prepare_interrupt(f, req, &d);
+
4294
+
4295 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4296 out_buf ? out_buf : (void *)in_buf);
+
4297
+
4298 fuse_finish_interrupt(f, req, &d);
+
4299 free_path(f, ino, path);
+
4300
+
4301 if (err < 0)
+
4302 goto err;
+
4303 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4304 goto out;
+
4305err:
+
4306 reply_err(req, err);
+
4307out:
+
4308 free(out_buf);
+
4309}
+
4310
+
4311static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4312 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4313{
+
4314 struct fuse *f = req_fuse_prepare(req);
+
4315 struct fuse_intr_data d;
+
4316 char *path;
+
4317 int err;
+
4318 unsigned revents = 0;
+
4319
+
4320 err = get_path_nullok(f, ino, &path);
+
4321 if (!err) {
+
4322 fuse_prepare_interrupt(f, req, &d);
+
4323 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4324 fuse_finish_interrupt(f, req, &d);
+
4325 free_path(f, ino, path);
+
4326 }
+
4327 if (!err)
+
4328 fuse_reply_poll(req, revents);
+
4329 else
+
4330 reply_err(req, err);
+
4331}
+
4332
+
4333static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4334 off_t offset, off_t length, struct fuse_file_info *fi)
+
4335{
+
4336 struct fuse *f = req_fuse_prepare(req);
+
4337 struct fuse_intr_data d;
+
4338 char *path;
+
4339 int err;
+
4340
+
4341 err = get_path_nullok(f, ino, &path);
+
4342 if (!err) {
+
4343 fuse_prepare_interrupt(f, req, &d);
+
4344 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4345 fuse_finish_interrupt(f, req, &d);
+
4346 free_path(f, ino, path);
+
4347 }
+
4348 reply_err(req, err);
+
4349}
+
4350
+
4351static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4352 off_t off_in, struct fuse_file_info *fi_in,
+
4353 fuse_ino_t nodeid_out, off_t off_out,
+
4354 struct fuse_file_info *fi_out, size_t len,
+
4355 int flags)
+
4356{
+
4357 struct fuse *f = req_fuse_prepare(req);
+
4358 struct fuse_intr_data d;
+
4359 char *path_in, *path_out;
+
4360 int err;
+
4361 ssize_t res;
+
4362
+
4363 err = get_path_nullok(f, nodeid_in, &path_in);
+
4364 if (err) {
+
4365 reply_err(req, err);
+
4366 return;
+
4367 }
+
4368
+
4369 err = get_path_nullok(f, nodeid_out, &path_out);
+
4370 if (err) {
+
4371 free_path(f, nodeid_in, path_in);
+
4372 reply_err(req, err);
+
4373 return;
+
4374 }
+
4375
+
4376 fuse_prepare_interrupt(f, req, &d);
+
4377 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4378 fi_out, off_out, len, flags);
+
4379 fuse_finish_interrupt(f, req, &d);
+
4380
+
4381 if (res >= 0)
+
4382 fuse_reply_write(req, res);
+
4383 else
+
4384 reply_err(req, res);
+
4385
+
4386 free_path(f, nodeid_in, path_in);
+
4387 free_path(f, nodeid_out, path_out);
+
4388}
+
4389
+
4390static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4391 struct fuse_file_info *fi)
+
4392{
+
4393 struct fuse *f = req_fuse_prepare(req);
+
4394 struct fuse_intr_data d;
+
4395 char *path;
+
4396 int err;
+
4397 off_t res;
+
4398
+
4399 err = get_path(f, ino, &path);
+
4400 if (err) {
+
4401 reply_err(req, err);
+
4402 return;
+
4403 }
+
4404
+
4405 fuse_prepare_interrupt(f, req, &d);
+
4406 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4407 fuse_finish_interrupt(f, req, &d);
+
4408 free_path(f, ino, path);
+
4409 if (res >= 0)
+
4410 fuse_reply_lseek(req, res);
+
4411 else
+
4412 reply_err(req, res);
+
4413}
+
4414
+
4415static int clean_delay(struct fuse *f)
+
4416{
+
4417 /*
+
4418 * This is calculating the delay between clean runs. To
+
4419 * reduce the number of cleans we are doing them 10 times
+
4420 * within the remember window.
+
4421 */
+
4422 int min_sleep = 60;
+
4423 int max_sleep = 3600;
+
4424 int sleep_time = f->conf.remember / 10;
+
4425
+
4426 if (sleep_time > max_sleep)
+
4427 return max_sleep;
+
4428 if (sleep_time < min_sleep)
+
4429 return min_sleep;
+
4430 return sleep_time;
+
4431}
+
4432
+
4433int fuse_clean_cache(struct fuse *f)
+
4434{
+
4435 struct node_lru *lnode;
+
4436 struct list_head *curr, *next;
+
4437 struct node *node;
+
4438 struct timespec now;
+
4439
+
4440 pthread_mutex_lock(&f->lock);
+
4441
+
4442 curr_time(&now);
+
4443
+
4444 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4445 double age;
+
4446
+
4447 next = curr->next;
+
4448 lnode = list_entry(curr, struct node_lru, lru);
+
4449 node = &lnode->node;
+
4450
+
4451 age = diff_timespec(&now, &lnode->forget_time);
+
4452 if (age <= f->conf.remember)
+
4453 break;
+
4454
+
4455 assert(node->nlookup == 1);
+
4456
+
4457 /* Don't forget active directories */
+
4458 if (node->refctr > 1)
+
4459 continue;
+
4460
+
4461 node->nlookup = 0;
+
4462 unhash_name(f, node);
+
4463 unref_node(f, node);
+
4464 }
+
4465 pthread_mutex_unlock(&f->lock);
+
4466
+
4467 return clean_delay(f);
+
4468}
+
4469
+
4470static struct fuse_lowlevel_ops fuse_path_ops = {
+
4471 .init = fuse_lib_init,
+
4472 .destroy = fuse_lib_destroy,
+
4473 .lookup = fuse_lib_lookup,
+
4474 .forget = fuse_lib_forget,
+
4475 .forget_multi = fuse_lib_forget_multi,
+
4476 .getattr = fuse_lib_getattr,
+
4477 .setattr = fuse_lib_setattr,
+
4478 .access = fuse_lib_access,
+
4479 .readlink = fuse_lib_readlink,
+
4480 .mknod = fuse_lib_mknod,
+
4481 .mkdir = fuse_lib_mkdir,
+
4482 .unlink = fuse_lib_unlink,
+
4483 .rmdir = fuse_lib_rmdir,
+
4484 .symlink = fuse_lib_symlink,
+
4485 .rename = fuse_lib_rename,
+
4486 .link = fuse_lib_link,
+
4487 .create = fuse_lib_create,
+
4488 .open = fuse_lib_open,
+
4489 .read = fuse_lib_read,
+
4490 .write_buf = fuse_lib_write_buf,
+
4491 .flush = fuse_lib_flush,
+
4492 .release = fuse_lib_release,
+
4493 .fsync = fuse_lib_fsync,
+
4494 .opendir = fuse_lib_opendir,
+
4495 .readdir = fuse_lib_readdir,
+
4496 .readdirplus = fuse_lib_readdirplus,
+
4497 .releasedir = fuse_lib_releasedir,
+
4498 .fsyncdir = fuse_lib_fsyncdir,
+
4499 .statfs = fuse_lib_statfs,
+
4500 .setxattr = fuse_lib_setxattr,
+
4501 .getxattr = fuse_lib_getxattr,
+
4502 .listxattr = fuse_lib_listxattr,
+
4503 .removexattr = fuse_lib_removexattr,
+
4504 .getlk = fuse_lib_getlk,
+
4505 .setlk = fuse_lib_setlk,
+
4506 .flock = fuse_lib_flock,
+
4507 .bmap = fuse_lib_bmap,
+
4508 .ioctl = fuse_lib_ioctl,
+
4509 .poll = fuse_lib_poll,
+
4510 .fallocate = fuse_lib_fallocate,
+
4511 .copy_file_range = fuse_lib_copy_file_range,
+
4512 .lseek = fuse_lib_lseek,
+
4513};
+
4514
+
4515int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4516{
+
4517 return fuse_lowlevel_notify_poll(ph);
+
4518}
+
4519
+
4520struct fuse_session *fuse_get_session(struct fuse *f)
+
4521{
+
4522 return f->se;
+
4523}
+
4524
+
4525static int fuse_session_loop_remember(struct fuse *f)
+
4526{
+
4527 struct fuse_session *se = f->se;
+
4528 int res = 0;
+
4529 struct timespec now;
+
4530 time_t next_clean;
+
4531 struct pollfd fds = {
+
4532 .fd = se->fd,
+
4533 .events = POLLIN
+
4534 };
+
4535 struct fuse_buf fbuf = {
+
4536 .mem = NULL,
+
4537 };
+
4538
+
4539 curr_time(&now);
+
4540 next_clean = now.tv_sec;
+
4541 while (!fuse_session_exited(se)) {
+
4542 unsigned timeout;
+
4543
+
4544 curr_time(&now);
+
4545 if (now.tv_sec < next_clean)
+
4546 timeout = next_clean - now.tv_sec;
+
4547 else
+
4548 timeout = 0;
+
4549
+
4550 res = poll(&fds, 1, timeout * 1000);
+
4551 if (res == -1) {
+
4552 if (errno == EINTR)
+
4553 continue;
+
4554 else
+
4555 break;
+
4556 } else if (res > 0) {
+
4557 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4558 NULL);
+
4559 if (res == -EINTR)
+
4560 continue;
+
4561 if (res <= 0)
+
4562 break;
+
4563
+
4564 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4565 } else {
+
4566 timeout = fuse_clean_cache(f);
+
4567 curr_time(&now);
+
4568 next_clean = now.tv_sec + timeout;
+
4569 }
+
4570 }
+
4571
+
4572 free(fbuf.mem);
+ +
4574 return res < 0 ? -1 : 0;
+
4575}
+
4576
+
4577int fuse_loop(struct fuse *f)
+
4578{
+
4579 if (!f)
+
4580 return -1;
+
4581
+
4582 if (lru_enabled(f))
+
4583 return fuse_session_loop_remember(f);
+
4584
+
4585 return fuse_session_loop(f->se);
+
4586}
+
4587
+
4588FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4589int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4590{
+
4591 if (f == NULL)
+
4592 return -1;
+
4593
+
4594 int res = fuse_start_cleanup_thread(f);
+
4595 if (res)
+
4596 return -1;
+
4597
+
4598 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4600 return res;
+
4601}
+
4602
+
4603int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4604FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4605int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4606{
+
4607 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4608 if (config == NULL)
+
4609 return ENOMEM;
+
4610
+
4611 fuse_loop_cfg_convert(config, config_v1);
+
4612
+
4613 int res = fuse_loop_mt_312(f, config);
+
4614
+
4615 fuse_loop_cfg_destroy(config);
+
4616
+
4617 return res;
+
4618}
+
4619
+
4620int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4621FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4622int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4623{
+
4624 int err;
+
4625 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4626
+
4627 if (config == NULL)
+
4628 return ENOMEM;
+
4629
+
4630 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4631
+
4632 err = fuse_loop_mt_312(f, config);
+
4633
+
4634 fuse_loop_cfg_destroy(config);
+
4635
+
4636 return err;
+
4637}
+
4638
+
4639void fuse_exit(struct fuse *f)
+
4640{
+
4641 fuse_session_exit(f->se);
+
4642}
+
4643
+
4644struct fuse_context *fuse_get_context(void)
+
4645{
+
4646 struct fuse_context_i *c = fuse_get_context_internal();
+
4647
+
4648 if (c)
+
4649 return &c->ctx;
+
4650 else
+
4651 return NULL;
+
4652}
+
4653
+
4654int fuse_getgroups(int size, gid_t list[])
+
4655{
+
4656 struct fuse_context_i *c = fuse_get_context_internal();
+
4657 if (!c)
+
4658 return -EINVAL;
+
4659
+
4660 return fuse_req_getgroups(c->req, size, list);
+
4661}
+
4662
+
4663int fuse_interrupted(void)
+
4664{
+
4665 struct fuse_context_i *c = fuse_get_context_internal();
+
4666
+
4667 if (c)
+
4668 return fuse_req_interrupted(c->req);
+
4669 else
+
4670 return 0;
+
4671}
+
4672
+
4673int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4674 fuse_ino_t ino;
+
4675 int err = lookup_path_in_cache(f, path, &ino);
+
4676 if (err) {
+
4677 return err;
+
4678 }
+
4679
+
4680 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4681}
+
4682
+
4683#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4684
+
4685static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4688 FUSE_LIB_OPT("debug", debug, 1),
+
4689 FUSE_LIB_OPT("-d", debug, 1),
+
4690 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4691 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4692 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4693 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4694 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4695 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4696 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4697 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4698 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4699 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4700 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4701 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4702 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4703 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4704 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4705 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4706 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4707 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4708 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4709 FUSE_LIB_OPT("noforget", remember, -1),
+
4710 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4711 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4712 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4714};
+
4715
+
4716static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4717 struct fuse_args *outargs)
+
4718{
+
4719 (void) arg; (void) outargs; (void) data; (void) key;
+
4720
+
4721 /* Pass through unknown options */
+
4722 return 1;
+
4723}
+
4724
+
4725
+
4726static const struct fuse_opt fuse_help_opts[] = {
+
4727 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4728 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4730};
+
4731
+
4732static void print_module_help(const char *name,
+ +
4734{
+
4735 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4736 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4737 fuse_opt_add_arg(&a, "-h") == -1)
+
4738 return;
+
4739 printf("\nOptions for %s module:\n", name);
+
4740 (*fac)(&a, NULL);
+ +
4742}
+
4743
+
4744void fuse_lib_help(struct fuse_args *args)
+
4745{
+
4746 /* These are not all options, but only the ones that
+
4747 may be of interest to an end-user */
+
4748 printf(
+
4749" -o kernel_cache cache files in kernel\n"
+
4750" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4751" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4752" -o umask=M set file permissions (octal)\n"
+
4753" -o fmask=M set file permissions (octal)\n"
+
4754" -o dmask=M set dir permissions (octal)\n"
+
4755" -o uid=N set file owner\n"
+
4756" -o gid=N set file group\n"
+
4757" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4758" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4759" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4760" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4761" -o noforget never forget cached inodes\n"
+
4762" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4763" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4764
+
4765
+
4766 /* Print low-level help */
+ +
4768
+
4769 /* Print help for builtin modules */
+
4770 print_module_help("subdir", &fuse_module_subdir_factory);
+
4771#ifdef HAVE_ICONV
+
4772 print_module_help("iconv", &fuse_module_iconv_factory);
+
4773#endif
+
4774
+
4775 /* Parse command line options in case we need to
+
4776 activate more modules */
+
4777 struct fuse_config conf = { .modules = NULL };
+
4778 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4779 fuse_lib_opt_proc) == -1
+
4780 || !conf.modules)
+
4781 return;
+
4782
+
4783 char *module;
+
4784 char *next;
+
4785 struct fuse_module *m;
+
4786
+
4787 // Iterate over all modules
+
4788 for (module = conf.modules; module; module = next) {
+
4789 char *p;
+
4790 for (p = module; *p && *p != ':'; p++);
+
4791 next = *p ? p + 1 : NULL;
+
4792 *p = '\0';
+
4793
+
4794 m = fuse_get_module(module);
+
4795 if (m)
+
4796 print_module_help(module, &m->factory);
+
4797 }
+
4798}
+
4799
+
4800static int fuse_init_intr_signal(int signum, int *installed)
+
4801{
+
4802 struct sigaction old_sa;
+
4803
+
4804 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4805 perror("fuse: cannot get old signal handler");
+
4806 return -1;
+
4807 }
+
4808
+
4809 if (old_sa.sa_handler == SIG_DFL) {
+
4810 struct sigaction sa;
+
4811
+
4812 memset(&sa, 0, sizeof(struct sigaction));
+
4813 sa.sa_handler = fuse_intr_sighandler;
+
4814 sigemptyset(&sa.sa_mask);
+
4815
+
4816 if (sigaction(signum, &sa, NULL) == -1) {
+
4817 perror("fuse: cannot set interrupt signal handler");
+
4818 return -1;
+
4819 }
+
4820 *installed = 1;
+
4821 }
+
4822 return 0;
+
4823}
+
4824
+
4825static void fuse_restore_intr_signal(int signum)
+
4826{
+
4827 struct sigaction sa;
+
4828
+
4829 memset(&sa, 0, sizeof(struct sigaction));
+
4830 sa.sa_handler = SIG_DFL;
+
4831 sigaction(signum, &sa, NULL);
+
4832}
+
4833
+
4834
+
4835static int fuse_push_module(struct fuse *f, const char *module,
+
4836 struct fuse_args *args)
+
4837{
+
4838 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4839 struct fuse_fs *newfs;
+
4840 struct fuse_module *m = fuse_get_module(module);
+
4841
+
4842 if (!m)
+
4843 return -1;
+
4844
+
4845 newfs = m->factory(args, fs);
+
4846 if (!newfs) {
+
4847 fuse_put_module(m);
+
4848 return -1;
+
4849 }
+
4850 f->fs = newfs;
+
4851 return 0;
+
4852}
+
4853
+
4854struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4855 void *user_data)
+
4856{
+
4857 struct fuse_fs *fs;
+
4858
+
4859 if (sizeof(struct fuse_operations) < op_size) {
+
4860 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4861 op_size = sizeof(struct fuse_operations);
+
4862 }
+
4863
+
4864 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4865 if (!fs) {
+
4866 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4867 return NULL;
+
4868 }
+
4869
+
4870 fs->user_data = user_data;
+
4871 if (op)
+
4872 memcpy(&fs->op, op, op_size);
+
4873 return fs;
+
4874}
+
4875
+
4876static int node_table_init(struct node_table *t)
+
4877{
+
4878 t->size = NODE_TABLE_MIN_SIZE;
+
4879 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4880 if (t->array == NULL) {
+
4881 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4882 return -1;
+
4883 }
+
4884 t->use = 0;
+
4885 t->split = 0;
+
4886
+
4887 return 0;
+
4888}
+
4889
+
4890static void *fuse_prune_nodes(void *fuse)
+
4891{
+
4892 struct fuse *f = fuse;
+
4893 int sleep_time;
+
4894
+
4895 pthread_setname_np(pthread_self(), "fuse_prune_nodes");
+
4896
+
4897 while(1) {
+
4898 sleep_time = fuse_clean_cache(f);
+
4899 sleep(sleep_time);
+
4900 }
+
4901 return NULL;
+
4902}
+
4903
+
4904int fuse_start_cleanup_thread(struct fuse *f)
+
4905{
+
4906 if (lru_enabled(f))
+
4907 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4908
+
4909 return 0;
+
4910}
+
4911
+
4912void fuse_stop_cleanup_thread(struct fuse *f)
+
4913{
+
4914 if (lru_enabled(f)) {
+
4915 pthread_mutex_lock(&f->lock);
+
4916 pthread_cancel(f->prune_thread);
+
4917 pthread_mutex_unlock(&f->lock);
+
4918 pthread_join(f->prune_thread, NULL);
+
4919 }
+
4920}
+
4921
+
4922/*
+
4923 * Not supposed to be called directly, but supposed to be called
+
4924 * through the fuse_new macro
+
4925 */
+
4926struct fuse *_fuse_new_31(struct fuse_args *args,
+
4927 const struct fuse_operations *op, size_t op_size,
+
4928 struct libfuse_version *version, void *user_data)
+
4929{
+
4930 struct fuse *f;
+
4931 struct node *root;
+
4932 struct fuse_fs *fs;
+
4933 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4934
+
4935 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4936 if (f == NULL) {
+
4937 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4938 goto out;
+
4939 }
+
4940
+
4941 f->conf.entry_timeout = 1.0;
+
4942 f->conf.attr_timeout = 1.0;
+
4943 f->conf.negative_timeout = 0.0;
+
4944 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
4945
+
4946 /* Parse options */
+
4947 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
4948 fuse_lib_opt_proc) == -1)
+
4949 goto out_free;
+
4950
+
4951 pthread_mutex_lock(&fuse_context_lock);
+
4952 static int builtin_modules_registered = 0;
+
4953 /* Have the builtin modules already been registered? */
+
4954 if (builtin_modules_registered == 0) {
+
4955 /* If not, register them. */
+
4956 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
4957#ifdef HAVE_ICONV
+
4958 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
4959#endif
+
4960 builtin_modules_registered= 1;
+
4961 }
+
4962 pthread_mutex_unlock(&fuse_context_lock);
+
4963
+
4964 if (fuse_create_context_key() == -1)
+
4965 goto out_free;
+
4966
+
4967 fs = fuse_fs_new(op, op_size, user_data);
+
4968 if (!fs)
+
4969 goto out_delete_context_key;
+
4970
+
4971 f->fs = fs;
+
4972
+
4973 /* Oh f**k, this is ugly! */
+
4974 if (!fs->op.lock) {
+
4975 llop.getlk = NULL;
+
4976 llop.setlk = NULL;
+
4977 }
+
4978
+
4979 f->pagesize = getpagesize();
+
4980 init_list_head(&f->partial_slabs);
+
4981 init_list_head(&f->full_slabs);
+
4982 init_list_head(&f->lru_table);
+
4983
+
4984 if (f->conf.modules) {
+
4985 char *module;
+
4986 char *next;
+
4987
+
4988 for (module = f->conf.modules; module; module = next) {
+
4989 char *p;
+
4990 for (p = module; *p && *p != ':'; p++);
+
4991 next = *p ? p + 1 : NULL;
+
4992 *p = '\0';
+
4993 if (module[0] &&
+
4994 fuse_push_module(f, module, args) == -1)
+
4995 goto out_free_fs;
+
4996 }
+
4997 }
+
4998
+
4999 if (!f->conf.ac_attr_timeout_set)
+
5000 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5001
+
5002#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5003 /*
+
5004 * In FreeBSD, we always use these settings as inode numbers
+
5005 * are needed to make getcwd(3) work.
+
5006 */
+
5007 f->conf.readdir_ino = 1;
+
5008#endif
+
5009
+
5010 /* not declared globally, to restrict usage of this function */
+
5011 struct fuse_session *fuse_session_new_versioned(
+
5012 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5013 size_t op_size, struct libfuse_version *version,
+
5014 void *userdata);
+
5015 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5016 f);
+
5017 if (f->se == NULL)
+
5018 goto out_free_fs;
+
5019
+
5020 if (f->conf.debug) {
+
5021 fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok);
+
5022 }
+
5023
+
5024 /* Trace topmost layer by default */
+
5025 f->fs->debug = f->conf.debug;
+
5026 f->ctr = 0;
+
5027 f->generation = 0;
+
5028 if (node_table_init(&f->name_table) == -1)
+
5029 goto out_free_session;
+
5030
+
5031 if (node_table_init(&f->id_table) == -1)
+
5032 goto out_free_name_table;
+
5033
+
5034 pthread_mutex_init(&f->lock, NULL);
+
5035
+
5036 root = alloc_node(f);
+
5037 if (root == NULL) {
+
5038 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5039 goto out_free_id_table;
+
5040 }
+
5041 if (lru_enabled(f)) {
+
5042 struct node_lru *lnode = node_lru(root);
+
5043 init_list_head(&lnode->lru);
+
5044 }
+
5045
+
5046 strcpy(root->inline_name, "/");
+
5047 root->name = root->inline_name;
+
5048 root->parent = NULL;
+
5049 root->nodeid = FUSE_ROOT_ID;
+
5050 inc_nlookup(root);
+
5051 hash_id(f, root);
+
5052
+
5053 return f;
+
5054
+
5055out_free_id_table:
+
5056 free(f->id_table.array);
+
5057out_free_name_table:
+
5058 free(f->name_table.array);
+
5059out_free_session:
+
5060 fuse_session_destroy(f->se);
+
5061out_free_fs:
+
5062 free(f->fs);
+
5063 free(f->conf.modules);
+
5064out_delete_context_key:
+
5065 fuse_delete_context_key();
+
5066out_free:
+
5067 free(f);
+
5068out:
+
5069 return NULL;
+
5070}
+
5071
+
5072/* Emulates 3.0-style fuse_new(), which processes --help */
+
5073FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5074struct fuse *_fuse_new_30(struct fuse_args *args,
+
5075 const struct fuse_operations *op,
+
5076 size_t op_size,
+
5077 struct libfuse_version *version,
+
5078 void *user_data)
+
5079{
+
5080 struct fuse_config conf = {0};
+
5081
+
5082 const struct fuse_opt opts[] = {
+
5083 FUSE_LIB_OPT("-h", show_help, 1),
+
5084 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5086 };
+
5087
+
5088 if (fuse_opt_parse(args, &conf, opts,
+
5089 fuse_lib_opt_proc) == -1)
+
5090 return NULL;
+
5091
+
5092 if (conf.show_help) {
+
5093 fuse_lib_help(args);
+
5094 return NULL;
+
5095 } else
+
5096 return _fuse_new_31(args, op, op_size, version, user_data);
+
5097}
+
5098
+
5099/* ABI compat version */
+
5100struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5101 size_t op_size, void *user_data);
+
5102FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5103struct fuse *fuse_new_31(struct fuse_args *args,
+
5104 const struct fuse_operations *op,
+
5105 size_t op_size, void *user_data)
+
5106{
+
5107 /* unknown version */
+
5108 struct libfuse_version version = { 0 };
+
5109
+
5110 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5111}
+
5112
+
5113/*
+
5114 * ABI compat version
+
5115 * Emulates 3.0-style fuse_new(), which processes --help
+
5116 */
+
5117struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5118 size_t op_size, void *user_data);
+
5119FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5120struct fuse *fuse_new_30(struct fuse_args *args,
+
5121 const struct fuse_operations *op,
+
5122 size_t op_size, void *user_data)
+
5123{
+
5124 struct fuse_config conf = {0};
+
5125
+
5126 const struct fuse_opt opts[] = {
+
5127 FUSE_LIB_OPT("-h", show_help, 1),
+
5128 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5130 };
+
5131
+
5132 if (fuse_opt_parse(args, &conf, opts,
+
5133 fuse_lib_opt_proc) == -1)
+
5134 return NULL;
+
5135
+
5136 if (conf.show_help) {
+
5137 fuse_lib_help(args);
+
5138 return NULL;
+
5139 } else
+
5140 return fuse_new_31(args, op, op_size, user_data);
+
5141}
+
5142
+
5143
+
5144void fuse_destroy(struct fuse *f)
+
5145{
+
5146 size_t i;
+
5147
+
5148 if (f->conf.intr && f->intr_installed)
+
5149 fuse_restore_intr_signal(f->conf.intr_signal);
+
5150
+
5151 if (f->fs) {
+
5152 fuse_create_context(f);
+
5153
+
5154 for (i = 0; i < f->id_table.size; i++) {
+
5155 struct node *node;
+
5156
+
5157 for (node = f->id_table.array[i]; node != NULL;
+
5158 node = node->id_next) {
+
5159 if (node->is_hidden) {
+
5160 char *path;
+
5161 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5162 fuse_fs_unlink(f->fs, path);
+
5163 free(path);
+
5164 }
+
5165 }
+
5166 }
+
5167 }
+
5168 }
+
5169 for (i = 0; i < f->id_table.size; i++) {
+
5170 struct node *node;
+
5171 struct node *next;
+
5172
+
5173 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5174 next = node->id_next;
+
5175 free_node(f, node);
+
5176 f->id_table.use--;
+
5177 }
+
5178 }
+
5179 assert(list_empty(&f->partial_slabs));
+
5180 assert(list_empty(&f->full_slabs));
+
5181
+
5182 while (fuse_modules) {
+
5183 fuse_put_module(fuse_modules);
+
5184 }
+
5185 free(f->id_table.array);
+
5186 free(f->name_table.array);
+
5187 pthread_mutex_destroy(&f->lock);
+
5188 fuse_session_destroy(f->se);
+
5189 free(f->fs);
+
5190 free(f->conf.modules);
+
5191 free(f);
+
5192 fuse_delete_context_key();
+
5193}
+
5194
+
5195int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5196 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5197}
+
5198
+
5199
+
5200void fuse_unmount(struct fuse *f) {
+ +
5202}
+
5203
+
5204int fuse_version(void)
+
5205{
+
5206 return FUSE_VERSION;
+
5207}
+
5208
+
5209const char *fuse_pkgversion(void)
+
5210{
+
5211 return PACKAGE_VERSION;
+
5212}
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4654
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
int fuse_interrupted(void)
Definition fuse.c:4663
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4904
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4673
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1403
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4639
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4433
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4912
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_CAP_EXPORT_SUPPORT
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5218
+
int fuse_version(void)
Definition fuse.c:5213
+
@ FUSE_BUF_SPLICE_MOVE
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ + +
uint32_t no_interrupt
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__i_8h_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..145513b --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__i_8h_source.html @@ -0,0 +1,249 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include "fuse.h"
+
10#include "fuse_lowlevel.h"
+
11
+
12#include <stdbool.h>
+
13
+
14#define MIN(a, b) \
+
15({ \
+
16 typeof(a) _a = (a); \
+
17 typeof(b) _b = (b); \
+
18 _a < _b ? _a : _b; \
+
19})
+
20
+
21struct mount_opts;
+
22
+
23struct fuse_req {
+
24 struct fuse_session *se;
+
25 uint64_t unique;
+
26 _Atomic int ref_cnt;
+
27 pthread_mutex_t lock;
+
28 struct fuse_ctx ctx;
+
29 struct fuse_chan *ch;
+
30 int interrupted;
+
31 unsigned int ioctl_64bit : 1;
+
32 union {
+
33 struct {
+
34 uint64_t unique;
+
35 } i;
+
36 struct {
+ +
38 void *data;
+
39 } ni;
+
40 } u;
+
41 struct fuse_req *next;
+
42 struct fuse_req *prev;
+
43};
+
44
+
45struct fuse_notify_req {
+
46 uint64_t unique;
+
47 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
48 const void *, const struct fuse_buf *);
+
49 struct fuse_notify_req *next;
+
50 struct fuse_notify_req *prev;
+
51};
+
52
+
53struct fuse_session {
+
54 char *mountpoint;
+
55 volatile int exited;
+
56 int fd;
+
57 struct fuse_custom_io *io;
+
58 struct mount_opts *mo;
+
59 int debug;
+
60 int deny_others;
+
61 struct fuse_lowlevel_ops op;
+
62 int got_init;
+
63 struct cuse_data *cuse_data;
+
64 void *userdata;
+
65 uid_t owner;
+
66 struct fuse_conn_info conn;
+
67 struct fuse_req list;
+
68 struct fuse_req interrupts;
+
69 pthread_mutex_t lock;
+
70 int got_destroy;
+
71 pthread_key_t pipe_key;
+
72 int broken_splice_nonblock;
+
73 uint64_t notify_ctr;
+
74 struct fuse_notify_req notify_list;
+
75 size_t bufsize;
+
76 int error;
+
77
+
78 /* This is useful if any kind of ABI incompatibility is found at
+
79 * a later version, to 'fix' it at run time.
+
80 */
+
81 struct libfuse_version version;
+
82 bool buf_reallocable;
+
83};
+
84
+
85struct fuse_chan {
+
86 pthread_mutex_t lock;
+
87 int ctr;
+
88 int fd;
+
89};
+
90
+
98struct fuse_module {
+
99 char *name;
+
100 fuse_module_factory_t factory;
+
101 struct fuse_module *next;
+
102 struct fusemod_so *so;
+
103 int ctr;
+
104};
+
105
+
114#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
115struct fuse_loop_config
+
116{
+
117 /* verififier that a correct struct was was passed. This is especially
+
118 * needed, as versions below (3, 12) were using a public struct
+
119 * (now called fuse_loop_config_v1), which was hard to extend with
+
120 * additional parameters, without risking that file system implementations
+
121 * would not have noticed and might either pass uninitialized members
+
122 * or even too small structs.
+
123 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
124 * or 1. v2 or even higher version just need to set a value here
+
125 * which not conflicting and very unlikely as having been set by
+
126 * file system implementation.
+
127 */
+
128 int version_id;
+
129
+
134 int clone_fd;
+ +
147
+
153 unsigned int max_threads;
+
154};
+
155#endif
+
156
+
157/* ----------------------------------------------------------- *
+
158 * Channel interface (when using -o clone_fd) *
+
159 * ----------------------------------------------------------- */
+
160
+
167struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
168
+
174void fuse_chan_put(struct fuse_chan *ch);
+
175
+
176struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
177void destroy_mount_opts(struct mount_opts *mo);
+
178void fuse_mount_version(void);
+
179unsigned get_max_read(struct mount_opts *o);
+
180void fuse_kern_unmount(const char *mountpoint, int fd);
+
181int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
182
+
183int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
184 int count);
+
185void fuse_free_req(fuse_req_t req);
+
186
+
187void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
188
+
189int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
190
+
191void fuse_buf_free(struct fuse_buf *buf);
+
192
+
193int fuse_session_receive_buf_internal(struct fuse_session *se,
+
194 struct fuse_buf *buf,
+
195 struct fuse_chan *ch);
+
196void fuse_session_process_buf_internal(struct fuse_session *se,
+
197 const struct fuse_buf *buf,
+
198 struct fuse_chan *ch);
+
199
+
200struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
201 size_t op_size, void *private_data);
+
202int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
203int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
204
+
210int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
211
+
212
+
213/*
+
214 * This can be changed dynamically on recent kernels through the
+
215 * /proc/sys/fs/fuse/max_pages_limit interface.
+
216 *
+
217 * Older kernels will always use the default value.
+
218 */
+
219#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
220#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
221
+
222/* room needed in buffer to accommodate header */
+
223#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
224
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1403
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + +
unsigned int max_threads
Definition fuse_i.h:153
+
unsigned int max_idle_threads
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__log_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..baf178d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__log_8c_source.html @@ -0,0 +1,162 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdarg.h>
+
14#include <stdio.h>
+
15#include <stdbool.h>
+
16#include <syslog.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(__attribute__((unused)) enum fuse_log_level level,
+
23 const char *fmt, va_list ap)
+
24{
+
25 if (to_syslog) {
+
26 int sys_log_level = LOG_ERR;
+
27
+
28 /*
+
29 * with glibc fuse_log_level has identical values as
+
30 * syslog levels, but we also support BSD - better we convert to
+
31 * be sure.
+
32 */
+
33 switch (level) {
+
34 case FUSE_LOG_DEBUG:
+
35 sys_log_level = LOG_DEBUG;
+
36 break;
+
37 case FUSE_LOG_INFO:
+
38 sys_log_level = LOG_INFO;
+
39 break;
+
40 case FUSE_LOG_NOTICE:
+
41 sys_log_level = LOG_NOTICE;
+
42 break;
+
43 case FUSE_LOG_WARNING:
+
44 sys_log_level = LOG_WARNING;
+
45 break;
+
46 case FUSE_LOG_ERR:
+
47 sys_log_level = LOG_ERR;
+
48 break;
+
49 case FUSE_LOG_CRIT:
+
50 sys_log_level = LOG_CRIT;
+
51 break;
+
52 case FUSE_LOG_ALERT:
+
53 sys_log_level = LOG_ALERT;
+
54 break;
+
55 case FUSE_LOG_EMERG:
+
56 sys_log_level = LOG_EMERG;
+
57 }
+
58
+
59 char log[MAX_SYSLOG_LINE_LEN];
+
60 vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap);
+
61 syslog(sys_log_level, "%s", log);
+
62 } else {
+
63 vfprintf(stderr, fmt, ap);
+
64 }
+
65}
+
66
+
67static fuse_log_func_t log_func = default_log_func;
+
68
+ +
70{
+
71 if (!func)
+
72 func = default_log_func;
+
73
+
74 log_func = func;
+
75}
+
76
+
77void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
78{
+
79 va_list ap;
+
80
+
81 va_start(ap, fmt);
+
82 log_func(level, fmt, ap);
+
83 va_end(ap);
+
84}
+
85
+
86void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
87{
+
88 to_syslog = true;
+
89
+
90 openlog(ident, option, facility);
+
91}
+
92
+
93void fuse_log_close_syslog(void)
+
94{
+
95 closelog();
+
96}
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..ea157bd --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop_8c_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+ +
45 return res;
+
46}
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_reset(struct fuse_session *se)
+ +
void * mem
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop__mt_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..349ca71 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,599 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <string.h>
+
23#include <unistd.h>
+
24#include <signal.h>
+
25#include <semaphore.h>
+
26#include <errno.h>
+
27#include <sys/time.h>
+
28#include <sys/ioctl.h>
+
29#include <assert.h>
+
30#include <limits.h>
+
31
+
32/* Environment var controlling the thread stack size */
+
33#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
34
+
35#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
36#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
37#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
38#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
39 * by default */
+
40
+
41/* an arbitrary large value that cannot be valid */
+
42#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
43
+
44struct fuse_worker {
+
45 struct fuse_worker *prev;
+
46 struct fuse_worker *next;
+
47 pthread_t thread_id;
+
48
+
49 // We need to include fuse_buf so that we can properly free
+
50 // it when a thread is terminated by pthread_cancel().
+
51 struct fuse_buf fbuf;
+
52 struct fuse_chan *ch;
+
53 struct fuse_mt *mt;
+
54};
+
55
+
56struct fuse_mt {
+
57 pthread_mutex_t lock;
+
58 int numworker;
+
59 int numavail;
+
60 struct fuse_session *se;
+
61 struct fuse_worker main;
+
62 sem_t finish;
+
63 int exit;
+
64 int error;
+
65 int clone_fd;
+
66 int max_idle;
+
67 int max_threads;
+
68};
+
69
+
70static struct fuse_chan *fuse_chan_new(int fd)
+
71{
+
72 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
73 if (ch == NULL) {
+
74 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
75 return NULL;
+
76 }
+
77
+
78 memset(ch, 0, sizeof(*ch));
+
79 ch->fd = fd;
+
80 ch->ctr = 1;
+
81 pthread_mutex_init(&ch->lock, NULL);
+
82
+
83 return ch;
+
84}
+
85
+
86struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
87{
+
88 assert(ch->ctr > 0);
+
89 pthread_mutex_lock(&ch->lock);
+
90 ch->ctr++;
+
91 pthread_mutex_unlock(&ch->lock);
+
92
+
93 return ch;
+
94}
+
95
+
96void fuse_chan_put(struct fuse_chan *ch)
+
97{
+
98 if (ch == NULL)
+
99 return;
+
100 pthread_mutex_lock(&ch->lock);
+
101 ch->ctr--;
+
102 if (!ch->ctr) {
+
103 pthread_mutex_unlock(&ch->lock);
+
104 close(ch->fd);
+
105 pthread_mutex_destroy(&ch->lock);
+
106 free(ch);
+
107 } else
+
108 pthread_mutex_unlock(&ch->lock);
+
109}
+
110
+
111static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
112{
+
113 struct fuse_worker *prev = next->prev;
+
114 w->next = next;
+
115 w->prev = prev;
+
116 prev->next = w;
+
117 next->prev = w;
+
118}
+
119
+
120static void list_del_worker(struct fuse_worker *w)
+
121{
+
122 struct fuse_worker *prev = w->prev;
+
123 struct fuse_worker *next = w->next;
+
124 prev->next = next;
+
125 next->prev = prev;
+
126}
+
127
+
128static int fuse_loop_start_thread(struct fuse_mt *mt);
+
129
+
130static void *fuse_do_work(void *data)
+
131{
+
132 struct fuse_worker *w = (struct fuse_worker *) data;
+
133 struct fuse_mt *mt = w->mt;
+
134
+
135 pthread_setname_np(pthread_self(), "fuse_worker");
+
136
+
137 while (!fuse_session_exited(mt->se)) {
+
138 int isforget = 0;
+
139 int res;
+
140
+
141 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
142 res = fuse_session_receive_buf_internal(mt->se, &w->fbuf,
+
143 w->ch);
+
144 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
145 if (res == -EINTR)
+
146 continue;
+
147 if (res <= 0) {
+
148 if (res < 0) {
+
149 fuse_session_exit(mt->se);
+
150 mt->error = res;
+
151 }
+
152 break;
+
153 }
+
154
+
155 pthread_mutex_lock(&mt->lock);
+
156 if (mt->exit) {
+
157 pthread_mutex_unlock(&mt->lock);
+
158 return NULL;
+
159 }
+
160
+
161 /*
+
162 * This disgusting hack is needed so that zillions of threads
+
163 * are not created on a burst of FORGET messages
+
164 */
+
165 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
166 struct fuse_in_header *in = w->fbuf.mem;
+
167
+
168 if (in->opcode == FUSE_FORGET ||
+
169 in->opcode == FUSE_BATCH_FORGET)
+
170 isforget = 1;
+
171 }
+
172
+
173 if (!isforget)
+
174 mt->numavail--;
+
175 if (mt->numavail == 0 && mt->numworker < mt->max_threads)
+
176 fuse_loop_start_thread(mt);
+
177 pthread_mutex_unlock(&mt->lock);
+
178
+
179 fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch);
+
180
+
181 pthread_mutex_lock(&mt->lock);
+
182 if (!isforget)
+
183 mt->numavail++;
+
184
+
185 /* creating and destroying threads is rather expensive - and there is
+
186 * not much gain from destroying existing threads. It is therefore
+
187 * discouraged to set max_idle to anything else than -1. If there
+
188 * is indeed a good reason to destruct threads it should be done
+
189 * delayed, a moving average might be useful for that.
+
190 */
+
191 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
192 if (mt->exit) {
+
193 pthread_mutex_unlock(&mt->lock);
+
194 return NULL;
+
195 }
+
196 list_del_worker(w);
+
197 mt->numavail--;
+
198 mt->numworker--;
+
199 pthread_mutex_unlock(&mt->lock);
+
200
+
201 pthread_detach(w->thread_id);
+
202 fuse_buf_free(&w->fbuf);
+
203 fuse_chan_put(w->ch);
+
204 free(w);
+
205 return NULL;
+
206 }
+
207 pthread_mutex_unlock(&mt->lock);
+
208 }
+
209
+
210 sem_post(&mt->finish);
+
211
+
212 return NULL;
+
213}
+
214
+
215int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
216{
+
217 sigset_t oldset;
+
218 sigset_t newset;
+
219 int res;
+
220 pthread_attr_t attr;
+
221 char *stack_size;
+
222
+
223 /* Override default stack size
+
224 * XXX: This should ideally be a parameter option. It is rather
+
225 * well hidden here.
+
226 */
+
227 pthread_attr_init(&attr);
+
228 stack_size = getenv(ENVNAME_THREAD_STACK);
+
229 if (stack_size) {
+
230 long size;
+
231
+
232 res = libfuse_strtol(stack_size, &size);
+
233 if (res)
+
234 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
235 stack_size);
+
236 else if (pthread_attr_setstacksize(&attr, size))
+
237 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
238 size);
+
239 }
+
240
+
241 /* Disallow signal reception in worker threads */
+
242 sigemptyset(&newset);
+
243 sigaddset(&newset, SIGTERM);
+
244 sigaddset(&newset, SIGINT);
+
245 sigaddset(&newset, SIGHUP);
+
246 sigaddset(&newset, SIGQUIT);
+
247 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
248 res = pthread_create(thread_id, &attr, func, arg);
+
249 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
250 pthread_attr_destroy(&attr);
+
251 if (res != 0) {
+
252 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
253 strerror(res));
+
254 return -1;
+
255 }
+
256
+
257 return 0;
+
258}
+
259
+
260static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
261{
+
262 int res;
+
263 int clonefd;
+
264 uint32_t masterfd;
+
265 const char *devname = "/dev/fuse";
+
266
+
267#ifndef O_CLOEXEC
+
268#define O_CLOEXEC 0
+
269#endif
+
270 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
271 if (clonefd == -1) {
+
272 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
273 strerror(errno));
+
274 return -1;
+
275 }
+
276#ifndef O_CLOEXEC
+
277 fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
278#endif
+
279
+
280 masterfd = se->fd;
+
281 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
282 if (res == -1) {
+
283 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
284 strerror(errno));
+
285 close(clonefd);
+
286 return -1;
+
287 }
+
288 return clonefd;
+
289}
+
290
+
291static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
292{
+
293 int clonefd;
+
294 struct fuse_session *se = mt->se;
+
295 struct fuse_chan *newch;
+
296
+
297 if (se->io != NULL) {
+
298 if (se->io->clone_fd != NULL)
+
299 clonefd = se->io->clone_fd(se->fd);
+
300 else
+
301 return NULL;
+
302 } else {
+
303 clonefd = fuse_clone_chan_fd_default(se);
+
304 }
+
305 if (clonefd < 0)
+
306 return NULL;
+
307
+
308 newch = fuse_chan_new(clonefd);
+
309 if (newch == NULL)
+
310 close(clonefd);
+
311
+
312 return newch;
+
313}
+
314
+
315static int fuse_loop_start_thread(struct fuse_mt *mt)
+
316{
+
317 int res;
+
318
+
319 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
320 if (!w) {
+
321 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
322 return -1;
+
323 }
+
324 memset(w, 0, sizeof(struct fuse_worker));
+
325 w->fbuf.mem = NULL;
+
326 w->mt = mt;
+
327
+
328 w->ch = NULL;
+
329 if (mt->clone_fd) {
+
330 w->ch = fuse_clone_chan(mt);
+
331 if(!w->ch) {
+
332 /* Don't attempt this again */
+
333 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
334 "without -o clone_fd.\n");
+
335 mt->clone_fd = 0;
+
336 }
+
337 }
+
338
+
339 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
340 if (res == -1) {
+
341 fuse_chan_put(w->ch);
+
342 free(w);
+
343 return -1;
+
344 }
+
345 list_add_worker(w, &mt->main);
+
346 mt->numavail ++;
+
347 mt->numworker ++;
+
348
+
349 return 0;
+
350}
+
351
+
352static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
353{
+
354 pthread_join(w->thread_id, NULL);
+
355 pthread_mutex_lock(&mt->lock);
+
356 list_del_worker(w);
+
357 pthread_mutex_unlock(&mt->lock);
+
358 fuse_buf_free(&w->fbuf);
+
359 fuse_chan_put(w->ch);
+
360 free(w);
+
361}
+
362
+
363int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
364FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
365int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
366{
+
367int err;
+
368 struct fuse_mt mt;
+
369 struct fuse_worker *w;
+
370 int created_config = 0;
+
371
+
372 if (config) {
+
373 err = fuse_loop_cfg_verify(config);
+
374 if (err)
+
375 return err;
+
376 } else {
+
377 /* The caller does not care about parameters - use the default */
+
378 config = fuse_loop_cfg_create();
+
379 created_config = 1;
+
380 }
+
381
+
382
+
383 memset(&mt, 0, sizeof(struct fuse_mt));
+
384 mt.se = se;
+
385 mt.clone_fd = config->clone_fd;
+
386 mt.error = 0;
+
387 mt.numworker = 0;
+
388 mt.numavail = 0;
+
389 mt.max_idle = config->max_idle_threads;
+
390 mt.max_threads = config->max_threads;
+
391 mt.main.thread_id = pthread_self();
+
392 mt.main.prev = mt.main.next = &mt.main;
+
393 sem_init(&mt.finish, 0, 0);
+
394 pthread_mutex_init(&mt.lock, NULL);
+
395
+
396 pthread_mutex_lock(&mt.lock);
+
397 err = fuse_loop_start_thread(&mt);
+
398 pthread_mutex_unlock(&mt.lock);
+
399 if (!err) {
+
400 /* sem_wait() is interruptible */
+
401 while (!fuse_session_exited(se))
+
402 sem_wait(&mt.finish);
+
403
+
404 pthread_mutex_lock(&mt.lock);
+
405 for (w = mt.main.next; w != &mt.main; w = w->next)
+
406 pthread_cancel(w->thread_id);
+
407 mt.exit = 1;
+
408 pthread_mutex_unlock(&mt.lock);
+
409
+
410 while (mt.main.next != &mt.main)
+
411 fuse_join_worker(&mt, mt.main.next);
+
412
+
413 err = mt.error;
+
414 }
+
415
+
416 pthread_mutex_destroy(&mt.lock);
+
417 sem_destroy(&mt.finish);
+
418 if(se->error != 0)
+
419 err = se->error;
+ +
421
+
422 if (created_config) {
+
423 fuse_loop_cfg_destroy(config);
+
424 config = NULL;
+
425 }
+
426
+
427 return err;
+
428}
+
429
+
430int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
431FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
432int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
433{
+
434 int err;
+
435 struct fuse_loop_config *config = NULL;
+
436
+
437 if (config_v1 != NULL) {
+
438 /* convert the given v1 config */
+
439 config = fuse_loop_cfg_create();
+
440 if (config == NULL)
+
441 return ENOMEM;
+
442
+
443 fuse_loop_cfg_convert(config, config_v1);
+
444 }
+
445
+
446 err = fuse_session_loop_mt_312(se, config);
+
447
+
448 fuse_loop_cfg_destroy(config);
+
449
+
450 return err;
+
451}
+
452
+
453
+
454int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
455FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
456int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
457{
+
458 int err;
+
459 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
460 if (clone_fd > 0)
+
461 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
462 err = fuse_session_loop_mt_312(se, config);
+
463
+
464 fuse_loop_cfg_destroy(config);
+
465
+
466 return err;
+
467}
+
468
+
469struct fuse_loop_config *fuse_loop_cfg_create(void)
+
470{
+
471 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
472 if (config == NULL)
+
473 return NULL;
+
474
+
475 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
476 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
477 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
478 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
479
+
480 return config;
+
481}
+
482
+
483void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
484{
+
485 free(config);
+
486}
+
487
+
488int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
489{
+
490 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
491 return -EINVAL;
+
492
+
493 return 0;
+
494}
+
495
+
496void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
497 struct fuse_loop_config_v1 *v1_conf)
+
498{
+
499 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
500
+
501 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
502}
+
503
+
504void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
505 unsigned int value)
+
506{
+
507 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
508 if (value != UINT_MAX)
+
509 fuse_log(FUSE_LOG_ERR,
+
510 "Ignoring invalid max threads value "
+
511 "%u > max (%u).\n", value,
+
512 FUSE_LOOP_MT_MAX_THREADS);
+
513 return;
+
514 }
+
515 config->max_idle_threads = value;
+
516}
+
517
+
518void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
519 unsigned int value)
+
520{
+
521 config->max_threads = value;
+
522}
+
523
+
524void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
525 unsigned int value)
+
526{
+
527 config->clone_fd = value;
+
528}
+
529
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_reset(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:153
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..c747a1e --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,3749 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include <stdbool.h>
+
13#define _GNU_SOURCE
+
14
+
15#include "fuse_config.h"
+
16#include "fuse_i.h"
+
17#include "fuse_kernel.h"
+
18#include "fuse_opt.h"
+
19#include "fuse_misc.h"
+
20#include "mount_util.h"
+
21#include "util.h"
+
22
+
23#include <stdio.h>
+
24#include <stdlib.h>
+
25#include <stddef.h>
+
26#include <stdalign.h>
+
27#include <string.h>
+
28#include <unistd.h>
+
29#include <limits.h>
+
30#include <errno.h>
+
31#include <assert.h>
+
32#include <sys/file.h>
+
33#include <sys/ioctl.h>
+
34
+
35#ifndef F_LINUX_SPECIFIC_BASE
+
36#define F_LINUX_SPECIFIC_BASE 1024
+
37#endif
+
38#ifndef F_SETPIPE_SZ
+
39#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
40#endif
+
41
+
42
+
43#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
44#define OFFSET_MAX 0x7fffffffffffffffLL
+
45
+
46#define container_of(ptr, type, member) ({ \
+
47 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
48 (type *)( (char *)__mptr - offsetof(type,member) );})
+
49
+
50struct fuse_pollhandle {
+
51 uint64_t kh;
+
52 struct fuse_session *se;
+
53};
+
54
+
55static size_t pagesize;
+
56
+
57static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
58{
+
59 pagesize = getpagesize();
+
60}
+
61
+
62static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
63{
+
64 attr->ino = stbuf->st_ino;
+
65 attr->mode = stbuf->st_mode;
+
66 attr->nlink = stbuf->st_nlink;
+
67 attr->uid = stbuf->st_uid;
+
68 attr->gid = stbuf->st_gid;
+
69 attr->rdev = stbuf->st_rdev;
+
70 attr->size = stbuf->st_size;
+
71 attr->blksize = stbuf->st_blksize;
+
72 attr->blocks = stbuf->st_blocks;
+
73 attr->atime = stbuf->st_atime;
+
74 attr->mtime = stbuf->st_mtime;
+
75 attr->ctime = stbuf->st_ctime;
+
76 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
77 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
78 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
79}
+
80
+
81static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
82{
+
83 stbuf->st_mode = attr->mode;
+
84 stbuf->st_uid = attr->uid;
+
85 stbuf->st_gid = attr->gid;
+
86 stbuf->st_size = attr->size;
+
87 stbuf->st_atime = attr->atime;
+
88 stbuf->st_mtime = attr->mtime;
+
89 stbuf->st_ctime = attr->ctime;
+
90 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
91 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
92 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
93}
+
94
+
95static size_t iov_length(const struct iovec *iov, size_t count)
+
96{
+
97 size_t seg;
+
98 size_t ret = 0;
+
99
+
100 for (seg = 0; seg < count; seg++)
+
101 ret += iov[seg].iov_len;
+
102 return ret;
+
103}
+
104
+
105static void list_init_req(struct fuse_req *req)
+
106{
+
107 req->next = req;
+
108 req->prev = req;
+
109}
+
110
+
111static void list_del_req(struct fuse_req *req)
+
112{
+
113 struct fuse_req *prev = req->prev;
+
114 struct fuse_req *next = req->next;
+
115 prev->next = next;
+
116 next->prev = prev;
+
117}
+
118
+
119static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
120{
+
121 struct fuse_req *prev = next->prev;
+
122 req->next = next;
+
123 req->prev = prev;
+
124 prev->next = req;
+
125 next->prev = req;
+
126}
+
127
+
128static void destroy_req(fuse_req_t req)
+
129{
+
130 assert(req->ch == NULL);
+
131 pthread_mutex_destroy(&req->lock);
+
132 free(req);
+
133}
+
134
+
135void fuse_free_req(fuse_req_t req)
+
136{
+
137 int ctr;
+
138 struct fuse_session *se = req->se;
+
139
+
140 if (se->conn.no_interrupt) {
+
141 ctr = --req->ref_cnt;
+
142 fuse_chan_put(req->ch);
+
143 req->ch = NULL;
+
144 } else {
+
145 pthread_mutex_lock(&se->lock);
+
146 req->u.ni.func = NULL;
+
147 req->u.ni.data = NULL;
+
148 list_del_req(req);
+
149 ctr = --req->ref_cnt;
+
150 fuse_chan_put(req->ch);
+
151 req->ch = NULL;
+
152 pthread_mutex_unlock(&se->lock);
+
153 }
+
154 if (!ctr)
+
155 destroy_req(req);
+
156}
+
157
+
158static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
159{
+
160 struct fuse_req *req;
+
161
+
162 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
163 if (req == NULL) {
+
164 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
165 } else {
+
166 req->se = se;
+
167 req->ref_cnt = 1;
+
168 list_init_req(req);
+
169 pthread_mutex_init(&req->lock, NULL);
+
170 }
+
171
+
172 return req;
+
173}
+
174
+
175/* Send data. If *ch* is NULL, send via session master fd */
+
176static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
177 struct iovec *iov, int count)
+
178{
+
179 struct fuse_out_header *out = iov[0].iov_base;
+
180
+
181 assert(se != NULL);
+
182 out->len = iov_length(iov, count);
+
183 if (se->debug) {
+
184 if (out->unique == 0) {
+
185 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
186 out->error, out->len);
+
187 } else if (out->error) {
+
188 fuse_log(FUSE_LOG_DEBUG,
+
189 " unique: %llu, error: %i (%s), outsize: %i\n",
+
190 (unsigned long long) out->unique, out->error,
+
191 strerror(-out->error), out->len);
+
192 } else {
+
193 fuse_log(FUSE_LOG_DEBUG,
+
194 " unique: %llu, success, outsize: %i\n",
+
195 (unsigned long long) out->unique, out->len);
+
196 }
+
197 }
+
198
+
199 ssize_t res;
+
200 if (se->io != NULL)
+
201 /* se->io->writev is never NULL if se->io is not NULL as
+
202 specified by fuse_session_custom_io()*/
+
203 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
204 se->userdata);
+
205 else
+
206 res = writev(ch ? ch->fd : se->fd, iov, count);
+
207
+
208 int err = errno;
+
209
+
210 if (res == -1) {
+
211 /* ENOENT means the operation was interrupted */
+
212 if (!fuse_session_exited(se) && err != ENOENT)
+
213 perror("fuse: writing device");
+
214 return -err;
+
215 }
+
216
+
217 return 0;
+
218}
+
219
+
220
+
221int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
222 int count)
+
223{
+
224 struct fuse_out_header out;
+
225
+
226#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
227 const char *str = strerrordesc_np(error * -1);
+
228 if ((str == NULL && error != 0) || error > 0) {
+
229#else
+
230 if (error <= -1000 || error > 0) {
+
231#endif
+
232 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
233 error = -ERANGE;
+
234 }
+
235
+
236 out.unique = req->unique;
+
237 out.error = error;
+
238
+
239 iov[0].iov_base = &out;
+
240 iov[0].iov_len = sizeof(struct fuse_out_header);
+
241
+
242 return fuse_send_msg(req->se, req->ch, iov, count);
+
243}
+
244
+
245static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
246 int count)
+
247{
+
248 int res;
+
249
+
250 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
251 fuse_free_req(req);
+
252 return res;
+
253}
+
254
+
255static int send_reply(fuse_req_t req, int error, const void *arg,
+
256 size_t argsize)
+
257{
+
258 struct iovec iov[2];
+
259 int count = 1;
+
260 if (argsize) {
+
261 iov[1].iov_base = (void *) arg;
+
262 iov[1].iov_len = argsize;
+
263 count++;
+
264 }
+
265 return send_reply_iov(req, error, iov, count);
+
266}
+
267
+
268int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
269{
+
270 int res;
+
271 struct iovec *padded_iov;
+
272
+
273 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
274 if (padded_iov == NULL)
+
275 return fuse_reply_err(req, ENOMEM);
+
276
+
277 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
278 count++;
+
279
+
280 res = send_reply_iov(req, 0, padded_iov, count);
+
281 free(padded_iov);
+
282
+
283 return res;
+
284}
+
285
+
286
+
287/* `buf` is allowed to be empty so that the proper size may be
+
288 allocated by the caller */
+
289size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
290 const char *name, const struct stat *stbuf, off_t off)
+
291{
+
292 (void)req;
+
293 size_t namelen;
+
294 size_t entlen;
+
295 size_t entlen_padded;
+
296 struct fuse_dirent *dirent;
+
297
+
298 namelen = strlen(name);
+
299 entlen = FUSE_NAME_OFFSET + namelen;
+
300 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
301
+
302 if ((buf == NULL) || (entlen_padded > bufsize))
+
303 return entlen_padded;
+
304
+
305 dirent = (struct fuse_dirent*) buf;
+
306 dirent->ino = stbuf->st_ino;
+
307 dirent->off = off;
+
308 dirent->namelen = namelen;
+
309 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
310 memcpy(dirent->name, name, namelen);
+
311 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
312
+
313 return entlen_padded;
+
314}
+
315
+
316static void convert_statfs(const struct statvfs *stbuf,
+
317 struct fuse_kstatfs *kstatfs)
+
318{
+
319 kstatfs->bsize = stbuf->f_bsize;
+
320 kstatfs->frsize = stbuf->f_frsize;
+
321 kstatfs->blocks = stbuf->f_blocks;
+
322 kstatfs->bfree = stbuf->f_bfree;
+
323 kstatfs->bavail = stbuf->f_bavail;
+
324 kstatfs->files = stbuf->f_files;
+
325 kstatfs->ffree = stbuf->f_ffree;
+
326 kstatfs->namelen = stbuf->f_namemax;
+
327}
+
328
+
329static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
330{
+
331 return send_reply(req, 0, arg, argsize);
+
332}
+
333
+
334int fuse_reply_err(fuse_req_t req, int err)
+
335{
+
336 return send_reply(req, -err, NULL, 0);
+
337}
+
338
+ +
340{
+
341 fuse_free_req(req);
+
342}
+
343
+
344static unsigned long calc_timeout_sec(double t)
+
345{
+
346 if (t > (double) ULONG_MAX)
+
347 return ULONG_MAX;
+
348 else if (t < 0.0)
+
349 return 0;
+
350 else
+
351 return (unsigned long) t;
+
352}
+
353
+
354static unsigned int calc_timeout_nsec(double t)
+
355{
+
356 double f = t - (double) calc_timeout_sec(t);
+
357 if (f < 0.0)
+
358 return 0;
+
359 else if (f >= 0.999999999)
+
360 return 999999999;
+
361 else
+
362 return (unsigned int) (f * 1.0e9);
+
363}
+
364
+
365static void fill_entry(struct fuse_entry_out *arg,
+
366 const struct fuse_entry_param *e)
+
367{
+
368 arg->nodeid = e->ino;
+
369 arg->generation = e->generation;
+
370 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
371 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
372 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
373 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
374 convert_stat(&e->attr, &arg->attr);
+
375}
+
376
+
377/* `buf` is allowed to be empty so that the proper size may be
+
378 allocated by the caller */
+
379size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
380 const char *name,
+
381 const struct fuse_entry_param *e, off_t off)
+
382{
+
383 (void)req;
+
384 size_t namelen;
+
385 size_t entlen;
+
386 size_t entlen_padded;
+
387
+
388 namelen = strlen(name);
+
389 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
390 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
391 if ((buf == NULL) || (entlen_padded > bufsize))
+
392 return entlen_padded;
+
393
+
394 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
395 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
396 fill_entry(&dp->entry_out, e);
+
397
+
398 struct fuse_dirent *dirent = &dp->dirent;
+
399 dirent->ino = e->attr.st_ino;
+
400 dirent->off = off;
+
401 dirent->namelen = namelen;
+
402 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
403 memcpy(dirent->name, name, namelen);
+
404 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
405
+
406 return entlen_padded;
+
407}
+
408
+
409static void fill_open(struct fuse_open_out *arg,
+
410 const struct fuse_file_info *f)
+
411{
+
412 arg->fh = f->fh;
+
413 if (f->backing_id > 0) {
+
414 arg->backing_id = f->backing_id;
+
415 arg->open_flags |= FOPEN_PASSTHROUGH;
+
416 }
+
417 if (f->direct_io)
+
418 arg->open_flags |= FOPEN_DIRECT_IO;
+
419 if (f->keep_cache)
+
420 arg->open_flags |= FOPEN_KEEP_CACHE;
+
421 if (f->cache_readdir)
+
422 arg->open_flags |= FOPEN_CACHE_DIR;
+
423 if (f->nonseekable)
+
424 arg->open_flags |= FOPEN_NONSEEKABLE;
+
425 if (f->noflush)
+
426 arg->open_flags |= FOPEN_NOFLUSH;
+ +
428 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
429}
+
430
+
431int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
432{
+
433 struct fuse_entry_out arg;
+
434 size_t size = req->se->conn.proto_minor < 9 ?
+
435 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
436
+
437 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
438 negative entry */
+
439 if (!e->ino && req->se->conn.proto_minor < 4)
+
440 return fuse_reply_err(req, ENOENT);
+
441
+
442 memset(&arg, 0, sizeof(arg));
+
443 fill_entry(&arg, e);
+
444 return send_reply_ok(req, &arg, size);
+
445}
+
446
+
447int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
448 const struct fuse_file_info *f)
+
449{
+
450 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
451 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
452 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
453 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
454 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
455
+
456 memset(buf, 0, sizeof(buf));
+
457 fill_entry(earg, e);
+
458 fill_open(oarg, f);
+
459 return send_reply_ok(req, buf,
+
460 entrysize + sizeof(struct fuse_open_out));
+
461}
+
462
+
463int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
464 double attr_timeout)
+
465{
+
466 struct fuse_attr_out arg;
+
467 size_t size = req->se->conn.proto_minor < 9 ?
+
468 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
469
+
470 memset(&arg, 0, sizeof(arg));
+
471 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
472 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
473 convert_stat(attr, &arg.attr);
+
474
+
475 return send_reply_ok(req, &arg, size);
+
476}
+
477
+
478int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
479{
+
480 return send_reply_ok(req, linkname, strlen(linkname));
+
481}
+
482
+
483int fuse_passthrough_open(fuse_req_t req, int fd)
+
484{
+
485 struct fuse_backing_map map = { .fd = fd };
+
486 int ret;
+
487
+
488 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
489 if (ret <= 0) {
+
490 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
491 return 0;
+
492 }
+
493
+
494 return ret;
+
495}
+
496
+
497int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
498{
+
499 int ret;
+
500
+
501 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
502 if (ret < 0)
+
503 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
504
+
505 return ret;
+
506}
+
507
+
508int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
+
509{
+
510 struct fuse_open_out arg;
+
511
+
512 memset(&arg, 0, sizeof(arg));
+
513 fill_open(&arg, f);
+
514 return send_reply_ok(req, &arg, sizeof(arg));
+
515}
+
516
+
517int fuse_reply_write(fuse_req_t req, size_t count)
+
518{
+
519 struct fuse_write_out arg;
+
520
+
521 memset(&arg, 0, sizeof(arg));
+
522 arg.size = count;
+
523
+
524 return send_reply_ok(req, &arg, sizeof(arg));
+
525}
+
526
+
527int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
528{
+
529 return send_reply_ok(req, buf, size);
+
530}
+
531
+
532static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
533 struct fuse_chan *ch,
+
534 struct iovec *iov, int iov_count,
+
535 struct fuse_bufvec *buf,
+
536 size_t len)
+
537{
+
538 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
539 void *mbuf;
+
540 int res;
+
541
+
542 /* Optimize common case */
+
543 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
544 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
545 /* FIXME: also avoid memory copy if there are multiple buffers
+
546 but none of them contain an fd */
+
547
+
548 iov[iov_count].iov_base = buf->buf[0].mem;
+
549 iov[iov_count].iov_len = len;
+
550 iov_count++;
+
551 return fuse_send_msg(se, ch, iov, iov_count);
+
552 }
+
553
+
554 res = posix_memalign(&mbuf, pagesize, len);
+
555 if (res != 0)
+
556 return res;
+
557
+
558 mem_buf.buf[0].mem = mbuf;
+
559 res = fuse_buf_copy(&mem_buf, buf, 0);
+
560 if (res < 0) {
+
561 free(mbuf);
+
562 return -res;
+
563 }
+
564 len = res;
+
565
+
566 iov[iov_count].iov_base = mbuf;
+
567 iov[iov_count].iov_len = len;
+
568 iov_count++;
+
569 res = fuse_send_msg(se, ch, iov, iov_count);
+
570 free(mbuf);
+
571
+
572 return res;
+
573}
+
574
+
575struct fuse_ll_pipe {
+
576 size_t size;
+
577 int can_grow;
+
578 int pipe[2];
+
579};
+
580
+
581static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
582{
+
583 close(llp->pipe[0]);
+
584 close(llp->pipe[1]);
+
585 free(llp);
+
586}
+
587
+
588#ifdef HAVE_SPLICE
+
589#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
590static int fuse_pipe(int fds[2])
+
591{
+
592 int rv = pipe(fds);
+
593
+
594 if (rv == -1)
+
595 return rv;
+
596
+
597 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
598 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
599 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
600 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
601 close(fds[0]);
+
602 close(fds[1]);
+
603 rv = -1;
+
604 }
+
605 return rv;
+
606}
+
607#else
+
608static int fuse_pipe(int fds[2])
+
609{
+
610 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
611}
+
612#endif
+
613
+
614static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
615{
+
616 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
617 if (llp == NULL) {
+
618 int res;
+
619
+
620 llp = malloc(sizeof(struct fuse_ll_pipe));
+
621 if (llp == NULL)
+
622 return NULL;
+
623
+
624 res = fuse_pipe(llp->pipe);
+
625 if (res == -1) {
+
626 free(llp);
+
627 return NULL;
+
628 }
+
629
+
630 /*
+
631 *the default size is 16 pages on linux
+
632 */
+
633 llp->size = pagesize * 16;
+
634 llp->can_grow = 1;
+
635
+
636 pthread_setspecific(se->pipe_key, llp);
+
637 }
+
638
+
639 return llp;
+
640}
+
641#endif
+
642
+
643static void fuse_ll_clear_pipe(struct fuse_session *se)
+
644{
+
645 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
646 if (llp) {
+
647 pthread_setspecific(se->pipe_key, NULL);
+
648 fuse_ll_pipe_free(llp);
+
649 }
+
650}
+
651
+
652#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
653static int read_back(int fd, char *buf, size_t len)
+
654{
+
655 int res;
+
656
+
657 res = read(fd, buf, len);
+
658 if (res == -1) {
+
659 fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+
660 return -EIO;
+
661 }
+
662 if (res != len) {
+
663 fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+
664 return -EIO;
+
665 }
+
666 return 0;
+
667}
+
668
+
669static int grow_pipe_to_max(int pipefd)
+
670{
+
671 int res;
+
672 long max;
+
673 long maxfd;
+
674 char buf[32];
+
675
+
676 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
677 if (maxfd < 0)
+
678 return -errno;
+
679
+
680 res = read(maxfd, buf, sizeof(buf) - 1);
+
681 if (res < 0) {
+
682 int saved_errno;
+
683
+
684 saved_errno = errno;
+
685 close(maxfd);
+
686 return -saved_errno;
+
687 }
+
688 close(maxfd);
+
689 buf[res] = '\0';
+
690
+
691 res = libfuse_strtol(buf, &max);
+
692 if (res)
+
693 return res;
+
694 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
695 if (res < 0)
+
696 return -errno;
+
697 return max;
+
698}
+
699
+
700static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
701 struct iovec *iov, int iov_count,
+
702 struct fuse_bufvec *buf, unsigned int flags)
+
703{
+
704 int res;
+
705 size_t len = fuse_buf_size(buf);
+
706 struct fuse_out_header *out = iov[0].iov_base;
+
707 struct fuse_ll_pipe *llp;
+
708 int splice_flags;
+
709 size_t pipesize;
+
710 size_t total_buf_size;
+
711 size_t idx;
+
712 size_t headerlen;
+
713 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
714
+
715 if (se->broken_splice_nonblock)
+
716 goto fallback;
+
717
+
718 if (flags & FUSE_BUF_NO_SPLICE)
+
719 goto fallback;
+
720
+
721 total_buf_size = 0;
+
722 for (idx = buf->idx; idx < buf->count; idx++) {
+
723 total_buf_size += buf->buf[idx].size;
+
724 if (idx == buf->idx)
+
725 total_buf_size -= buf->off;
+
726 }
+
727 if (total_buf_size < 2 * pagesize)
+
728 goto fallback;
+
729
+
730 if (se->conn.proto_minor < 14 ||
+
731 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
732 goto fallback;
+
733
+
734 llp = fuse_ll_get_pipe(se);
+
735 if (llp == NULL)
+
736 goto fallback;
+
737
+
738
+
739 headerlen = iov_length(iov, iov_count);
+
740
+
741 out->len = headerlen + len;
+
742
+
743 /*
+
744 * Heuristic for the required pipe size, does not work if the
+
745 * source contains less than page size fragments
+
746 */
+
747 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
748
+
749 if (llp->size < pipesize) {
+
750 if (llp->can_grow) {
+
751 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
752 if (res == -1) {
+
753 res = grow_pipe_to_max(llp->pipe[0]);
+
754 if (res > 0)
+
755 llp->size = res;
+
756 llp->can_grow = 0;
+
757 goto fallback;
+
758 }
+
759 llp->size = res;
+
760 }
+
761 if (llp->size < pipesize)
+
762 goto fallback;
+
763 }
+
764
+
765
+
766 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
767 if (res == -1)
+
768 goto fallback;
+
769
+
770 if (res != headerlen) {
+
771 res = -EIO;
+
772 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
773 headerlen);
+
774 goto clear_pipe;
+
775 }
+
776
+
777 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
778 pipe_buf.buf[0].fd = llp->pipe[1];
+
779
+
780 res = fuse_buf_copy(&pipe_buf, buf,
+ +
782 if (res < 0) {
+
783 if (res == -EAGAIN || res == -EINVAL) {
+
784 /*
+
785 * Should only get EAGAIN on kernels with
+
786 * broken SPLICE_F_NONBLOCK support (<=
+
787 * 2.6.35) where this error or a short read is
+
788 * returned even if the pipe itself is not
+
789 * full
+
790 *
+
791 * EINVAL might mean that splice can't handle
+
792 * this combination of input and output.
+
793 */
+
794 if (res == -EAGAIN)
+
795 se->broken_splice_nonblock = 1;
+
796
+
797 pthread_setspecific(se->pipe_key, NULL);
+
798 fuse_ll_pipe_free(llp);
+
799 goto fallback;
+
800 }
+
801 res = -res;
+
802 goto clear_pipe;
+
803 }
+
804
+
805 if (res != 0 && res < len) {
+
806 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
807 void *mbuf;
+
808 size_t now_len = res;
+
809 /*
+
810 * For regular files a short count is either
+
811 * 1) due to EOF, or
+
812 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
813 *
+
814 * For other inputs it's possible that we overflowed
+
815 * the pipe because of small buffer fragments.
+
816 */
+
817
+
818 res = posix_memalign(&mbuf, pagesize, len);
+
819 if (res != 0)
+
820 goto clear_pipe;
+
821
+
822 mem_buf.buf[0].mem = mbuf;
+
823 mem_buf.off = now_len;
+
824 res = fuse_buf_copy(&mem_buf, buf, 0);
+
825 if (res > 0) {
+
826 char *tmpbuf;
+
827 size_t extra_len = res;
+
828 /*
+
829 * Trickiest case: got more data. Need to get
+
830 * back the data from the pipe and then fall
+
831 * back to regular write.
+
832 */
+
833 tmpbuf = malloc(headerlen);
+
834 if (tmpbuf == NULL) {
+
835 free(mbuf);
+
836 res = ENOMEM;
+
837 goto clear_pipe;
+
838 }
+
839 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
840 free(tmpbuf);
+
841 if (res != 0) {
+
842 free(mbuf);
+
843 goto clear_pipe;
+
844 }
+
845 res = read_back(llp->pipe[0], mbuf, now_len);
+
846 if (res != 0) {
+
847 free(mbuf);
+
848 goto clear_pipe;
+
849 }
+
850 len = now_len + extra_len;
+
851 iov[iov_count].iov_base = mbuf;
+
852 iov[iov_count].iov_len = len;
+
853 iov_count++;
+
854 res = fuse_send_msg(se, ch, iov, iov_count);
+
855 free(mbuf);
+
856 return res;
+
857 }
+
858 free(mbuf);
+
859 res = now_len;
+
860 }
+
861 len = res;
+
862 out->len = headerlen + len;
+
863
+
864 if (se->debug) {
+
865 fuse_log(FUSE_LOG_DEBUG,
+
866 " unique: %llu, success, outsize: %i (splice)\n",
+
867 (unsigned long long) out->unique, out->len);
+
868 }
+
869
+
870 splice_flags = 0;
+
871 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
872 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
873 splice_flags |= SPLICE_F_MOVE;
+
874
+
875 if (se->io != NULL && se->io->splice_send != NULL) {
+
876 res = se->io->splice_send(llp->pipe[0], NULL,
+
877 ch ? ch->fd : se->fd, NULL, out->len,
+
878 splice_flags, se->userdata);
+
879 } else {
+
880 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
881 out->len, splice_flags);
+
882 }
+
883 if (res == -1) {
+
884 res = -errno;
+
885 perror("fuse: splice from pipe");
+
886 goto clear_pipe;
+
887 }
+
888 if (res != out->len) {
+
889 res = -EIO;
+
890 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
891 res, out->len);
+
892 goto clear_pipe;
+
893 }
+
894 return 0;
+
895
+
896clear_pipe:
+
897 fuse_ll_clear_pipe(se);
+
898 return res;
+
899
+
900fallback:
+
901 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
902}
+
903#else
+
904static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
905 struct iovec *iov, int iov_count,
+
906 struct fuse_bufvec *buf, unsigned int flags)
+
907{
+
908 size_t len = fuse_buf_size(buf);
+
909 (void) flags;
+
910
+
911 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
912}
+
913#endif
+
914
+
915int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+
916 enum fuse_buf_copy_flags flags)
+
917{
+
918 struct iovec iov[2];
+
919 struct fuse_out_header out;
+
920 int res;
+
921
+
922 iov[0].iov_base = &out;
+
923 iov[0].iov_len = sizeof(struct fuse_out_header);
+
924
+
925 out.unique = req->unique;
+
926 out.error = 0;
+
927
+
928 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags);
+
929 if (res <= 0) {
+
930 fuse_free_req(req);
+
931 return res;
+
932 } else {
+
933 return fuse_reply_err(req, res);
+
934 }
+
935}
+
936
+
937int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
938{
+
939 struct fuse_statfs_out arg;
+
940 size_t size = req->se->conn.proto_minor < 4 ?
+
941 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
942
+
943 memset(&arg, 0, sizeof(arg));
+
944 convert_statfs(stbuf, &arg.st);
+
945
+
946 return send_reply_ok(req, &arg, size);
+
947}
+
948
+
949int fuse_reply_xattr(fuse_req_t req, size_t count)
+
950{
+
951 struct fuse_getxattr_out arg;
+
952
+
953 memset(&arg, 0, sizeof(arg));
+
954 arg.size = count;
+
955
+
956 return send_reply_ok(req, &arg, sizeof(arg));
+
957}
+
958
+
959int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
960{
+
961 struct fuse_lk_out arg;
+
962
+
963 memset(&arg, 0, sizeof(arg));
+
964 arg.lk.type = lock->l_type;
+
965 if (lock->l_type != F_UNLCK) {
+
966 arg.lk.start = lock->l_start;
+
967 if (lock->l_len == 0)
+
968 arg.lk.end = OFFSET_MAX;
+
969 else
+
970 arg.lk.end = lock->l_start + lock->l_len - 1;
+
971 }
+
972 arg.lk.pid = lock->l_pid;
+
973 return send_reply_ok(req, &arg, sizeof(arg));
+
974}
+
975
+
976int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
977{
+
978 struct fuse_bmap_out arg;
+
979
+
980 memset(&arg, 0, sizeof(arg));
+
981 arg.block = idx;
+
982
+
983 return send_reply_ok(req, &arg, sizeof(arg));
+
984}
+
985
+
986static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
987 size_t count)
+
988{
+
989 struct fuse_ioctl_iovec *fiov;
+
990 size_t i;
+
991
+
992 fiov = malloc(sizeof(fiov[0]) * count);
+
993 if (!fiov)
+
994 return NULL;
+
995
+
996 for (i = 0; i < count; i++) {
+
997 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
998 fiov[i].len = iov[i].iov_len;
+
999 }
+
1000
+
1001 return fiov;
+
1002}
+
1003
+ +
1005 const struct iovec *in_iov, size_t in_count,
+
1006 const struct iovec *out_iov, size_t out_count)
+
1007{
+
1008 struct fuse_ioctl_out arg;
+
1009 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1010 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1011 struct iovec iov[4];
+
1012 size_t count = 1;
+
1013 int res;
+
1014
+
1015 memset(&arg, 0, sizeof(arg));
+
1016 arg.flags |= FUSE_IOCTL_RETRY;
+
1017 arg.in_iovs = in_count;
+
1018 arg.out_iovs = out_count;
+
1019 iov[count].iov_base = &arg;
+
1020 iov[count].iov_len = sizeof(arg);
+
1021 count++;
+
1022
+
1023 if (req->se->conn.proto_minor < 16) {
+
1024 if (in_count) {
+
1025 iov[count].iov_base = (void *)in_iov;
+
1026 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1027 count++;
+
1028 }
+
1029
+
1030 if (out_count) {
+
1031 iov[count].iov_base = (void *)out_iov;
+
1032 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1033 count++;
+
1034 }
+
1035 } else {
+
1036 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1037 if (sizeof(void *) == 4 && req->ioctl_64bit) {
+
1038 res = fuse_reply_err(req, EINVAL);
+
1039 goto out;
+
1040 }
+
1041
+
1042 if (in_count) {
+
1043 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1044 if (!in_fiov)
+
1045 goto enomem;
+
1046
+
1047 iov[count].iov_base = (void *)in_fiov;
+
1048 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1049 count++;
+
1050 }
+
1051 if (out_count) {
+
1052 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1053 if (!out_fiov)
+
1054 goto enomem;
+
1055
+
1056 iov[count].iov_base = (void *)out_fiov;
+
1057 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1058 count++;
+
1059 }
+
1060 }
+
1061
+
1062 res = send_reply_iov(req, 0, iov, count);
+
1063out:
+
1064 free(in_fiov);
+
1065 free(out_fiov);
+
1066
+
1067 return res;
+
1068
+
1069enomem:
+
1070 res = fuse_reply_err(req, ENOMEM);
+
1071 goto out;
+
1072}
+
1073
+
1074int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1075{
+
1076 struct fuse_ioctl_out arg;
+
1077 struct iovec iov[3];
+
1078 size_t count = 1;
+
1079
+
1080 memset(&arg, 0, sizeof(arg));
+
1081 arg.result = result;
+
1082 iov[count].iov_base = &arg;
+
1083 iov[count].iov_len = sizeof(arg);
+
1084 count++;
+
1085
+
1086 if (size) {
+
1087 iov[count].iov_base = (char *) buf;
+
1088 iov[count].iov_len = size;
+
1089 count++;
+
1090 }
+
1091
+
1092 return send_reply_iov(req, 0, iov, count);
+
1093}
+
1094
+
1095int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1096 int count)
+
1097{
+
1098 struct iovec *padded_iov;
+
1099 struct fuse_ioctl_out arg;
+
1100 int res;
+
1101
+
1102 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1103 if (padded_iov == NULL)
+
1104 return fuse_reply_err(req, ENOMEM);
+
1105
+
1106 memset(&arg, 0, sizeof(arg));
+
1107 arg.result = result;
+
1108 padded_iov[1].iov_base = &arg;
+
1109 padded_iov[1].iov_len = sizeof(arg);
+
1110
+
1111 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1112
+
1113 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1114 free(padded_iov);
+
1115
+
1116 return res;
+
1117}
+
1118
+
1119int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1120{
+
1121 struct fuse_poll_out arg;
+
1122
+
1123 memset(&arg, 0, sizeof(arg));
+
1124 arg.revents = revents;
+
1125
+
1126 return send_reply_ok(req, &arg, sizeof(arg));
+
1127}
+
1128
+
1129int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1130{
+
1131 struct fuse_lseek_out arg;
+
1132
+
1133 memset(&arg, 0, sizeof(arg));
+
1134 arg.offset = off;
+
1135
+
1136 return send_reply_ok(req, &arg, sizeof(arg));
+
1137}
+
1138
+
1139static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1140{
+
1141 char *name = (char *) inarg;
+
1142
+
1143 if (req->se->op.lookup)
+
1144 req->se->op.lookup(req, nodeid, name);
+
1145 else
+
1146 fuse_reply_err(req, ENOSYS);
+
1147}
+
1148
+
1149static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1150{
+
1151 struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
1152
+
1153 if (req->se->op.forget)
+
1154 req->se->op.forget(req, nodeid, arg->nlookup);
+
1155 else
+
1156 fuse_reply_none(req);
+
1157}
+
1158
+
1159static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+
1160 const void *inarg)
+
1161{
+
1162 struct fuse_batch_forget_in *arg = (void *) inarg;
+
1163 struct fuse_forget_one *param = (void *) PARAM(arg);
+
1164 unsigned int i;
+
1165
+
1166 (void) nodeid;
+
1167
+
1168 if (req->se->op.forget_multi) {
+
1169 req->se->op.forget_multi(req, arg->count,
+
1170 (struct fuse_forget_data *) param);
+
1171 } else if (req->se->op.forget) {
+
1172 for (i = 0; i < arg->count; i++) {
+
1173 struct fuse_forget_one *forget = &param[i];
+
1174 struct fuse_req *dummy_req;
+
1175
+
1176 dummy_req = fuse_ll_alloc_req(req->se);
+
1177 if (dummy_req == NULL)
+
1178 break;
+
1179
+
1180 dummy_req->unique = req->unique;
+
1181 dummy_req->ctx = req->ctx;
+
1182 dummy_req->ch = NULL;
+
1183
+
1184 req->se->op.forget(dummy_req, forget->nodeid,
+
1185 forget->nlookup);
+
1186 }
+
1187 fuse_reply_none(req);
+
1188 } else {
+
1189 fuse_reply_none(req);
+
1190 }
+
1191}
+
1192
+
1193static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1194{
+
1195 struct fuse_file_info *fip = NULL;
+
1196 struct fuse_file_info fi;
+
1197
+
1198 if (req->se->conn.proto_minor >= 9) {
+
1199 struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
1200
+
1201 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1202 memset(&fi, 0, sizeof(fi));
+
1203 fi.fh = arg->fh;
+
1204 fip = &fi;
+
1205 }
+
1206 }
+
1207
+
1208 if (req->se->op.getattr)
+
1209 req->se->op.getattr(req, nodeid, fip);
+
1210 else
+
1211 fuse_reply_err(req, ENOSYS);
+
1212}
+
1213
+
1214static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1215{
+
1216 struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
1217
+
1218 if (req->se->op.setattr) {
+
1219 struct fuse_file_info *fi = NULL;
+
1220 struct fuse_file_info fi_store;
+
1221 struct stat stbuf;
+
1222 memset(&stbuf, 0, sizeof(stbuf));
+
1223 convert_attr(arg, &stbuf);
+
1224 if (arg->valid & FATTR_FH) {
+
1225 arg->valid &= ~FATTR_FH;
+
1226 memset(&fi_store, 0, sizeof(fi_store));
+
1227 fi = &fi_store;
+
1228 fi->fh = arg->fh;
+
1229 }
+
1230 arg->valid &=
+
1231 FUSE_SET_ATTR_MODE |
+
1232 FUSE_SET_ATTR_UID |
+
1233 FUSE_SET_ATTR_GID |
+
1234 FUSE_SET_ATTR_SIZE |
+
1235 FUSE_SET_ATTR_ATIME |
+
1236 FUSE_SET_ATTR_MTIME |
+
1237 FUSE_SET_ATTR_KILL_SUID |
+
1238 FUSE_SET_ATTR_KILL_SGID |
+
1239 FUSE_SET_ATTR_ATIME_NOW |
+
1240 FUSE_SET_ATTR_MTIME_NOW |
+
1241 FUSE_SET_ATTR_CTIME;
+
1242
+
1243 req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+
1244 } else
+
1245 fuse_reply_err(req, ENOSYS);
+
1246}
+
1247
+
1248static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1249{
+
1250 struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
1251
+
1252 if (req->se->op.access)
+
1253 req->se->op.access(req, nodeid, arg->mask);
+
1254 else
+
1255 fuse_reply_err(req, ENOSYS);
+
1256}
+
1257
+
1258static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1259{
+
1260 (void) inarg;
+
1261
+
1262 if (req->se->op.readlink)
+
1263 req->se->op.readlink(req, nodeid);
+
1264 else
+
1265 fuse_reply_err(req, ENOSYS);
+
1266}
+
1267
+
1268static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1269{
+
1270 struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+
1271 char *name = PARAM(arg);
+
1272
+
1273 if (req->se->conn.proto_minor >= 12)
+
1274 req->ctx.umask = arg->umask;
+
1275 else
+
1276 name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1277
+
1278 if (req->se->op.mknod)
+
1279 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1280 else
+
1281 fuse_reply_err(req, ENOSYS);
+
1282}
+
1283
+
1284static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1285{
+
1286 struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
1287
+
1288 if (req->se->conn.proto_minor >= 12)
+
1289 req->ctx.umask = arg->umask;
+
1290
+
1291 if (req->se->op.mkdir)
+
1292 req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+
1293 else
+
1294 fuse_reply_err(req, ENOSYS);
+
1295}
+
1296
+
1297static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1298{
+
1299 char *name = (char *) inarg;
+
1300
+
1301 if (req->se->op.unlink)
+
1302 req->se->op.unlink(req, nodeid, name);
+
1303 else
+
1304 fuse_reply_err(req, ENOSYS);
+
1305}
+
1306
+
1307static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1308{
+
1309 char *name = (char *) inarg;
+
1310
+
1311 if (req->se->op.rmdir)
+
1312 req->se->op.rmdir(req, nodeid, name);
+
1313 else
+
1314 fuse_reply_err(req, ENOSYS);
+
1315}
+
1316
+
1317static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1318{
+
1319 char *name = (char *) inarg;
+
1320 char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
1321
+
1322 if (req->se->op.symlink)
+
1323 req->se->op.symlink(req, linkname, nodeid, name);
+
1324 else
+
1325 fuse_reply_err(req, ENOSYS);
+
1326}
+
1327
+
1328static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1329{
+
1330 struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+
1331 char *oldname = PARAM(arg);
+
1332 char *newname = oldname + strlen(oldname) + 1;
+
1333
+
1334 if (req->se->op.rename)
+
1335 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1336 0);
+
1337 else
+
1338 fuse_reply_err(req, ENOSYS);
+
1339}
+
1340
+
1341static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1342{
+
1343 struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg;
+
1344 char *oldname = PARAM(arg);
+
1345 char *newname = oldname + strlen(oldname) + 1;
+
1346
+
1347 if (req->se->op.rename)
+
1348 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1349 arg->flags);
+
1350 else
+
1351 fuse_reply_err(req, ENOSYS);
+
1352}
+
1353
+
1354static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1355{
+
1356 struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
1357
+
1358 if (req->se->op.link)
+
1359 req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+
1360 else
+
1361 fuse_reply_err(req, ENOSYS);
+
1362}
+
1363
+
1364static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1365{
+
1366 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1367
+
1368 if (req->se->op.tmpfile) {
+
1369 struct fuse_file_info fi;
+
1370
+
1371 memset(&fi, 0, sizeof(fi));
+
1372 fi.flags = arg->flags;
+
1373
+
1374 if (req->se->conn.proto_minor >= 12)
+
1375 req->ctx.umask = arg->umask;
+
1376
+
1377 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1378 } else
+
1379 fuse_reply_err(req, ENOSYS);
+
1380}
+
1381
+
1382static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1383{
+
1384 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1385
+
1386 if (req->se->op.create) {
+
1387 struct fuse_file_info fi;
+
1388 char *name = PARAM(arg);
+
1389
+
1390 memset(&fi, 0, sizeof(fi));
+
1391 fi.flags = arg->flags;
+
1392
+
1393 if (req->se->conn.proto_minor >= 12)
+
1394 req->ctx.umask = arg->umask;
+
1395 else
+
1396 name = (char *) inarg + sizeof(struct fuse_open_in);
+
1397
+
1398 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1399 } else
+
1400 fuse_reply_err(req, ENOSYS);
+
1401}
+
1402
+
1403static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1404{
+
1405 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1406 struct fuse_file_info fi;
+
1407
+
1408 memset(&fi, 0, sizeof(fi));
+
1409 fi.flags = arg->flags;
+
1410
+
1411 if (req->se->op.open)
+
1412 req->se->op.open(req, nodeid, &fi);
+
1413 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1414 fuse_reply_err(req, ENOSYS);
+
1415 else
+
1416 fuse_reply_open(req, &fi);
+
1417}
+
1418
+
1419static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1420{
+
1421 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1422
+
1423 if (req->se->op.read) {
+
1424 struct fuse_file_info fi;
+
1425
+
1426 memset(&fi, 0, sizeof(fi));
+
1427 fi.fh = arg->fh;
+
1428 if (req->se->conn.proto_minor >= 9) {
+
1429 fi.lock_owner = arg->lock_owner;
+
1430 fi.flags = arg->flags;
+
1431 }
+
1432 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1433 } else
+
1434 fuse_reply_err(req, ENOSYS);
+
1435}
+
1436
+
1437static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1438{
+
1439 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1440 struct fuse_file_info fi;
+
1441 char *param;
+
1442
+
1443 memset(&fi, 0, sizeof(fi));
+
1444 fi.fh = arg->fh;
+
1445 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1446
+
1447 if (req->se->conn.proto_minor < 9) {
+
1448 param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1449 } else {
+
1450 fi.lock_owner = arg->lock_owner;
+
1451 fi.flags = arg->flags;
+
1452 param = PARAM(arg);
+
1453 }
+
1454
+
1455 if (req->se->op.write)
+
1456 req->se->op.write(req, nodeid, param, arg->size,
+
1457 arg->offset, &fi);
+
1458 else
+
1459 fuse_reply_err(req, ENOSYS);
+
1460}
+
1461
+
1462static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+
1463 const struct fuse_buf *ibuf)
+
1464{
+
1465 struct fuse_session *se = req->se;
+
1466 struct fuse_bufvec bufv = {
+
1467 .buf[0] = *ibuf,
+
1468 .count = 1,
+
1469 };
+
1470 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1471 struct fuse_file_info fi;
+
1472
+
1473 memset(&fi, 0, sizeof(fi));
+
1474 fi.fh = arg->fh;
+
1475 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1476
+
1477 if (se->conn.proto_minor < 9) {
+
1478 bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1479 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1480 FUSE_COMPAT_WRITE_IN_SIZE;
+
1481 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1482 } else {
+
1483 fi.lock_owner = arg->lock_owner;
+
1484 fi.flags = arg->flags;
+
1485 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1486 bufv.buf[0].mem = PARAM(arg);
+
1487
+
1488 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1489 sizeof(struct fuse_write_in);
+
1490 }
+
1491 if (bufv.buf[0].size < arg->size) {
+
1492 fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n");
+
1493 fuse_reply_err(req, EIO);
+
1494 goto out;
+
1495 }
+
1496 bufv.buf[0].size = arg->size;
+
1497
+
1498 se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
1499
+
1500out:
+
1501 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1502 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1503 fuse_ll_clear_pipe(se);
+
1504}
+
1505
+
1506static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1507{
+
1508 struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+
1509 struct fuse_file_info fi;
+
1510
+
1511 memset(&fi, 0, sizeof(fi));
+
1512 fi.fh = arg->fh;
+
1513 fi.flush = 1;
+
1514 if (req->se->conn.proto_minor >= 7)
+
1515 fi.lock_owner = arg->lock_owner;
+
1516
+
1517 if (req->se->op.flush)
+
1518 req->se->op.flush(req, nodeid, &fi);
+
1519 else
+
1520 fuse_reply_err(req, ENOSYS);
+
1521}
+
1522
+
1523static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1524{
+
1525 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1526 struct fuse_file_info fi;
+
1527
+
1528 memset(&fi, 0, sizeof(fi));
+
1529 fi.flags = arg->flags;
+
1530 fi.fh = arg->fh;
+
1531 if (req->se->conn.proto_minor >= 8) {
+
1532 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1533 fi.lock_owner = arg->lock_owner;
+
1534 }
+
1535 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1536 fi.flock_release = 1;
+
1537 fi.lock_owner = arg->lock_owner;
+
1538 }
+
1539
+
1540 if (req->se->op.release)
+
1541 req->se->op.release(req, nodeid, &fi);
+
1542 else
+
1543 fuse_reply_err(req, 0);
+
1544}
+
1545
+
1546static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1547{
+
1548 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1549 struct fuse_file_info fi;
+
1550 int datasync = arg->fsync_flags & 1;
+
1551
+
1552 memset(&fi, 0, sizeof(fi));
+
1553 fi.fh = arg->fh;
+
1554
+
1555 if (req->se->op.fsync)
+
1556 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1557 else
+
1558 fuse_reply_err(req, ENOSYS);
+
1559}
+
1560
+
1561static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1562{
+
1563 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1564 struct fuse_file_info fi;
+
1565
+
1566 memset(&fi, 0, sizeof(fi));
+
1567 fi.flags = arg->flags;
+
1568
+
1569 if (req->se->op.opendir)
+
1570 req->se->op.opendir(req, nodeid, &fi);
+
1571 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1572 fuse_reply_err(req, ENOSYS);
+
1573 else
+
1574 fuse_reply_open(req, &fi);
+
1575}
+
1576
+
1577static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1578{
+
1579 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1580 struct fuse_file_info fi;
+
1581
+
1582 memset(&fi, 0, sizeof(fi));
+
1583 fi.fh = arg->fh;
+
1584
+
1585 if (req->se->op.readdir)
+
1586 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1587 else
+
1588 fuse_reply_err(req, ENOSYS);
+
1589}
+
1590
+
1591static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1592{
+
1593 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1594 struct fuse_file_info fi;
+
1595
+
1596 memset(&fi, 0, sizeof(fi));
+
1597 fi.fh = arg->fh;
+
1598
+
1599 if (req->se->op.readdirplus)
+
1600 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1601 else
+
1602 fuse_reply_err(req, ENOSYS);
+
1603}
+
1604
+
1605static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1606{
+
1607 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1608 struct fuse_file_info fi;
+
1609
+
1610 memset(&fi, 0, sizeof(fi));
+
1611 fi.flags = arg->flags;
+
1612 fi.fh = arg->fh;
+
1613
+
1614 if (req->se->op.releasedir)
+
1615 req->se->op.releasedir(req, nodeid, &fi);
+
1616 else
+
1617 fuse_reply_err(req, 0);
+
1618}
+
1619
+
1620static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1621{
+
1622 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1623 struct fuse_file_info fi;
+
1624 int datasync = arg->fsync_flags & 1;
+
1625
+
1626 memset(&fi, 0, sizeof(fi));
+
1627 fi.fh = arg->fh;
+
1628
+
1629 if (req->se->op.fsyncdir)
+
1630 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
1631 else
+
1632 fuse_reply_err(req, ENOSYS);
+
1633}
+
1634
+
1635static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1636{
+
1637 (void) nodeid;
+
1638 (void) inarg;
+
1639
+
1640 if (req->se->op.statfs)
+
1641 req->se->op.statfs(req, nodeid);
+
1642 else {
+
1643 struct statvfs buf = {
+
1644 .f_namemax = 255,
+
1645 .f_bsize = 512,
+
1646 };
+
1647 fuse_reply_statfs(req, &buf);
+
1648 }
+
1649}
+
1650
+
1651static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1652{
+
1653 struct fuse_session *se = req->se;
+
1654 unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT);
+
1655 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+
1656 char *name = xattr_ext ? PARAM(arg) :
+
1657 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
1658 char *value = name + strlen(name) + 1;
+
1659
+
1660 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
1661 if (req->se->op.setxattr)
+
1662 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
1663 arg->flags);
+
1664 else
+
1665 fuse_reply_err(req, ENOSYS);
+
1666}
+
1667
+
1668static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1669{
+
1670 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1671
+
1672 if (req->se->op.getxattr)
+
1673 req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+
1674 else
+
1675 fuse_reply_err(req, ENOSYS);
+
1676}
+
1677
+
1678static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1679{
+
1680 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1681
+
1682 if (req->se->op.listxattr)
+
1683 req->se->op.listxattr(req, nodeid, arg->size);
+
1684 else
+
1685 fuse_reply_err(req, ENOSYS);
+
1686}
+
1687
+
1688static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1689{
+
1690 char *name = (char *) inarg;
+
1691
+
1692 if (req->se->op.removexattr)
+
1693 req->se->op.removexattr(req, nodeid, name);
+
1694 else
+
1695 fuse_reply_err(req, ENOSYS);
+
1696}
+
1697
+
1698static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+
1699 struct flock *flock)
+
1700{
+
1701 memset(flock, 0, sizeof(struct flock));
+
1702 flock->l_type = fl->type;
+
1703 flock->l_whence = SEEK_SET;
+
1704 flock->l_start = fl->start;
+
1705 if (fl->end == OFFSET_MAX)
+
1706 flock->l_len = 0;
+
1707 else
+
1708 flock->l_len = fl->end - fl->start + 1;
+
1709 flock->l_pid = fl->pid;
+
1710}
+
1711
+
1712static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1713{
+
1714 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1715 struct fuse_file_info fi;
+
1716 struct flock flock;
+
1717
+
1718 memset(&fi, 0, sizeof(fi));
+
1719 fi.fh = arg->fh;
+
1720 fi.lock_owner = arg->owner;
+
1721
+
1722 convert_fuse_file_lock(&arg->lk, &flock);
+
1723 if (req->se->op.getlk)
+
1724 req->se->op.getlk(req, nodeid, &fi, &flock);
+
1725 else
+
1726 fuse_reply_err(req, ENOSYS);
+
1727}
+
1728
+
1729static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+
1730 const void *inarg, int sleep)
+
1731{
+
1732 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1733 struct fuse_file_info fi;
+
1734 struct flock flock;
+
1735
+
1736 memset(&fi, 0, sizeof(fi));
+
1737 fi.fh = arg->fh;
+
1738 fi.lock_owner = arg->owner;
+
1739
+
1740 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
1741 int op = 0;
+
1742
+
1743 switch (arg->lk.type) {
+
1744 case F_RDLCK:
+
1745 op = LOCK_SH;
+
1746 break;
+
1747 case F_WRLCK:
+
1748 op = LOCK_EX;
+
1749 break;
+
1750 case F_UNLCK:
+
1751 op = LOCK_UN;
+
1752 break;
+
1753 }
+
1754 if (!sleep)
+
1755 op |= LOCK_NB;
+
1756
+
1757 if (req->se->op.flock)
+
1758 req->se->op.flock(req, nodeid, &fi, op);
+
1759 else
+
1760 fuse_reply_err(req, ENOSYS);
+
1761 } else {
+
1762 convert_fuse_file_lock(&arg->lk, &flock);
+
1763 if (req->se->op.setlk)
+
1764 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
1765 else
+
1766 fuse_reply_err(req, ENOSYS);
+
1767 }
+
1768}
+
1769
+
1770static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1771{
+
1772 do_setlk_common(req, nodeid, inarg, 0);
+
1773}
+
1774
+
1775static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1776{
+
1777 do_setlk_common(req, nodeid, inarg, 1);
+
1778}
+
1779
+
1780static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
1781{
+
1782 struct fuse_req *curr;
+
1783
+
1784 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
1785 if (curr->unique == req->u.i.unique) {
+ +
1787 void *data;
+
1788
+
1789 curr->ref_cnt++;
+
1790 pthread_mutex_unlock(&se->lock);
+
1791
+
1792 /* Ugh, ugly locking */
+
1793 pthread_mutex_lock(&curr->lock);
+
1794 pthread_mutex_lock(&se->lock);
+
1795 curr->interrupted = 1;
+
1796 func = curr->u.ni.func;
+
1797 data = curr->u.ni.data;
+
1798 pthread_mutex_unlock(&se->lock);
+
1799 if (func)
+
1800 func(curr, data);
+
1801 pthread_mutex_unlock(&curr->lock);
+
1802
+
1803 pthread_mutex_lock(&se->lock);
+
1804 curr->ref_cnt--;
+
1805 if (!curr->ref_cnt) {
+
1806 destroy_req(curr);
+
1807 }
+
1808
+
1809 return 1;
+
1810 }
+
1811 }
+
1812 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1813 curr = curr->next) {
+
1814 if (curr->u.i.unique == req->u.i.unique)
+
1815 return 1;
+
1816 }
+
1817 return 0;
+
1818}
+
1819
+
1820static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1821{
+
1822 struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+
1823 struct fuse_session *se = req->se;
+
1824
+
1825 (void) nodeid;
+
1826 if (se->debug)
+
1827 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
1828 (unsigned long long) arg->unique);
+
1829
+
1830 req->u.i.unique = arg->unique;
+
1831
+
1832 pthread_mutex_lock(&se->lock);
+
1833 if (find_interrupted(se, req)) {
+
1834 fuse_chan_put(req->ch);
+
1835 req->ch = NULL;
+
1836 destroy_req(req);
+
1837 } else
+
1838 list_add_req(req, &se->interrupts);
+
1839 pthread_mutex_unlock(&se->lock);
+
1840}
+
1841
+
1842static struct fuse_req *check_interrupt(struct fuse_session *se,
+
1843 struct fuse_req *req)
+
1844{
+
1845 struct fuse_req *curr;
+
1846
+
1847 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1848 curr = curr->next) {
+
1849 if (curr->u.i.unique == req->unique) {
+
1850 req->interrupted = 1;
+
1851 list_del_req(curr);
+
1852 fuse_chan_put(curr->ch);
+
1853 curr->ch = NULL;
+
1854 destroy_req(curr);
+
1855 return NULL;
+
1856 }
+
1857 }
+
1858 curr = se->interrupts.next;
+
1859 if (curr != &se->interrupts) {
+
1860 list_del_req(curr);
+
1861 list_init_req(curr);
+
1862 return curr;
+
1863 } else
+
1864 return NULL;
+
1865}
+
1866
+
1867static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1868{
+
1869 struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
1870
+
1871 if (req->se->op.bmap)
+
1872 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
1873 else
+
1874 fuse_reply_err(req, ENOSYS);
+
1875}
+
1876
+
1877static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1878{
+
1879 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+
1880 unsigned int flags = arg->flags;
+
1881 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
1882 struct fuse_file_info fi;
+
1883
+
1884 if (flags & FUSE_IOCTL_DIR &&
+
1885 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
1886 fuse_reply_err(req, ENOTTY);
+
1887 return;
+
1888 }
+
1889
+
1890 memset(&fi, 0, sizeof(fi));
+
1891 fi.fh = arg->fh;
+
1892
+
1893 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
1894 !(flags & FUSE_IOCTL_32BIT)) {
+
1895 req->ioctl_64bit = 1;
+
1896 }
+
1897
+
1898 if (req->se->op.ioctl)
+
1899 req->se->op.ioctl(req, nodeid, arg->cmd,
+
1900 (void *)(uintptr_t)arg->arg, &fi, flags,
+
1901 in_buf, arg->in_size, arg->out_size);
+
1902 else
+
1903 fuse_reply_err(req, ENOSYS);
+
1904}
+
1905
+
1906void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
1907{
+
1908 free(ph);
+
1909}
+
1910
+
1911static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1912{
+
1913 struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+
1914 struct fuse_file_info fi;
+
1915
+
1916 memset(&fi, 0, sizeof(fi));
+
1917 fi.fh = arg->fh;
+
1918 fi.poll_events = arg->events;
+
1919
+
1920 if (req->se->op.poll) {
+
1921 struct fuse_pollhandle *ph = NULL;
+
1922
+
1923 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
1924 ph = malloc(sizeof(struct fuse_pollhandle));
+
1925 if (ph == NULL) {
+
1926 fuse_reply_err(req, ENOMEM);
+
1927 return;
+
1928 }
+
1929 ph->kh = arg->kh;
+
1930 ph->se = req->se;
+
1931 }
+
1932
+
1933 req->se->op.poll(req, nodeid, &fi, ph);
+
1934 } else {
+
1935 fuse_reply_err(req, ENOSYS);
+
1936 }
+
1937}
+
1938
+
1939static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1940{
+
1941 struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+
1942 struct fuse_file_info fi;
+
1943
+
1944 memset(&fi, 0, sizeof(fi));
+
1945 fi.fh = arg->fh;
+
1946
+
1947 if (req->se->op.fallocate)
+
1948 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+
1949 else
+
1950 fuse_reply_err(req, ENOSYS);
+
1951}
+
1952
+
1953static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
+
1954{
+
1955 struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
+
1956 struct fuse_file_info fi_in, fi_out;
+
1957
+
1958 memset(&fi_in, 0, sizeof(fi_in));
+
1959 fi_in.fh = arg->fh_in;
+
1960
+
1961 memset(&fi_out, 0, sizeof(fi_out));
+
1962 fi_out.fh = arg->fh_out;
+
1963
+
1964
+
1965 if (req->se->op.copy_file_range)
+
1966 req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
+
1967 &fi_in, arg->nodeid_out,
+
1968 arg->off_out, &fi_out, arg->len,
+
1969 arg->flags);
+
1970 else
+
1971 fuse_reply_err(req, ENOSYS);
+
1972}
+
1973
+
1974static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1975{
+
1976 struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg;
+
1977 struct fuse_file_info fi;
+
1978
+
1979 memset(&fi, 0, sizeof(fi));
+
1980 fi.fh = arg->fh;
+
1981
+
1982 if (req->se->op.lseek)
+
1983 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
1984 else
+
1985 fuse_reply_err(req, ENOSYS);
+
1986}
+
1987
+
1988static bool want_flags_valid(uint64_t capable, uint64_t want)
+
1989{
+
1990 uint64_t unknown_flags = want & (~capable);
+
1991 if (unknown_flags != 0) {
+
1992 fuse_log(FUSE_LOG_ERR,
+
1993 "fuse: unknown connection 'want' flags: 0x%08lx\n",
+
1994 unknown_flags);
+
1995 return false;
+
1996 }
+
1997 return true;
+
1998}
+
1999
+
2003static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn,
+
2004 uint64_t want_ext_default)
+
2005{
+
2006 /* Convert want to want_ext if necessary */
+
2007 if (conn->want != 0) {
+
2008 if (conn->want_ext != want_ext_default) {
+
2009 fuse_log(FUSE_LOG_ERR,
+
2010 "fuse: both 'want' and 'want_ext' are set\n");
+
2011 return -EINVAL;
+
2012 }
+
2013 conn->want_ext |= conn->want;
+
2014 }
+
2015
+
2016 return 0;
+
2017}
+
2018
+
2019/* Prevent bogus data races (bogus since "init" is called before
+
2020 * multi-threading becomes relevant */
+
2021static __attribute__((no_sanitize("thread")))
+
2022void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2023{
+
2024 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
2025 struct fuse_init_out outarg;
+
2026 struct fuse_session *se = req->se;
+
2027 size_t bufsize = se->bufsize;
+
2028 size_t outargsize = sizeof(outarg);
+
2029 uint64_t inargflags = 0;
+
2030 uint64_t outargflags = 0;
+
2031 bool buf_reallocable = se->buf_reallocable;
+
2032 (void) nodeid;
+
2033 if (se->debug) {
+
2034 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2035 if (arg->major == 7 && arg->minor >= 6) {
+
2036 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2037 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2038 arg->max_readahead);
+
2039 }
+
2040 }
+
2041 se->conn.proto_major = arg->major;
+
2042 se->conn.proto_minor = arg->minor;
+
2043 se->conn.capable_ext = 0;
+
2044 se->conn.want_ext = 0;
+
2045
+
2046 memset(&outarg, 0, sizeof(outarg));
+
2047 outarg.major = FUSE_KERNEL_VERSION;
+
2048 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2049
+
2050 if (arg->major < 7) {
+
2051 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2052 arg->major, arg->minor);
+
2053 fuse_reply_err(req, EPROTO);
+
2054 return;
+
2055 }
+
2056
+
2057 if (arg->major > 7) {
+
2058 /* Wait for a second INIT request with a 7.X version */
+
2059 send_reply_ok(req, &outarg, sizeof(outarg));
+
2060 return;
+
2061 }
+
2062
+
2063 if (arg->minor >= 6) {
+
2064 if (arg->max_readahead < se->conn.max_readahead)
+
2065 se->conn.max_readahead = arg->max_readahead;
+
2066 inargflags = arg->flags;
+
2067 if (inargflags & FUSE_INIT_EXT)
+
2068 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2069 if (inargflags & FUSE_ASYNC_READ)
+
2070 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2071 if (inargflags & FUSE_POSIX_LOCKS)
+
2072 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2073 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2074 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2075 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2076 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2077 if (inargflags & FUSE_DONT_MASK)
+
2078 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2079 if (inargflags & FUSE_FLOCK_LOCKS)
+
2080 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2081 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2082 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2083 if (inargflags & FUSE_DO_READDIRPLUS)
+
2084 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2085 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2086 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2087 if (inargflags & FUSE_ASYNC_DIO)
+
2088 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2089 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2090 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2091 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2092 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2093 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2094 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2095 if (inargflags & FUSE_POSIX_ACL)
+
2096 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2097 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2098 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2099 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2100 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2101 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2102 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2103 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2104 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2105 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2106 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2107 if (inargflags & FUSE_SETXATTR_EXT)
+
2108 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2109 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2110 size_t max_bufsize =
+
2111 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2112 + FUSE_BUFFER_HEADER_SIZE;
+
2113 if (bufsize > max_bufsize) {
+
2114 bufsize = max_bufsize;
+
2115 }
+
2116 buf_reallocable = false;
+
2117 }
+
2118 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2119 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2120 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2121 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2122 if (inargflags & FUSE_PASSTHROUGH)
+
2123 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2124 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2125 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2126 } else {
+
2127 se->conn.max_readahead = 0;
+
2128 }
+
2129
+
2130 if (se->conn.proto_minor >= 14) {
+
2131#ifdef HAVE_SPLICE
+
2132#ifdef HAVE_VMSPLICE
+
2133 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2134 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2136 }
+
2137#endif
+
2138 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2139 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2140 }
+
2141#endif
+
2142 }
+
2143 if (se->conn.proto_minor >= 18)
+
2144 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2145
+
2146 /* Default settings for modern filesystems.
+
2147 *
+
2148 * Most of these capabilities were disabled by default in
+
2149 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2150 * we can finally enable them by default (as long as they're
+
2151 * supported by the kernel).
+
2152 */
+
2153#define LL_SET_DEFAULT(cond, cap) \
+
2154 if ((cond)) \
+
2155 fuse_set_feature_flag(&se->conn, cap)
+
2156
+
2157 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2158 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2159 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2160 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2161 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2162 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2163 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2165 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2166 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2167 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2169
+
2170 /* This could safely become default, but libfuse needs an API extension
+
2171 * to support it
+
2172 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2173 */
+
2174
+
2175 se->conn.time_gran = 1;
+
2176
+
2177 se->got_init = 1;
+
2178 if (se->op.init) {
+
2179 uint64_t want_ext_default = se->conn.want_ext;
+
2180 int rc;
+
2181
+
2182 // Apply the first 32 bits of capable_ext to capable
+
2183 se->conn.capable =
+
2184 (uint32_t)(se->conn.capable_ext & 0xFFFFFFFF);
+
2185
+
2186 se->op.init(se->userdata, &se->conn);
+
2187
+
2188 /*
+
2189 * se->conn.want is 32-bit value and deprecated in favour of
+
2190 * se->conn.want_ext
+
2191 * Userspace might still use conn.want - we need to convert it
+
2192 */
+
2193 rc = convert_to_conn_want_ext(&se->conn, want_ext_default);
+
2194 if (rc != 0) {
+
2195 fuse_reply_err(req, EPROTO);
+
2196 se->error = -EPROTO;
+ +
2198 return;
+
2199 }
+
2200 }
+
2201
+
2202 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2203 fuse_reply_err(req, EPROTO);
+
2204 se->error = -EPROTO;
+ +
2206 return;
+
2207 }
+
2208
+
2209 unsigned max_read_mo = get_max_read(se->mo);
+
2210 if (se->conn.max_read != max_read_mo) {
+
2211 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2212 "requested different maximum read size (%u vs %u)\n",
+
2213 se->conn.max_read, max_read_mo);
+
2214 fuse_reply_err(req, EPROTO);
+
2215 se->error = -EPROTO;
+ +
2217 return;
+
2218 }
+
2219
+
2220 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2221 fuse_log(FUSE_LOG_ERR,
+
2222 "fuse: warning: buffer size too small: %zu\n",
+
2223 bufsize);
+
2224 bufsize = FUSE_MIN_READ_BUFFER;
+
2225 }
+
2226
+
2227 if (buf_reallocable)
+
2228 bufsize = UINT_MAX;
+
2229 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2230 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2231
+
2232 if (arg->flags & FUSE_MAX_PAGES) {
+
2233 outarg.flags |= FUSE_MAX_PAGES;
+
2234 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2235 }
+
2236 outargflags = outarg.flags;
+
2237 /* Always enable big writes, this is superseded
+
2238 by the max_write option */
+
2239 outargflags |= FUSE_BIG_WRITES;
+
2240
+
2241 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2242 outargflags |= FUSE_ASYNC_READ;
+
2243 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2244 outargflags |= FUSE_POSIX_LOCKS;
+
2245 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2246 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2247 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2248 outargflags |= FUSE_EXPORT_SUPPORT;
+
2249 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2250 outargflags |= FUSE_DONT_MASK;
+
2251 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2252 outargflags |= FUSE_FLOCK_LOCKS;
+
2253 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2254 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2255 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2256 outargflags |= FUSE_DO_READDIRPLUS;
+
2257 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2258 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2259 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2260 outargflags |= FUSE_ASYNC_DIO;
+
2261 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2262 outargflags |= FUSE_WRITEBACK_CACHE;
+
2263 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2264 outargflags |= FUSE_PARALLEL_DIROPS;
+
2265 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2266 outargflags |= FUSE_POSIX_ACL;
+
2267 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2268 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2269 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2270 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2271 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2272 outargflags |= FUSE_CACHE_SYMLINKS;
+
2273 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2274 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2275 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2276 outargflags |= FUSE_SETXATTR_EXT;
+
2277 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2278 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2279 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2280 outargflags |= FUSE_PASSTHROUGH;
+
2281 /*
+
2282 * outarg.max_stack_depth includes the fuse stack layer,
+
2283 * so it is one more than max_backing_stack_depth.
+
2284 */
+
2285 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2286 }
+
2287 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2288 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2289
+
2290 if (inargflags & FUSE_INIT_EXT) {
+
2291 outargflags |= FUSE_INIT_EXT;
+
2292 outarg.flags2 = outargflags >> 32;
+
2293 }
+
2294
+
2295 outarg.flags = outargflags;
+
2296
+
2297 outarg.max_readahead = se->conn.max_readahead;
+
2298 outarg.max_write = se->conn.max_write;
+
2299 if (se->conn.proto_minor >= 13) {
+
2300 if (se->conn.max_background >= (1 << 16))
+
2301 se->conn.max_background = (1 << 16) - 1;
+
2302 if (se->conn.congestion_threshold > se->conn.max_background)
+
2303 se->conn.congestion_threshold = se->conn.max_background;
+
2304 if (!se->conn.congestion_threshold) {
+
2305 se->conn.congestion_threshold =
+
2306 se->conn.max_background * 3 / 4;
+
2307 }
+
2308
+
2309 outarg.max_background = se->conn.max_background;
+
2310 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2311 }
+
2312 if (se->conn.proto_minor >= 23)
+
2313 outarg.time_gran = se->conn.time_gran;
+
2314
+
2315 if (se->debug) {
+
2316 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2317 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2318 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2319 outarg.max_readahead);
+
2320 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2321 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2322 outarg.max_background);
+
2323 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2324 outarg.congestion_threshold);
+
2325 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2326 outarg.time_gran);
+
2327 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2328 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2329 outarg.max_stack_depth);
+
2330 }
+
2331 if (arg->minor < 5)
+
2332 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2333 else if (arg->minor < 23)
+
2334 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2335
+
2336 send_reply_ok(req, &outarg, outargsize);
+
2337}
+
2338
+
2339static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2340{
+
2341 struct fuse_session *se = req->se;
+
2342
+
2343 (void) nodeid;
+
2344 (void) inarg;
+
2345
+
2346 se->got_destroy = 1;
+
2347 se->got_init = 0;
+
2348 if (se->op.destroy)
+
2349 se->op.destroy(se->userdata);
+
2350
+
2351 send_reply_ok(req, NULL, 0);
+
2352}
+
2353
+
2354static void list_del_nreq(struct fuse_notify_req *nreq)
+
2355{
+
2356 struct fuse_notify_req *prev = nreq->prev;
+
2357 struct fuse_notify_req *next = nreq->next;
+
2358 prev->next = next;
+
2359 next->prev = prev;
+
2360}
+
2361
+
2362static void list_add_nreq(struct fuse_notify_req *nreq,
+
2363 struct fuse_notify_req *next)
+
2364{
+
2365 struct fuse_notify_req *prev = next->prev;
+
2366 nreq->next = next;
+
2367 nreq->prev = prev;
+
2368 prev->next = nreq;
+
2369 next->prev = nreq;
+
2370}
+
2371
+
2372static void list_init_nreq(struct fuse_notify_req *nreq)
+
2373{
+
2374 nreq->next = nreq;
+
2375 nreq->prev = nreq;
+
2376}
+
2377
+
2378static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
2379 const void *inarg, const struct fuse_buf *buf)
+
2380{
+
2381 struct fuse_session *se = req->se;
+
2382 struct fuse_notify_req *nreq;
+
2383 struct fuse_notify_req *head;
+
2384
+
2385 pthread_mutex_lock(&se->lock);
+
2386 head = &se->notify_list;
+
2387 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
2388 if (nreq->unique == req->unique) {
+
2389 list_del_nreq(nreq);
+
2390 break;
+
2391 }
+
2392 }
+
2393 pthread_mutex_unlock(&se->lock);
+
2394
+
2395 if (nreq != head)
+
2396 nreq->reply(nreq, req, nodeid, inarg, buf);
+
2397}
+
2398
+
2399static int send_notify_iov(struct fuse_session *se, int notify_code,
+
2400 struct iovec *iov, int count)
+
2401{
+
2402 struct fuse_out_header out;
+
2403
+
2404 if (!se->got_init)
+
2405 return -ENOTCONN;
+
2406
+
2407 out.unique = 0;
+
2408 out.error = notify_code;
+
2409 iov[0].iov_base = &out;
+
2410 iov[0].iov_len = sizeof(struct fuse_out_header);
+
2411
+
2412 return fuse_send_msg(se, NULL, iov, count);
+
2413}
+
2414
+
2415int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
2416{
+
2417 if (ph != NULL) {
+
2418 struct fuse_notify_poll_wakeup_out outarg;
+
2419 struct iovec iov[2];
+
2420
+
2421 outarg.kh = ph->kh;
+
2422
+
2423 iov[1].iov_base = &outarg;
+
2424 iov[1].iov_len = sizeof(outarg);
+
2425
+
2426 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
2427 } else {
+
2428 return 0;
+
2429 }
+
2430}
+
2431
+
2432int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
2433 off_t off, off_t len)
+
2434{
+
2435 struct fuse_notify_inval_inode_out outarg;
+
2436 struct iovec iov[2];
+
2437
+
2438 if (!se)
+
2439 return -EINVAL;
+
2440
+
2441 if (se->conn.proto_minor < 12)
+
2442 return -ENOSYS;
+
2443
+
2444 outarg.ino = ino;
+
2445 outarg.off = off;
+
2446 outarg.len = len;
+
2447
+
2448 iov[1].iov_base = &outarg;
+
2449 iov[1].iov_len = sizeof(outarg);
+
2450
+
2451 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
2452}
+
2453
+
2473static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
2474 const char *name, size_t namelen,
+
2475 enum fuse_notify_entry_flags flags)
+
2476{
+
2477 struct fuse_notify_inval_entry_out outarg;
+
2478 struct iovec iov[3];
+
2479
+
2480 if (!se)
+
2481 return -EINVAL;
+
2482
+
2483 if (se->conn.proto_minor < 12)
+
2484 return -ENOSYS;
+
2485
+
2486 outarg.parent = parent;
+
2487 outarg.namelen = namelen;
+
2488 outarg.flags = 0;
+
2489 if (flags & FUSE_LL_EXPIRE_ONLY)
+
2490 outarg.flags |= FUSE_EXPIRE_ONLY;
+
2491
+
2492 iov[1].iov_base = &outarg;
+
2493 iov[1].iov_len = sizeof(outarg);
+
2494 iov[2].iov_base = (void *)name;
+
2495 iov[2].iov_len = namelen + 1;
+
2496
+
2497 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
2498}
+
2499
+
2500int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
2501 const char *name, size_t namelen)
+
2502{
+
2503 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
2504}
+
2505
+
2506int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
2507 const char *name, size_t namelen)
+
2508{
+
2509 if (!se)
+
2510 return -EINVAL;
+
2511
+
2512 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
2513 return -ENOSYS;
+
2514
+
2515 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
2516}
+
2517
+
2518
+
2519int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
2520 fuse_ino_t parent, fuse_ino_t child,
+
2521 const char *name, size_t namelen)
+
2522{
+
2523 struct fuse_notify_delete_out outarg;
+
2524 struct iovec iov[3];
+
2525
+
2526 if (!se)
+
2527 return -EINVAL;
+
2528
+
2529 if (se->conn.proto_minor < 18)
+
2530 return -ENOSYS;
+
2531
+
2532 outarg.parent = parent;
+
2533 outarg.child = child;
+
2534 outarg.namelen = namelen;
+
2535 outarg.padding = 0;
+
2536
+
2537 iov[1].iov_base = &outarg;
+
2538 iov[1].iov_len = sizeof(outarg);
+
2539 iov[2].iov_base = (void *)name;
+
2540 iov[2].iov_len = namelen + 1;
+
2541
+
2542 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
2543}
+
2544
+
2545int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
2546 off_t offset, struct fuse_bufvec *bufv,
+
2547 enum fuse_buf_copy_flags flags)
+
2548{
+
2549 struct fuse_out_header out;
+
2550 struct fuse_notify_store_out outarg;
+
2551 struct iovec iov[3];
+
2552 size_t size = fuse_buf_size(bufv);
+
2553 int res;
+
2554
+
2555 if (!se)
+
2556 return -EINVAL;
+
2557
+
2558 if (se->conn.proto_minor < 15)
+
2559 return -ENOSYS;
+
2560
+
2561 out.unique = 0;
+
2562 out.error = FUSE_NOTIFY_STORE;
+
2563
+
2564 outarg.nodeid = ino;
+
2565 outarg.offset = offset;
+
2566 outarg.size = size;
+
2567 outarg.padding = 0;
+
2568
+
2569 iov[0].iov_base = &out;
+
2570 iov[0].iov_len = sizeof(out);
+
2571 iov[1].iov_base = &outarg;
+
2572 iov[1].iov_len = sizeof(outarg);
+
2573
+
2574 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags);
+
2575 if (res > 0)
+
2576 res = -res;
+
2577
+
2578 return res;
+
2579}
+
2580
+
2581struct fuse_retrieve_req {
+
2582 struct fuse_notify_req nreq;
+
2583 void *cookie;
+
2584};
+
2585
+
2586static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
2587 fuse_req_t req, fuse_ino_t ino,
+
2588 const void *inarg,
+
2589 const struct fuse_buf *ibuf)
+
2590{
+
2591 struct fuse_session *se = req->se;
+
2592 struct fuse_retrieve_req *rreq =
+
2593 container_of(nreq, struct fuse_retrieve_req, nreq);
+
2594 const struct fuse_notify_retrieve_in *arg = inarg;
+
2595 struct fuse_bufvec bufv = {
+
2596 .buf[0] = *ibuf,
+
2597 .count = 1,
+
2598 };
+
2599
+
2600 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
2601 bufv.buf[0].mem = PARAM(arg);
+
2602
+
2603 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
2604 sizeof(struct fuse_notify_retrieve_in);
+
2605
+
2606 if (bufv.buf[0].size < arg->size) {
+
2607 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
2608 fuse_reply_none(req);
+
2609 goto out;
+
2610 }
+
2611 bufv.buf[0].size = arg->size;
+
2612
+
2613 if (se->op.retrieve_reply) {
+
2614 se->op.retrieve_reply(req, rreq->cookie, ino,
+
2615 arg->offset, &bufv);
+
2616 } else {
+
2617 fuse_reply_none(req);
+
2618 }
+
2619out:
+
2620 free(rreq);
+
2621 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
2622 fuse_ll_clear_pipe(se);
+
2623}
+
2624
+
2625int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
2626 size_t size, off_t offset, void *cookie)
+
2627{
+
2628 struct fuse_notify_retrieve_out outarg;
+
2629 struct iovec iov[2];
+
2630 struct fuse_retrieve_req *rreq;
+
2631 int err;
+
2632
+
2633 if (!se)
+
2634 return -EINVAL;
+
2635
+
2636 if (se->conn.proto_minor < 15)
+
2637 return -ENOSYS;
+
2638
+
2639 rreq = malloc(sizeof(*rreq));
+
2640 if (rreq == NULL)
+
2641 return -ENOMEM;
+
2642
+
2643 pthread_mutex_lock(&se->lock);
+
2644 rreq->cookie = cookie;
+
2645 rreq->nreq.unique = se->notify_ctr++;
+
2646 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
2647 list_add_nreq(&rreq->nreq, &se->notify_list);
+
2648 pthread_mutex_unlock(&se->lock);
+
2649
+
2650 outarg.notify_unique = rreq->nreq.unique;
+
2651 outarg.nodeid = ino;
+
2652 outarg.offset = offset;
+
2653 outarg.size = size;
+
2654 outarg.padding = 0;
+
2655
+
2656 iov[1].iov_base = &outarg;
+
2657 iov[1].iov_len = sizeof(outarg);
+
2658
+
2659 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
2660 if (err) {
+
2661 pthread_mutex_lock(&se->lock);
+
2662 list_del_nreq(&rreq->nreq);
+
2663 pthread_mutex_unlock(&se->lock);
+
2664 free(rreq);
+
2665 }
+
2666
+
2667 return err;
+
2668}
+
2669
+ +
2671{
+
2672 return req->se->userdata;
+
2673}
+
2674
+
2675const struct fuse_ctx *fuse_req_ctx(fuse_req_t req)
+
2676{
+
2677 return &req->ctx;
+
2678}
+
2679
+ +
2681 void *data)
+
2682{
+
2683 pthread_mutex_lock(&req->lock);
+
2684 pthread_mutex_lock(&req->se->lock);
+
2685 req->u.ni.func = func;
+
2686 req->u.ni.data = data;
+
2687 pthread_mutex_unlock(&req->se->lock);
+
2688 if (req->interrupted && func)
+
2689 func(req, data);
+
2690 pthread_mutex_unlock(&req->lock);
+
2691}
+
2692
+ +
2694{
+
2695 int interrupted;
+
2696
+
2697 pthread_mutex_lock(&req->se->lock);
+
2698 interrupted = req->interrupted;
+
2699 pthread_mutex_unlock(&req->se->lock);
+
2700
+
2701 return interrupted;
+
2702}
+
2703
+
2704static struct {
+
2705 void (*func)(fuse_req_t, fuse_ino_t, const void *);
+
2706 const char *name;
+
2707} fuse_ll_ops[] = {
+
2708 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
2709 [FUSE_FORGET] = { do_forget, "FORGET" },
+
2710 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
2711 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
2712 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
2713 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
2714 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
2715 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
2716 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
2717 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
2718 [FUSE_RENAME] = { do_rename, "RENAME" },
+
2719 [FUSE_LINK] = { do_link, "LINK" },
+
2720 [FUSE_OPEN] = { do_open, "OPEN" },
+
2721 [FUSE_READ] = { do_read, "READ" },
+
2722 [FUSE_WRITE] = { do_write, "WRITE" },
+
2723 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
2724 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
2725 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
2726 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
2727 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
2728 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
2729 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
2730 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
2731 [FUSE_INIT] = { do_init, "INIT" },
+
2732 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
2733 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
2734 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
2735 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
2736 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
2737 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
2738 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
2739 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
2740 [FUSE_CREATE] = { do_create, "CREATE" },
+
2741 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
2742 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
2743 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
2744 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
2745 [FUSE_POLL] = { do_poll, "POLL" },
+
2746 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
2747 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
2748 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
2749 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
2750 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
2751 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
2752 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
2753 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
2754 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
2755};
+
2756
+
2757/*
+
2758 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
2759 * Without ABI compatibility we could use the size of the array.
+
2760 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
2761 */
+
2762#define FUSE_MAXOP (CUSE_INIT + 1)
+
2763
+
2764static const char *opname(enum fuse_opcode opcode)
+
2765{
+
2766 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
2767 return "???";
+
2768 else
+
2769 return fuse_ll_ops[opcode].name;
+
2770}
+
2771
+
2772static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
2773 struct fuse_bufvec *src)
+
2774{
+
2775 ssize_t res = fuse_buf_copy(dst, src, 0);
+
2776 if (res < 0) {
+
2777 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
2778 return res;
+
2779 }
+
2780 if ((size_t)res < fuse_buf_size(dst)) {
+
2781 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
2782 return -1;
+
2783 }
+
2784 return 0;
+
2785}
+
2786
+
2787void fuse_session_process_buf(struct fuse_session *se,
+
2788 const struct fuse_buf *buf)
+
2789{
+
2790 fuse_session_process_buf_internal(se, buf, NULL);
+
2791}
+
2792
+
2793/* libfuse internal handler */
+
2794void fuse_session_process_buf_internal(struct fuse_session *se,
+
2795 const struct fuse_buf *buf, struct fuse_chan *ch)
+
2796{
+
2797 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
2798 sizeof(struct fuse_write_in);
+
2799 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
2800 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
2801 struct fuse_in_header *in;
+
2802 const void *inarg;
+
2803 struct fuse_req *req;
+
2804 void *mbuf = NULL;
+
2805 int err;
+
2806 int res;
+
2807
+
2808 if (buf->flags & FUSE_BUF_IS_FD) {
+
2809 if (buf->size < tmpbuf.buf[0].size)
+
2810 tmpbuf.buf[0].size = buf->size;
+
2811
+
2812 mbuf = malloc(tmpbuf.buf[0].size);
+
2813 if (mbuf == NULL) {
+
2814 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
2815 goto clear_pipe;
+
2816 }
+
2817 tmpbuf.buf[0].mem = mbuf;
+
2818
+
2819 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2820 if (res < 0)
+
2821 goto clear_pipe;
+
2822
+
2823 in = mbuf;
+
2824 } else {
+
2825 in = buf->mem;
+
2826 }
+
2827
+
2828 if (se->debug) {
+
2829 fuse_log(FUSE_LOG_DEBUG,
+
2830 "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
2831 (unsigned long long) in->unique,
+
2832 opname((enum fuse_opcode) in->opcode), in->opcode,
+
2833 (unsigned long long) in->nodeid, buf->size, in->pid);
+
2834 }
+
2835
+
2836 req = fuse_ll_alloc_req(se);
+
2837 if (req == NULL) {
+
2838 struct fuse_out_header out = {
+
2839 .unique = in->unique,
+
2840 .error = -ENOMEM,
+
2841 };
+
2842 struct iovec iov = {
+
2843 .iov_base = &out,
+
2844 .iov_len = sizeof(struct fuse_out_header),
+
2845 };
+
2846
+
2847 fuse_send_msg(se, ch, &iov, 1);
+
2848 goto clear_pipe;
+
2849 }
+
2850
+
2851 req->unique = in->unique;
+
2852 req->ctx.uid = in->uid;
+
2853 req->ctx.gid = in->gid;
+
2854 req->ctx.pid = in->pid;
+
2855 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
2856
+
2857 err = EIO;
+
2858 if (!se->got_init) {
+
2859 enum fuse_opcode expected;
+
2860
+
2861 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
2862 if (in->opcode != expected)
+
2863 goto reply_err;
+
2864 } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+
2865 goto reply_err;
+
2866
+
2867 err = EACCES;
+
2868 /* Implement -o allow_root */
+
2869 if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
+
2870 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+
2871 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+
2872 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+
2873 in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+
2874 in->opcode != FUSE_NOTIFY_REPLY &&
+
2875 in->opcode != FUSE_READDIRPLUS)
+
2876 goto reply_err;
+
2877
+
2878 err = ENOSYS;
+
2879 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
2880 goto reply_err;
+
2881 /* Do not process interrupt request */
+
2882 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
2883 if (se->debug)
+
2884 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
2885 goto reply_err;
+
2886 }
+
2887 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
2888 struct fuse_req *intr;
+
2889 pthread_mutex_lock(&se->lock);
+
2890 intr = check_interrupt(se, req);
+
2891 list_add_req(req, &se->list);
+
2892 pthread_mutex_unlock(&se->lock);
+
2893 if (intr)
+
2894 fuse_reply_err(intr, EAGAIN);
+
2895 }
+
2896
+
2897 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
2898 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
2899 in->opcode != FUSE_NOTIFY_REPLY) {
+
2900 void *newmbuf;
+
2901
+
2902 err = ENOMEM;
+
2903 newmbuf = realloc(mbuf, buf->size);
+
2904 if (newmbuf == NULL)
+
2905 goto reply_err;
+
2906 mbuf = newmbuf;
+
2907
+
2908 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
2909 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
2910
+
2911 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2912 err = -res;
+
2913 if (res < 0)
+
2914 goto reply_err;
+
2915
+
2916 in = mbuf;
+
2917 }
+
2918
+
2919 inarg = (void *) &in[1];
+
2920 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
2921 do_write_buf(req, in->nodeid, inarg, buf);
+
2922 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
2923 do_notify_reply(req, in->nodeid, inarg, buf);
+
2924 else
+
2925 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
2926
+
2927out_free:
+
2928 free(mbuf);
+
2929 return;
+
2930
+
2931reply_err:
+
2932 fuse_reply_err(req, err);
+
2933clear_pipe:
+
2934 if (buf->flags & FUSE_BUF_IS_FD)
+
2935 fuse_ll_clear_pipe(se);
+
2936 goto out_free;
+
2937}
+
2938
+
2939#define LL_OPTION(n,o,v) \
+
2940 { n, offsetof(struct fuse_session, o), v }
+
2941
+
2942static const struct fuse_opt fuse_ll_opts[] = {
+
2943 LL_OPTION("debug", debug, 1),
+
2944 LL_OPTION("-d", debug, 1),
+
2945 LL_OPTION("--debug", debug, 1),
+
2946 LL_OPTION("allow_root", deny_others, 1),
+ +
2948};
+
2949
+
2950void fuse_lowlevel_version(void)
+
2951{
+
2952 printf("using FUSE kernel interface version %i.%i\n",
+
2953 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
2954 fuse_mount_version();
+
2955}
+
2956
+
2957void fuse_lowlevel_help(void)
+
2958{
+
2959 /* These are not all options, but the ones that are
+
2960 potentially of interest to an end-user */
+
2961 printf(
+
2962" -o allow_other allow access by all users\n"
+
2963" -o allow_root allow access by root\n"
+
2964" -o auto_unmount auto unmount on process termination\n");
+
2965}
+
2966
+
2967void fuse_session_destroy(struct fuse_session *se)
+
2968{
+
2969 struct fuse_ll_pipe *llp;
+
2970
+
2971 if (se->got_init && !se->got_destroy) {
+
2972 if (se->op.destroy)
+
2973 se->op.destroy(se->userdata);
+
2974 }
+
2975 llp = pthread_getspecific(se->pipe_key);
+
2976 if (llp != NULL)
+
2977 fuse_ll_pipe_free(llp);
+
2978 pthread_key_delete(se->pipe_key);
+
2979 pthread_mutex_destroy(&se->lock);
+
2980 free(se->cuse_data);
+
2981 if (se->fd != -1)
+
2982 close(se->fd);
+
2983 if (se->io != NULL)
+
2984 free(se->io);
+
2985 destroy_mount_opts(se->mo);
+
2986 free(se);
+
2987}
+
2988
+
2989
+
2990static void fuse_ll_pipe_destructor(void *data)
+
2991{
+
2992 struct fuse_ll_pipe *llp = data;
+
2993 fuse_ll_pipe_free(llp);
+
2994}
+
2995
+
2996void fuse_buf_free(struct fuse_buf *buf)
+
2997{
+
2998 if (buf->mem == NULL)
+
2999 return;
+
3000
+
3001 size_t write_header_sz =
+
3002 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
3003
+
3004 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
3005 free(ptr);
+
3006 buf->mem = NULL;
+
3007}
+
3008
+
3009/*
+
3010 * This is used to allocate buffers that hold fuse requests
+
3011 */
+
3012static void *buf_alloc(size_t size, bool internal)
+
3013{
+
3014 /*
+
3015 * For libfuse internal caller add in alignment. That cannot be done
+
3016 * for an external caller, as it is not guaranteed that the external
+
3017 * caller frees the raw pointer.
+
3018 */
+
3019 if (internal) {
+
3020 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3021 sizeof(struct fuse_write_in);
+
3022 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3023
+
3024 char *buf = aligned_alloc(pagesize, new_size);
+
3025 if (buf == NULL)
+
3026 return NULL;
+
3027
+
3028 buf += pagesize - write_header_sz;
+
3029
+
3030 return buf;
+
3031 } else {
+
3032 return malloc(size);
+
3033 }
+
3034}
+
3035
+
3036/*
+
3037 *@param internal true if called from libfuse internal code
+
3038 */
+
3039static int _fuse_session_receive_buf(struct fuse_session *se,
+
3040 struct fuse_buf *buf, struct fuse_chan *ch,
+
3041 bool internal)
+
3042{
+
3043 int err;
+
3044 ssize_t res;
+
3045 size_t bufsize = se->bufsize;
+
3046#ifdef HAVE_SPLICE
+
3047 struct fuse_ll_pipe *llp;
+
3048 struct fuse_buf tmpbuf;
+
3049
+
3050 if (se->conn.proto_minor < 14 ||
+
3051 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3052 goto fallback;
+
3053
+
3054 llp = fuse_ll_get_pipe(se);
+
3055 if (llp == NULL)
+
3056 goto fallback;
+
3057
+
3058 if (llp->size < bufsize) {
+
3059 if (llp->can_grow) {
+
3060 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3061 if (res == -1) {
+
3062 llp->can_grow = 0;
+
3063 res = grow_pipe_to_max(llp->pipe[0]);
+
3064 if (res > 0)
+
3065 llp->size = res;
+
3066 goto fallback;
+
3067 }
+
3068 llp->size = res;
+
3069 }
+
3070 if (llp->size < bufsize)
+
3071 goto fallback;
+
3072 }
+
3073
+
3074 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3075 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3076 llp->pipe[1], NULL, bufsize, 0,
+
3077 se->userdata);
+
3078 } else {
+
3079 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3080 bufsize, 0);
+
3081 }
+
3082 err = errno;
+
3083
+
3084 if (fuse_session_exited(se))
+
3085 return 0;
+
3086
+
3087 if (res == -1) {
+
3088 if (err == ENODEV) {
+
3089 /* Filesystem was unmounted, or connection was aborted
+
3090 via /sys/fs/fuse/connections */
+ +
3092 return 0;
+
3093 }
+
3094 if (err != EINTR && err != EAGAIN)
+
3095 perror("fuse: splice from device");
+
3096 return -err;
+
3097 }
+
3098
+
3099 if (res < sizeof(struct fuse_in_header)) {
+
3100 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3101 return -EIO;
+
3102 }
+
3103
+
3104 tmpbuf = (struct fuse_buf){
+
3105 .size = res,
+
3106 .flags = FUSE_BUF_IS_FD,
+
3107 .fd = llp->pipe[0],
+
3108 };
+
3109
+
3110 /*
+
3111 * Don't bother with zero copy for small requests.
+
3112 * fuse_loop_mt() needs to check for FORGET so this more than
+
3113 * just an optimization.
+
3114 */
+
3115 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3116 pagesize) {
+
3117 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3118 struct fuse_bufvec dst = { .count = 1 };
+
3119
+
3120 if (!buf->mem) {
+
3121 buf->mem = buf_alloc(se->bufsize, internal);
+
3122 if (!buf->mem) {
+
3123 fuse_log(
+
3124 FUSE_LOG_ERR,
+
3125 "fuse: failed to allocate read buffer\n");
+
3126 return -ENOMEM;
+
3127 }
+
3128 buf->mem_size = se->bufsize;
+
3129 if (internal)
+
3130 se->buf_reallocable = true;
+
3131 }
+
3132 buf->size = se->bufsize;
+
3133 buf->flags = 0;
+
3134 dst.buf[0] = *buf;
+
3135
+
3136 res = fuse_buf_copy(&dst, &src, 0);
+
3137 if (res < 0) {
+
3138 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3139 strerror(-res));
+
3140 fuse_ll_clear_pipe(se);
+
3141 return res;
+
3142 }
+
3143 if (res < tmpbuf.size) {
+
3144 fuse_log(FUSE_LOG_ERR,
+
3145 "fuse: copy from pipe: short read\n");
+
3146 fuse_ll_clear_pipe(se);
+
3147 return -EIO;
+
3148 }
+
3149 assert(res == tmpbuf.size);
+
3150
+
3151 } else {
+
3152 /* Don't overwrite buf->mem, as that would cause a leak */
+
3153 buf->fd = tmpbuf.fd;
+
3154 buf->flags = tmpbuf.flags;
+
3155 }
+
3156 buf->size = tmpbuf.size;
+
3157
+
3158 return res;
+
3159
+
3160fallback:
+
3161#endif
+
3162 if (!buf->mem) {
+
3163 buf->mem = buf_alloc(se->bufsize, internal);
+
3164 if (!buf->mem) {
+
3165 fuse_log(FUSE_LOG_ERR,
+
3166 "fuse: failed to allocate read buffer\n");
+
3167 return -ENOMEM;
+
3168 }
+
3169 buf->mem_size = se->bufsize;
+
3170 if (internal)
+
3171 se->buf_reallocable = true;
+
3172 }
+
3173
+
3174restart:
+
3175 if (se->buf_reallocable)
+
3176 bufsize = buf->mem_size;
+
3177 if (se->io != NULL) {
+
3178 /* se->io->read is never NULL if se->io is not NULL as
+
3179 specified by fuse_session_custom_io()*/
+
3180 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
3181 se->userdata);
+
3182 } else {
+
3183 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
3184 }
+
3185 err = errno;
+
3186
+
3187 if (fuse_session_exited(se))
+
3188 return 0;
+
3189 if (res == -1) {
+
3190 if (err == EINVAL && se->buf_reallocable &&
+
3191 se->bufsize > buf->mem_size) {
+
3192 void *newbuf = buf_alloc(se->bufsize, internal);
+
3193 if (!newbuf) {
+
3194 fuse_log(
+
3195 FUSE_LOG_ERR,
+
3196 "fuse: failed to (re)allocate read buffer\n");
+
3197 return -ENOMEM;
+
3198 }
+
3199 fuse_buf_free(buf);
+
3200 buf->mem = newbuf;
+
3201 buf->mem_size = se->bufsize;
+
3202 se->buf_reallocable = true;
+
3203 goto restart;
+
3204 }
+
3205
+
3206 /* ENOENT means the operation was interrupted, it's safe
+
3207 to restart */
+
3208 if (err == ENOENT)
+
3209 goto restart;
+
3210
+
3211 if (err == ENODEV) {
+
3212 /* Filesystem was unmounted, or connection was aborted
+
3213 via /sys/fs/fuse/connections */
+ +
3215 return 0;
+
3216 }
+
3217 /* Errors occurring during normal operation: EINTR (read
+
3218 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
3219 umounted) */
+
3220 if (err != EINTR && err != EAGAIN)
+
3221 perror("fuse: reading device");
+
3222 return -err;
+
3223 }
+
3224 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
3225 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
3226 return -EIO;
+
3227 }
+
3228
+
3229 buf->size = res;
+
3230
+
3231 return res;
+
3232}
+
3233
+
3234int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
3235{
+
3236 return _fuse_session_receive_buf(se, buf, NULL, false);
+
3237}
+
3238
+
3239/* libfuse internal handler */
+
3240int fuse_session_receive_buf_internal(struct fuse_session *se,
+
3241 struct fuse_buf *buf,
+
3242 struct fuse_chan *ch)
+
3243{
+
3244 return _fuse_session_receive_buf(se, buf, ch, true);
+
3245}
+
3246
+
3247struct fuse_session *
+
3248fuse_session_new_versioned(struct fuse_args *args,
+
3249 const struct fuse_lowlevel_ops *op, size_t op_size,
+
3250 struct libfuse_version *version, void *userdata)
+
3251{
+
3252 int err;
+
3253 struct fuse_session *se;
+
3254 struct mount_opts *mo;
+
3255
+
3256 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
3257 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3258 op_size = sizeof(struct fuse_lowlevel_ops);
+
3259 }
+
3260
+
3261 if (args->argc == 0) {
+
3262 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
3263 return NULL;
+
3264 }
+
3265
+
3266 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
3267 if (se == NULL) {
+
3268 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
3269 goto out1;
+
3270 }
+
3271 se->fd = -1;
+
3272 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
3273 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
3274 se->conn.max_readahead = UINT_MAX;
+
3275
+
3276 /* Parse options */
+
3277 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
3278 goto out2;
+
3279 if(se->deny_others) {
+
3280 /* Allowing access only by root is done by instructing
+
3281 * kernel to allow access by everyone, and then restricting
+
3282 * access to root and mountpoint owner in libfuse.
+
3283 */
+
3284 // We may be adding the option a second time, but
+
3285 // that doesn't hurt.
+
3286 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
3287 goto out2;
+
3288 }
+
3289 mo = parse_mount_opts(args);
+
3290 if (mo == NULL)
+
3291 goto out3;
+
3292
+
3293 if(args->argc == 1 &&
+
3294 args->argv[0][0] == '-') {
+
3295 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
3296 "will be ignored\n");
+
3297 } else if (args->argc != 1) {
+
3298 int i;
+
3299 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
3300 for(i = 1; i < args->argc-1; i++)
+
3301 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
3302 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
3303 goto out4;
+
3304 }
+
3305
+
3306 if (se->debug)
+
3307 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
3308
+
3309 list_init_req(&se->list);
+
3310 list_init_req(&se->interrupts);
+
3311 list_init_nreq(&se->notify_list);
+
3312 se->notify_ctr = 1;
+
3313 pthread_mutex_init(&se->lock, NULL);
+
3314
+
3315 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
3316 if (err) {
+
3317 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
3318 strerror(err));
+
3319 goto out5;
+
3320 }
+
3321
+
3322 memcpy(&se->op, op, op_size);
+
3323 se->owner = getuid();
+
3324 se->userdata = userdata;
+
3325
+
3326 se->mo = mo;
+
3327
+
3328 /* Fuse server application should pass the version it was compiled
+
3329 * against and pass it. If a libfuse version accidentally introduces an
+
3330 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
3331 * by checking the version numbers.
+
3332 */
+
3333 se->version = *version;
+
3334
+
3335 return se;
+
3336
+
3337out5:
+
3338 pthread_mutex_destroy(&se->lock);
+
3339out4:
+
3340 fuse_opt_free_args(args);
+
3341out3:
+
3342 if (mo != NULL)
+
3343 destroy_mount_opts(mo);
+
3344out2:
+
3345 free(se);
+
3346out1:
+
3347 return NULL;
+
3348}
+
3349
+
3350struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3351 const struct fuse_lowlevel_ops *op,
+
3352 size_t op_size, void *userdata);
+
3353struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3354 const struct fuse_lowlevel_ops *op,
+
3355 size_t op_size,
+
3356 void *userdata)
+
3357{
+
3358 /* unknown version */
+
3359 struct libfuse_version version = { 0 };
+
3360
+
3361 return fuse_session_new_versioned(args, op, op_size, &version,
+
3362 userdata);
+
3363}
+
3364
+
3365FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
3366int fuse_session_custom_io_317(struct fuse_session *se,
+
3367 const struct fuse_custom_io *io, size_t op_size, int fd)
+
3368{
+
3369 if (sizeof(struct fuse_custom_io) < op_size) {
+
3370 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3371 op_size = sizeof(struct fuse_custom_io);
+
3372 }
+
3373
+
3374 if (fd < 0) {
+
3375 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
3376 "fuse_session_custom_io()\n", fd);
+
3377 return -EBADF;
+
3378 }
+
3379 if (io == NULL) {
+
3380 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
3381 "fuse_session_custom_io()\n");
+
3382 return -EINVAL;
+
3383 } else if (io->read == NULL || io->writev == NULL) {
+
3384 /* If the user provides their own file descriptor, we can't
+
3385 guarantee that the default behavior of the io operations made
+
3386 in libfuse will function properly. Therefore, we enforce the
+
3387 user to implement these io operations when using custom io. */
+
3388 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
3389 "implement both io->read() and io->writev\n");
+
3390 return -EINVAL;
+
3391 }
+
3392
+
3393 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
3394 if (se->io == NULL) {
+
3395 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
3396 "Error: %s\n", strerror(errno));
+
3397 return -errno;
+
3398 }
+
3399
+
3400 se->fd = fd;
+
3401 memcpy(se->io, io, op_size);
+
3402 return 0;
+
3403}
+
3404
+
3405int fuse_session_custom_io_30(struct fuse_session *se,
+
3406 const struct fuse_custom_io *io, int fd);
+
3407FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
3408int fuse_session_custom_io_30(struct fuse_session *se,
+
3409 const struct fuse_custom_io *io, int fd)
+
3410{
+
3411 return fuse_session_custom_io_317(se, io,
+
3412 offsetof(struct fuse_custom_io, clone_fd), fd);
+
3413}
+
3414
+
3415int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
3416{
+
3417 int fd;
+
3418
+
3419 if (mountpoint == NULL) {
+
3420 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
3421 return -1;
+
3422 }
+
3423
+
3424 /*
+
3425 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
3426 * would ensue.
+
3427 */
+
3428 do {
+
3429 fd = open("/dev/null", O_RDWR);
+
3430 if (fd > 2)
+
3431 close(fd);
+
3432 } while (fd >= 0 && fd <= 2);
+
3433
+
3434 /*
+
3435 * To allow FUSE daemons to run without privileges, the caller may open
+
3436 * /dev/fuse before launching the file system and pass on the file
+
3437 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
3438 * parent process takes care of performing the mount in this case.
+
3439 */
+
3440 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
3441 if (fd != -1) {
+
3442 if (fcntl(fd, F_GETFD) == -1) {
+
3443 fuse_log(FUSE_LOG_ERR,
+
3444 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
3445 fd);
+
3446 return -1;
+
3447 }
+
3448 se->fd = fd;
+
3449 return 0;
+
3450 }
+
3451
+
3452 /* Open channel */
+
3453 fd = fuse_kern_mount(mountpoint, se->mo);
+
3454 if (fd == -1)
+
3455 return -1;
+
3456 se->fd = fd;
+
3457
+
3458 /* Save mountpoint */
+
3459 se->mountpoint = strdup(mountpoint);
+
3460 if (se->mountpoint == NULL)
+
3461 goto error_out;
+
3462
+
3463 return 0;
+
3464
+
3465error_out:
+
3466 fuse_kern_unmount(mountpoint, fd);
+
3467 return -1;
+
3468}
+
3469
+
3470int fuse_session_fd(struct fuse_session *se)
+
3471{
+
3472 return se->fd;
+
3473}
+
3474
+
3475void fuse_session_unmount(struct fuse_session *se)
+
3476{
+
3477 if (se->mountpoint != NULL) {
+
3478 fuse_kern_unmount(se->mountpoint, se->fd);
+
3479 se->fd = -1;
+
3480 free(se->mountpoint);
+
3481 se->mountpoint = NULL;
+
3482 }
+
3483}
+
3484
+
3485#ifdef linux
+
3486int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3487{
+
3488 char *buf;
+
3489 size_t bufsize = 1024;
+
3490 char path[128];
+
3491 int ret;
+
3492 int fd;
+
3493 unsigned long pid = req->ctx.pid;
+
3494 char *s;
+
3495
+
3496 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
3497
+
3498retry:
+
3499 buf = malloc(bufsize);
+
3500 if (buf == NULL)
+
3501 return -ENOMEM;
+
3502
+
3503 ret = -EIO;
+
3504 fd = open(path, O_RDONLY);
+
3505 if (fd == -1)
+
3506 goto out_free;
+
3507
+
3508 ret = read(fd, buf, bufsize);
+
3509 close(fd);
+
3510 if (ret < 0) {
+
3511 ret = -EIO;
+
3512 goto out_free;
+
3513 }
+
3514
+
3515 if ((size_t)ret == bufsize) {
+
3516 free(buf);
+
3517 bufsize *= 4;
+
3518 goto retry;
+
3519 }
+
3520
+
3521 buf[ret] = '\0';
+
3522 ret = -EIO;
+
3523 s = strstr(buf, "\nGroups:");
+
3524 if (s == NULL)
+
3525 goto out_free;
+
3526
+
3527 s += 8;
+
3528 ret = 0;
+
3529 while (1) {
+
3530 char *end;
+
3531 unsigned long val = strtoul(s, &end, 0);
+
3532 if (end == s)
+
3533 break;
+
3534
+
3535 s = end;
+
3536 if (ret < size)
+
3537 list[ret] = val;
+
3538 ret++;
+
3539 }
+
3540
+
3541out_free:
+
3542 free(buf);
+
3543 return ret;
+
3544}
+
3545#else /* linux */
+
3546/*
+
3547 * This is currently not implemented on other than Linux...
+
3548 */
+
3549int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3550{
+
3551 (void) req; (void) size; (void) list;
+
3552 return -ENOSYS;
+
3553}
+
3554#endif
+
3555
+
3556/* Prevent spurious data race warning - we don't care
+
3557 * about races for this flag */
+
3558__attribute__((no_sanitize_thread))
+
3559void fuse_session_exit(struct fuse_session *se)
+
3560{
+
3561 se->exited = 1;
+
3562}
+
3563
+
3564__attribute__((no_sanitize_thread))
+
3565void fuse_session_reset(struct fuse_session *se)
+
3566{
+
3567 se->exited = 0;
+
3568 se->error = 0;
+
3569}
+
3570
+
3571__attribute__((no_sanitize_thread))
+
3572int fuse_session_exited(struct fuse_session *se)
+
3573{
+
3574 return se->exited;
+
3575}
+
@ FUSE_CAP_POSIX_ACL
+
@ FUSE_CAP_READDIRPLUS
+
@ FUSE_CAP_NO_OPENDIR_SUPPORT
+
@ FUSE_CAP_PARALLEL_DIROPS
+
@ FUSE_CAP_ASYNC_DIO
+
@ FUSE_CAP_NO_EXPORT_SUPPORT
+
@ FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_CAP_IOCTL_DIR
+
@ FUSE_CAP_AUTO_INVAL_DATA
+
@ FUSE_CAP_SPLICE_READ
+
@ FUSE_CAP_SPLICE_MOVE
+
@ FUSE_CAP_POSIX_LOCKS
+
@ FUSE_CAP_HANDLE_KILLPRIV_V2
+
@ FUSE_CAP_HANDLE_KILLPRIV
+
@ FUSE_CAP_DONT_MASK
+
@ FUSE_CAP_ATOMIC_O_TRUNC
+
@ FUSE_CAP_SPLICE_WRITE
+
@ FUSE_CAP_PASSTHROUGH
+
@ FUSE_CAP_FLOCK_LOCKS
+
@ FUSE_CAP_EXPIRE_ONLY
+
@ FUSE_CAP_EXPORT_SUPPORT
+
@ FUSE_CAP_READDIRPLUS_AUTO
+
@ FUSE_CAP_NO_OPEN_SUPPORT
+
@ FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
@ FUSE_CAP_SETXATTR_EXT
+
@ FUSE_CAP_ASYNC_READ
+
@ FUSE_CAP_CACHE_SYMLINKS
+
@ FUSE_CAP_EXPLICIT_INVAL_DATA
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
@ FUSE_BUF_IS_FD
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
struct fuse_req * fuse_req_t
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
uint64_t fuse_ino_t
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + + +
uint64_t want_ext
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__misc_8h_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..c703ca7 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__opt_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..1f8eb49 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__opt_8c_source.html @@ -0,0 +1,504 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
143
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2fuse__signals_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..3d0a456 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2fuse__signals_8c_source.html @@ -0,0 +1,263 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
26static int ignore_sigs[] = { SIGPIPE};
+
27static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
28static struct fuse_session *fuse_instance;
+
29
+
30#define BT_STACK_SZ (1024 * 1024)
+
31static void *backtrace_buffer[BT_STACK_SZ];
+
32
+
33static void dump_stack(void)
+
34{
+
35#ifdef HAVE_BACKTRACE
+
36 char **strings;
+
37
+
38 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
39 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
40
+
41 if (strings == NULL) {
+
42 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
43 strerror(errno));
+
44 return;
+
45 }
+
46
+
47 for (int idx = 0; idx < nptrs; idx++)
+
48 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
49
+
50 free(strings);
+
51#endif
+
52}
+
53
+
54static void exit_handler(int sig)
+
55{
+
56 if (fuse_instance == NULL)
+
57 return;
+
58
+
59 fuse_session_exit(fuse_instance);
+
60
+
61 if (sig < 0) {
+
62 fuse_log(FUSE_LOG_ERR,
+
63 "assertion error: signal value <= 0\n");
+
64 dump_stack();
+
65 abort();
+
66 fuse_instance->error = sig;
+
67 }
+
68
+
69 fuse_instance->error = sig;
+
70}
+
71
+
72static void exit_backtrace(int sig)
+
73{
+
74 if (fuse_instance == NULL)
+
75 return;
+
76
+
77 fuse_session_exit(fuse_instance);
+
78
+
79 fuse_remove_signal_handlers(fuse_instance);
+
80 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
81 dump_stack();
+
82 abort();
+
83}
+
84
+
85
+
86static void do_nothing(int sig)
+
87{
+
88 (void) sig;
+
89}
+
90
+
91static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
92{
+
93 struct sigaction sa;
+
94 struct sigaction old_sa;
+
95
+
96 memset(&sa, 0, sizeof(struct sigaction));
+
97 sa.sa_handler = remove ? SIG_DFL : handler;
+
98 sigemptyset(&(sa.sa_mask));
+
99 sa.sa_flags = 0;
+
100
+
101 if (sigaction(sig, NULL, &old_sa) == -1) {
+
102 perror("fuse: cannot get old signal handler");
+
103 return -1;
+
104 }
+
105
+
106 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
107 sigaction(sig, &sa, NULL) == -1) {
+
108 perror("fuse: cannot set signal handler");
+
109 return -1;
+
110 }
+
111 return 0;
+
112}
+
113
+
114static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
115 void (*handler)(int))
+
116{
+
117 for (int idx = 0; idx < nr_signals; idx++) {
+
118 int signal = signals[idx];
+
119
+
120 /*
+
121 * If we used SIG_IGN instead of the do_nothing function,
+
122 * then we would be unable to tell if we set SIG_IGN (and
+
123 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
124 * or if it was already set to SIG_IGN (and should be left
+
125 * untouched.
+
126 */
+
127 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
128 fuse_log(FUSE_LOG_ERR,
+
129 "Failed to install signal handler for sig %d\n",
+
130 signal);
+
131 return -1;
+
132 }
+
133 }
+
134
+
135 return 0;
+
136}
+
137
+
138int fuse_set_signal_handlers(struct fuse_session *se)
+
139{
+
140 size_t nr_signals;
+
141 int rc;
+
142
+
143 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
144 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
145 if (rc < 0)
+
146 return rc;
+
147
+
148 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
149 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
150 if (rc < 0)
+
151 return rc;
+
152
+
153 if (fuse_instance == NULL)
+
154 fuse_instance = se;
+
155 return 0;
+
156}
+
157
+
158int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
159{
+
160 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
161
+
162 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
163 exit_backtrace);
+
164 if (rc < 0)
+
165 return rc;
+
166
+
167 if (fuse_instance == NULL)
+
168 fuse_instance = se;
+
169
+
170 return 0;
+
171}
+
172
+
173static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
174 void (*handler)(int))
+
175{
+
176 for (int idx = 0; idx < nr_signals; idx++)
+
177 set_one_signal_handler(signals[idx], handler, 1);
+
178}
+
179
+
180void fuse_remove_signal_handlers(struct fuse_session *se)
+
181{
+
182 size_t nr_signals;
+
183
+
184 if (fuse_instance != se)
+
185 fuse_log(FUSE_LOG_ERR,
+
186 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
187 else
+
188 fuse_instance = NULL;
+
189
+
190 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
191 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
192
+
193 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
194 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
195
+
196 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
197 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
198}
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2helper_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2helper_8c_source.html new file mode 100644 index 0000000..66a25e5 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2helper_8c_source.html @@ -0,0 +1,598 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
130void fuse_cmdline_help(void)
+
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
237int fuse_parse_cmdline_30(struct fuse_args *args,
+
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
252
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
306
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond,cap) \
+
427 if (cond) conn->want |= (cap)
+
428#define LL_DISABLE(cond,cap) \
+
429 if (cond) conn->want &= ~(cap)
+
430
+
431 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
432 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
433
+
434 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
435 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
436
+
437 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
438 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
439
+
440 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
441 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
442
+
443 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
444 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
445
+
446 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
447 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
448
+
449 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
450 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
451
+
452 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
453 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
454
+
455 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
456 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
457}
+
458
+
459struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
460{
+
461 struct fuse_conn_info_opts *opts;
+
462
+
463 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
464 if(opts == NULL) {
+
465 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
466 return NULL;
+
467 }
+
468 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
469 free(opts);
+
470 return NULL;
+
471 }
+
472 return opts;
+
473}
+
474
+
475int fuse_open_channel(const char *mountpoint, const char* options)
+
476{
+
477 struct mount_opts *opts = NULL;
+
478 int fd = -1;
+
479 const char *argv[] = { "", "-o", options };
+
480 int argc = sizeof(argv) / sizeof(argv[0]);
+
481 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
482
+
483 opts = parse_mount_opts(&args);
+
484 if (opts == NULL)
+
485 return -1;
+
486
+
487 fd = fuse_kern_mount(mountpoint, opts);
+
488 destroy_mount_opts(opts);
+
489
+
490 return fd;
+
491}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5204
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5153
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4577
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4744
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:479
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4520
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5209
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
@ FUSE_CAP_READDIRPLUS
+
@ FUSE_CAP_ASYNC_DIO
+
@ FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_CAP_AUTO_INVAL_DATA
+
@ FUSE_CAP_SPLICE_READ
+
@ FUSE_CAP_SPLICE_MOVE
+
@ FUSE_CAP_POSIX_LOCKS
+
@ FUSE_CAP_SPLICE_WRITE
+
@ FUSE_CAP_FLOCK_LOCKS
+
@ FUSE_CAP_READDIRPLUS_AUTO
+
@ FUSE_CAP_ASYNC_READ
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:463
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:416
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2modules_2iconv_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..51f7173 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2modules_2iconv_8c_source.html @@ -0,0 +1,816 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571static void *iconv_init(struct fuse_conn_info *conn,
+
572 struct fuse_config *cfg)
+
573{
+
574 struct iconv *ic = iconv_get();
+
575 fuse_fs_init(ic->next, conn, cfg);
+
576 /* Don't touch cfg->nullpath_ok, we can work with
+
577 either */
+
578 return ic;
+
579}
+
580
+
581static void iconv_destroy(void *data)
+
582{
+
583 struct iconv *ic = data;
+
584 fuse_fs_destroy(ic->next);
+
585 iconv_close(ic->tofs);
+
586 iconv_close(ic->fromfs);
+
587 pthread_mutex_destroy(&ic->lock);
+
588 free(ic->from_code);
+
589 free(ic->to_code);
+
590 free(ic);
+
591}
+
592
+
593static const struct fuse_operations iconv_oper = {
+
594 .destroy = iconv_destroy,
+
595 .init = iconv_init,
+
596 .getattr = iconv_getattr,
+
597 .access = iconv_access,
+
598 .readlink = iconv_readlink,
+
599 .opendir = iconv_opendir,
+
600 .readdir = iconv_readdir,
+
601 .releasedir = iconv_releasedir,
+
602 .mknod = iconv_mknod,
+
603 .mkdir = iconv_mkdir,
+
604 .symlink = iconv_symlink,
+
605 .unlink = iconv_unlink,
+
606 .rmdir = iconv_rmdir,
+
607 .rename = iconv_rename,
+
608 .link = iconv_link,
+
609 .chmod = iconv_chmod,
+
610 .chown = iconv_chown,
+
611 .truncate = iconv_truncate,
+
612 .utimens = iconv_utimens,
+
613 .create = iconv_create,
+
614 .open = iconv_open_file,
+
615 .read_buf = iconv_read_buf,
+
616 .write_buf = iconv_write_buf,
+
617 .statfs = iconv_statfs,
+
618 .flush = iconv_flush,
+
619 .release = iconv_release,
+
620 .fsync = iconv_fsync,
+
621 .fsyncdir = iconv_fsyncdir,
+
622 .setxattr = iconv_setxattr,
+
623 .getxattr = iconv_getxattr,
+
624 .listxattr = iconv_listxattr,
+
625 .removexattr = iconv_removexattr,
+
626 .lock = iconv_lock,
+
627 .flock = iconv_flock,
+
628 .bmap = iconv_bmap,
+
629 .lseek = iconv_lseek,
+
630};
+
631
+
632static const struct fuse_opt iconv_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
636 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
638};
+
639
+
640static void iconv_help(void)
+
641{
+
642 char *charmap;
+
643 const char *old = setlocale(LC_CTYPE, "");
+
644
+
645 charmap = strdup(nl_langinfo(CODESET));
+
646 if (old)
+
647 setlocale(LC_CTYPE, old);
+
648 else
+
649 perror("setlocale");
+
650
+
651 printf(
+
652" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
653" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
654 charmap);
+
655 free(charmap);
+
656}
+
657
+
658static int iconv_opt_proc(void *data, const char *arg, int key,
+
659 struct fuse_args *outargs)
+
660{
+
661 (void) data; (void) arg; (void) outargs;
+
662
+
663 if (!key) {
+
664 iconv_help();
+
665 return -1;
+
666 }
+
667
+
668 return 1;
+
669}
+
670
+
671static struct fuse_fs *iconv_new(struct fuse_args *args,
+
672 struct fuse_fs *next[])
+
673{
+
674 struct fuse_fs *fs;
+
675 struct iconv *ic;
+
676 const char *old = NULL;
+
677 const char *from;
+
678 const char *to;
+
679
+
680 ic = calloc(1, sizeof(struct iconv));
+
681 if (ic == NULL) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
683 return NULL;
+
684 }
+
685
+
686 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
687 goto out_free;
+
688
+
689 if (!next[0] || next[1]) {
+
690 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
691 goto out_free;
+
692 }
+
693
+
694 from = ic->from_code ? ic->from_code : "UTF-8";
+
695 to = ic->to_code ? ic->to_code : "";
+
696 /* FIXME: detect charset equivalence? */
+
697 if (!to[0])
+
698 old = setlocale(LC_CTYPE, "");
+
699 ic->tofs = iconv_open(from, to);
+
700 if (ic->tofs == (iconv_t) -1) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
702 to, from);
+
703 goto out_free;
+
704 }
+
705 ic->fromfs = iconv_open(to, from);
+
706 if (ic->tofs == (iconv_t) -1) {
+
707 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
708 from, to);
+
709 goto out_iconv_close_to;
+
710 }
+
711 if (old) {
+
712 setlocale(LC_CTYPE, old);
+
713 old = NULL;
+
714 }
+
715
+
716 ic->next = next[0];
+
717 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
718 if (!fs)
+
719 goto out_iconv_close_from;
+
720
+
721 return fs;
+
722
+
723out_iconv_close_from:
+
724 iconv_close(ic->fromfs);
+
725out_iconv_close_to:
+
726 iconv_close(ic->tofs);
+
727out_free:
+
728 free(ic->from_code);
+
729 free(ic->to_code);
+
730 free(ic);
+
731 if (old) {
+
732 setlocale(LC_CTYPE, old);
+
733 }
+
734 return NULL;
+
735}
+
736
+
737FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1415
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2modules_2subdir_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..b02ee04 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2modules_2subdir_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556static void *subdir_init(struct fuse_conn_info *conn,
+
557 struct fuse_config *cfg)
+
558{
+
559 struct subdir *d = subdir_get();
+
560 fuse_fs_init(d->next, conn, cfg);
+
561 /* Don't touch cfg->nullpath_ok, we can work with
+
562 either */
+
563 return d;
+
564}
+
565
+
566static void subdir_destroy(void *data)
+
567{
+
568 struct subdir *d = data;
+
569 fuse_fs_destroy(d->next);
+
570 free(d->base);
+
571 free(d);
+
572}
+
573
+
574static const struct fuse_operations subdir_oper = {
+
575 .destroy = subdir_destroy,
+
576 .init = subdir_init,
+
577 .getattr = subdir_getattr,
+
578 .access = subdir_access,
+
579 .readlink = subdir_readlink,
+
580 .opendir = subdir_opendir,
+
581 .readdir = subdir_readdir,
+
582 .releasedir = subdir_releasedir,
+
583 .mknod = subdir_mknod,
+
584 .mkdir = subdir_mkdir,
+
585 .symlink = subdir_symlink,
+
586 .unlink = subdir_unlink,
+
587 .rmdir = subdir_rmdir,
+
588 .rename = subdir_rename,
+
589 .link = subdir_link,
+
590 .chmod = subdir_chmod,
+
591 .chown = subdir_chown,
+
592 .truncate = subdir_truncate,
+
593 .utimens = subdir_utimens,
+
594 .create = subdir_create,
+
595 .open = subdir_open,
+
596 .read_buf = subdir_read_buf,
+
597 .write_buf = subdir_write_buf,
+
598 .statfs = subdir_statfs,
+
599 .flush = subdir_flush,
+
600 .release = subdir_release,
+
601 .fsync = subdir_fsync,
+
602 .fsyncdir = subdir_fsyncdir,
+
603 .setxattr = subdir_setxattr,
+
604 .getxattr = subdir_getxattr,
+
605 .listxattr = subdir_listxattr,
+
606 .removexattr = subdir_removexattr,
+
607 .lock = subdir_lock,
+
608 .flock = subdir_flock,
+
609 .bmap = subdir_bmap,
+
610 .lseek = subdir_lseek,
+
611};
+
612
+
613static const struct fuse_opt subdir_opts[] = {
+
614 FUSE_OPT_KEY("-h", 0),
+
615 FUSE_OPT_KEY("--help", 0),
+
616 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
617 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
618 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
620};
+
621
+
622static void subdir_help(void)
+
623{
+
624 printf(
+
625" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
626" -o [no]rellinks transform absolute symlinks to relative\n");
+
627}
+
628
+
629static int subdir_opt_proc(void *data, const char *arg, int key,
+
630 struct fuse_args *outargs)
+
631{
+
632 (void) data; (void) arg; (void) outargs;
+
633
+
634 if (!key) {
+
635 subdir_help();
+
636 return -1;
+
637 }
+
638
+
639 return 1;
+
640}
+
641
+
642static struct fuse_fs *subdir_new(struct fuse_args *args,
+
643 struct fuse_fs *next[])
+
644{
+
645 struct fuse_fs *fs;
+
646 struct subdir *d;
+
647
+
648 d = calloc(1, sizeof(struct subdir));
+
649 if (d == NULL) {
+
650 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
651 return NULL;
+
652 }
+
653
+
654 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
655 goto out_free;
+
656
+
657 if (!next[0] || next[1]) {
+
658 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
659 goto out_free;
+
660 }
+
661
+
662 if (!d->base) {
+
663 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
664 goto out_free;
+
665 }
+
666
+
667 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
668 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
669 if (!tmp) {
+
670 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
671 goto out_free;
+
672 }
+
673 d->base = tmp;
+
674 strcat(d->base, "/");
+
675 }
+
676 d->baselen = strlen(d->base);
+
677 d->next = next[0];
+
678 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
679 if (!fs)
+
680 goto out_free;
+
681 return fs;
+
682
+
683out_free:
+
684 free(d->base);
+
685 free(d);
+
686 return NULL;
+
687}
+
688
+
689FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4854
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1415
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2mount_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2mount_8c_source.html new file mode 100644 index 0000000..f0416d6 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2mount_8c_source.html @@ -0,0 +1,791 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52
+
53#ifndef MS_DIRSYNC
+
54#define MS_DIRSYNC 128
+
55#endif
+
56
+
57enum {
+
58 KEY_KERN_FLAG,
+
59 KEY_KERN_OPT,
+
60 KEY_FUSERMOUNT_OPT,
+
61 KEY_SUBTYPE_OPT,
+
62 KEY_MTAB_OPT,
+
63 KEY_ALLOW_OTHER,
+
64 KEY_RO,
+
65};
+
66
+
67struct mount_opts {
+
68 int allow_other;
+
69 int flags;
+
70 int auto_unmount;
+
71 int blkdev;
+
72 char *fsname;
+
73 char *subtype;
+
74 char *subtype_opt;
+
75 char *mtab_opts;
+
76 char *fusermount_opts;
+
77 char *kernel_opts;
+
78 unsigned max_read;
+
79};
+
80
+
81#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
82
+
83static const struct fuse_opt fuse_mount_opts[] = {
+
84 FUSE_MOUNT_OPT("allow_other", allow_other),
+
85 FUSE_MOUNT_OPT("blkdev", blkdev),
+
86 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
87 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
88 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
89 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
90 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
91 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
92 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
95 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
96 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
103 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-r", KEY_RO),
+
105 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
106 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
122};
+
123
+
124/*
+
125 * Running fusermount by calling 'posix_spawn'
+
126 *
+
127 * @param out_pid might be NULL
+
128 */
+
129static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
130 char const * const argv[], pid_t *out_pid)
+
131{
+
132 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
133 pid_t pid;
+
134
+
135 /* See man 7 environ for the global environ pointer */
+
136
+
137 /* first try the install path */
+
138 int status = posix_spawn(&pid, full_path, action, NULL,
+
139 (char * const *) argv, environ);
+
140 if (status != 0) {
+
141 /* if that fails, try a system install */
+
142 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
143 (char * const *) argv, environ);
+
144 }
+
145
+
146 if (status != 0) {
+
147 fuse_log(FUSE_LOG_ERR,
+
148 "On calling fusermount posix_spawn failed: %s\n",
+
149 strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0);
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawaning %s to unumount failed",
+
345 FUSERMOUNT_PROG);
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addclose(&action, 1);
+
394 posix_spawn_file_actions_addclose(&action, 2);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed");
+
410 return -1;
+
411 }
+
412 // passed to child now, so can close here.
+
413 close(fds[0]);
+
414
+
415 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
416 // process exits.
+
417 return 0;
+
418 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
419}
+
420
+
421static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
422 const char *opts, int quiet)
+
423{
+
424 int fds[2];
+
425 pid_t pid;
+
426 int res;
+
427
+
428 if (!mountpoint) {
+
429 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
430 return -1;
+
431 }
+
432
+
433 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
434 if(res == -1) {
+
435 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
436 FUSERMOUNT_PROG, strerror(errno));
+
437 return -1;
+
438 }
+
439
+
440 char arg_fd_entry[30];
+
441 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
442 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
443 /*
+
444 * This helps to identify the FD hold by parent process.
+
445 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
446 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
447 * One potential use case is to satisfy FD-Leak checks.
+
448 */
+
449 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
450 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
451
+
452 char const *const argv[] = {
+
453 FUSERMOUNT_PROG,
+
454 "-o", opts ? opts : "",
+
455 "--",
+
456 mountpoint,
+
457 NULL,
+
458 };
+
459
+
460
+
461 posix_spawn_file_actions_t action;
+
462 posix_spawn_file_actions_init(&action);
+
463
+
464 if (quiet) {
+
465 posix_spawn_file_actions_addclose(&action, 1);
+
466 posix_spawn_file_actions_addclose(&action, 2);
+
467 }
+
468 posix_spawn_file_actions_addclose(&action, fds[1]);
+
469
+
470 int status = fusermount_posix_spawn(&action, argv, &pid);
+
471
+
472 posix_spawn_file_actions_destroy(&action);
+
473
+
474 if(status != 0) {
+
475 close(fds[0]);
+
476 close(fds[1]);
+
477 fuse_log(FUSE_LOG_ERR, "posix_spawnp() for %s failed",
+
478 FUSERMOUNT_PROG, strerror(errno));
+
479 return -1;
+
480 }
+
481
+
482 // passed to child now, so can close here.
+
483 close(fds[0]);
+
484
+
485 int fd = receive_fd(fds[1]);
+
486
+
487 if (!mo->auto_unmount) {
+
488 /* with auto_unmount option fusermount3 will not exit until
+
489 this socket is closed */
+
490 close(fds[1]);
+
491 waitpid(pid, NULL, 0); /* bury zombie */
+
492 }
+
493
+
494 if (fd >= 0)
+
495 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
496
+
497 return fd;
+
498}
+
499
+
500#ifndef O_CLOEXEC
+
501#define O_CLOEXEC 0
+
502#endif
+
503
+
504static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
505 const char *mnt_opts)
+
506{
+
507 char tmp[128];
+
508 const char *devname = "/dev/fuse";
+
509 char *source = NULL;
+
510 char *type = NULL;
+
511 struct stat stbuf;
+
512 int fd;
+
513 int res;
+
514
+
515 if (!mnt) {
+
516 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
517 return -1;
+
518 }
+
519
+
520 res = stat(mnt, &stbuf);
+
521 if (res == -1) {
+
522 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
523 mnt, strerror(errno));
+
524 return -1;
+
525 }
+
526
+
527 fd = open(devname, O_RDWR | O_CLOEXEC);
+
528 if (fd == -1) {
+
529 if (errno == ENODEV || errno == ENOENT)
+
530 fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n");
+
531 else
+
532 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
533 devname, strerror(errno));
+
534 return -1;
+
535 }
+
536 if (!O_CLOEXEC)
+
537 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
538
+
539 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
540 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
541
+
542 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
543 if (res == -1)
+
544 goto out_close;
+
545
+
546 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
547 (mo->subtype ? strlen(mo->subtype) : 0) +
+
548 strlen(devname) + 32);
+
549
+
550 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
551 if (!type || !source) {
+
552 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
553 goto out_close;
+
554 }
+
555
+
556 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
557 if (mo->subtype) {
+
558 strcat(type, ".");
+
559 strcat(type, mo->subtype);
+
560 }
+
561 strcpy(source,
+
562 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
563
+
564 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
565 if (res == -1 && errno == ENODEV && mo->subtype) {
+
566 /* Probably missing subtype support */
+
567 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
568 if (mo->fsname) {
+
569 if (!mo->blkdev)
+
570 sprintf(source, "%s#%s", mo->subtype,
+
571 mo->fsname);
+
572 } else {
+
573 strcpy(source, type);
+
574 }
+
575 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
576 }
+
577 if (res == -1) {
+
578 /*
+
579 * Maybe kernel doesn't support unprivileged mounts, in this
+
580 * case try falling back to fusermount3
+
581 */
+
582 if (errno == EPERM) {
+
583 res = -2;
+
584 } else {
+
585 int errno_save = errno;
+
586 if (mo->blkdev && errno == ENODEV &&
+
587 !fuse_mnt_check_fuseblk())
+
588 fuse_log(FUSE_LOG_ERR,
+
589 "fuse: 'fuseblk' support missing\n");
+
590 else
+
591 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
592 strerror(errno_save));
+
593 }
+
594
+
595 goto out_close;
+
596 }
+
597
+
598#ifndef IGNORE_MTAB
+
599 if (geteuid() == 0) {
+
600 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
601 res = -1;
+
602 if (!newmnt)
+
603 goto out_umount;
+
604
+
605 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
606 mnt_opts);
+
607 free(newmnt);
+
608 if (res == -1)
+
609 goto out_umount;
+
610 }
+
611#endif /* IGNORE_MTAB */
+
612 free(type);
+
613 free(source);
+
614
+
615 return fd;
+
616
+
617out_umount:
+
618 umount2(mnt, 2); /* lazy umount */
+
619out_close:
+
620 free(type);
+
621 free(source);
+
622 close(fd);
+
623 return res;
+
624}
+
625
+
626static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
627{
+
628 int i;
+
629
+
630 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
631 return -1;
+
632
+
633 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
634 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
635 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
636 return -1;
+
637 }
+
638 return 0;
+
639}
+
640
+
641struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
642{
+
643 struct mount_opts *mo;
+
644
+
645 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
646 if (mo == NULL)
+
647 return NULL;
+
648
+
649 memset(mo, 0, sizeof(struct mount_opts));
+
650 mo->flags = MS_NOSUID | MS_NODEV;
+
651
+
652 if (args &&
+
653 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
654 goto err_out;
+
655
+
656 return mo;
+
657
+
658err_out:
+
659 destroy_mount_opts(mo);
+
660 return NULL;
+
661}
+
662
+
663void destroy_mount_opts(struct mount_opts *mo)
+
664{
+
665 free(mo->fsname);
+
666 free(mo->subtype);
+
667 free(mo->fusermount_opts);
+
668 free(mo->subtype_opt);
+
669 free(mo->kernel_opts);
+
670 free(mo->mtab_opts);
+
671 free(mo);
+
672}
+
673
+
674
+
675int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
676{
+
677 int res = -1;
+
678 char *mnt_opts = NULL;
+
679
+
680 res = -1;
+
681 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
682 goto out;
+
683 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
684 goto out;
+
685 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
686 goto out;
+
687
+
688 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
689 if (res >= 0 && mo->auto_unmount) {
+
690 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
691 // Something went wrong, let's umount like in fuse_mount_sys.
+
692 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
693 res = -1;
+
694 }
+
695 } else if (res == -2) {
+
696 if (mo->fusermount_opts &&
+
697 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
698 goto out;
+
699
+
700 if (mo->subtype) {
+
701 char *tmp_opts = NULL;
+
702
+
703 res = -1;
+
704 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
705 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
706 free(tmp_opts);
+
707 goto out;
+
708 }
+
709
+
710 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
711 free(tmp_opts);
+
712 if (res == -1)
+
713 res = fuse_mount_fusermount(mountpoint, mo,
+
714 mnt_opts, 0);
+
715 } else {
+
716 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
717 }
+
718 }
+
719out:
+
720 free(mnt_opts);
+
721 return res;
+
722}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2mount__bsd_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..bacfb9a --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2mount__bsd_8c_source.html @@ -0,0 +1,333 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15#include "util.h"
+
16
+
17#include <sys/param.h>
+
18#include "fuse_mount_compat.h"
+
19
+
20#include <sys/wait.h>
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <unistd.h>
+
24#include <stddef.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <string.h>
+
28
+
29#define FUSERMOUNT_PROG "mount_fusefs"
+
30#define FUSE_DEV_TRUNK "/dev/fuse"
+
31
+
32enum {
+
33 KEY_RO,
+
34 KEY_KERN
+
35};
+
36
+
37struct mount_opts {
+
38 int allow_other;
+
39 char *kernel_opts;
+
40 unsigned max_read;
+
41};
+
42
+
43#define FUSE_DUAL_OPT_KEY(templ, key) \
+
44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
45
+
46static const struct fuse_opt fuse_mount_opts[] = {
+
47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
49 FUSE_OPT_KEY("-r", KEY_RO),
+
50 /* standard FreeBSD mount options */
+
51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
75 /* options supported under both Linux and FBSD */
+
76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
78 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
79 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
80 /* FBSD FUSE specific mount options */
+
81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
84#if __FreeBSD_version >= 1200519
+
85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
86#endif
+
87 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
88 /*
+
89 * Linux specific mount options, but let just the mount util
+
90 * handle them
+
91 */
+
92 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
94};
+
95
+
96void fuse_mount_version(void)
+
97{
+
98 system(FUSERMOUNT_PROG " --version");
+
99}
+
100
+
101unsigned get_max_read(struct mount_opts *o)
+
102{
+
103 return o->max_read;
+
104}
+
105
+
106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
107 struct fuse_args *outargs)
+
108{
+
109 (void) outargs;
+
110 struct mount_opts *mo = data;
+
111
+
112 switch (key) {
+
113 case KEY_RO:
+
114 arg = "ro";
+
115 /* fall through */
+
116
+
117 case KEY_KERN:
+
118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
119 }
+
120
+
121 /* Pass through unknown options */
+
122 return 1;
+
123}
+
124
+
125void fuse_kern_unmount(const char *mountpoint, int fd)
+
126{
+
127 if (close(fd) < 0)
+
128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
+
129 if (unmount(mountpoint, MNT_FORCE) < 0)
+
130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
+
131 mountpoint, strerror(errno));
+
132}
+
133
+
134static int fuse_mount_core(const char *mountpoint, const char *opts)
+
135{
+
136 const char *mountprog = FUSERMOUNT_PROG;
+
137 long fd;
+
138 char *fdnam, *dev;
+
139 pid_t pid, cpid;
+
140 int status;
+
141 int err;
+
142
+
143 fdnam = getenv("FUSE_DEV_FD");
+
144
+
145 if (fdnam) {
+
146 err = libfuse_strtol(fdnam, &fd);
+
147 if (err || fd < 0) {
+
148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
149 return -1;
+
150 }
+
151
+
152 goto mount;
+
153 }
+
154
+
155 dev = getenv("FUSE_DEV_NAME");
+
156
+
157 if (! dev)
+
158 dev = (char *)FUSE_DEV_TRUNK;
+
159
+
160 if ((fd = open(dev, O_RDWR)) < 0) {
+
161 perror("fuse: failed to open fuse device");
+
162 return -1;
+
163 }
+
164
+
165mount:
+
166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
167 goto out;
+
168
+
169 pid = fork();
+
170 cpid = pid;
+
171
+
172 if (pid == -1) {
+
173 perror("fuse: fork() failed");
+
174 close(fd);
+
175 return -1;
+
176 }
+
177
+
178 if (pid == 0) {
+
179 pid = fork();
+
180
+
181 if (pid == -1) {
+
182 perror("fuse: fork() failed");
+
183 close(fd);
+
184 _exit(EXIT_FAILURE);
+
185 }
+
186
+
187 if (pid == 0) {
+
188 const char *argv[32];
+
189 int a = 0;
+
190 int ret = -1;
+
191
+
192 if (! fdnam)
+
193 {
+
194 ret = asprintf(&fdnam, "%ld", fd);
+
195 if(ret == -1)
+
196 {
+
197 perror("fuse: failed to assemble mount arguments");
+
198 close(fd);
+
199 _exit(EXIT_FAILURE);
+
200 }
+
201 }
+
202
+
203 argv[a++] = mountprog;
+
204 if (opts) {
+
205 argv[a++] = "-o";
+
206 argv[a++] = opts;
+
207 }
+
208 argv[a++] = fdnam;
+
209 argv[a++] = mountpoint;
+
210 argv[a++] = NULL;
+
211 execvp(mountprog, (char **) argv);
+
212 perror("fuse: failed to exec mount program");
+
213 free(fdnam);
+
214 _exit(EXIT_FAILURE);
+
215 }
+
216
+
217 _exit(EXIT_SUCCESS);
+
218 }
+
219
+
220 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
221 perror("fuse: failed to mount file system");
+
222 if (close(fd) < 0)
+
223 perror("fuse: closing FD");
+
224 return -1;
+
225 }
+
226
+
227out:
+
228 return fd;
+
229}
+
230
+
231struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
232{
+
233 struct mount_opts *mo;
+
234
+
235 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
236 if (mo == NULL)
+
237 return NULL;
+
238
+
239 memset(mo, 0, sizeof(struct mount_opts));
+
240
+
241 if (args &&
+
242 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
243 goto err_out;
+
244
+
245 return mo;
+
246
+
247err_out:
+
248 destroy_mount_opts(mo);
+
249 return NULL;
+
250}
+
251
+
252void destroy_mount_opts(struct mount_opts *mo)
+
253{
+
254 free(mo->kernel_opts);
+
255 free(mo);
+
256}
+
257
+
258int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
259{
+
260 /* mount util should not try to spawn the daemon */
+
261 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
262 /* to notify the mount util it's called from lib */
+
263 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
264
+
265 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
266}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8c_source.html new file mode 100644 index 0000000..33444eb --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8c_source.html @@ -0,0 +1,433 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78 }
+
79
+
80 return 1;
+
81}
+
82#endif /* IGNORE_MTAB */
+
83
+
84static int add_mount(const char *progname, const char *fsname,
+
85 const char *mnt, const char *type, const char *opts)
+
86{
+
87 int res;
+
88 int status;
+
89 sigset_t blockmask;
+
90 sigset_t oldmask;
+
91
+
92 sigemptyset(&blockmask);
+
93 sigaddset(&blockmask, SIGCHLD);
+
94 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
95 if (res == -1) {
+
96 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
97 return -1;
+
98 }
+
99
+
100 res = fork();
+
101 if (res == -1) {
+
102 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
103 goto out_restore;
+
104 }
+
105 if (res == 0) {
+
106 char *env = NULL;
+
107
+
108 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
109
+
110 if(setuid(geteuid()) == -1) {
+
111 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
112 res = -1;
+
113 goto out_restore;
+
114 }
+
115
+
116 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
117 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
118 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
119 progname, strerror(errno));
+
120 exit(1);
+
121 }
+
122 res = waitpid(res, &status, 0);
+
123 if (res == -1)
+
124 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
125
+
126 if (status != 0)
+
127 res = -1;
+
128
+
129 out_restore:
+
130 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
131
+
132 return res;
+
133}
+
134
+
135int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
136 const char *mnt, const char *type, const char *opts)
+
137{
+
138 if (!mtab_needs_update(mnt))
+
139 return 0;
+
140
+
141 return add_mount(progname, fsname, mnt, type, opts);
+
142}
+
143
+
144static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
145{
+
146 int res;
+
147 int status;
+
148 sigset_t blockmask;
+
149 sigset_t oldmask;
+
150
+
151 sigemptyset(&blockmask);
+
152 sigaddset(&blockmask, SIGCHLD);
+
153 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
154 if (res == -1) {
+
155 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
156 return -1;
+
157 }
+
158
+
159 res = fork();
+
160 if (res == -1) {
+
161 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
162 goto out_restore;
+
163 }
+
164 if (res == 0) {
+
165 char *env = NULL;
+
166
+
167 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
168
+
169 if(setuid(geteuid()) == -1) {
+
170 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
171 res = -1;
+
172 goto out_restore;
+
173 }
+
174
+
175 if (lazy) {
+
176 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
177 "-l", NULL, &env);
+
178 } else {
+
179 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
180 NULL, &env);
+
181 }
+
182 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
183 progname, strerror(errno));
+
184 exit(1);
+
185 }
+
186 res = waitpid(res, &status, 0);
+
187 if (res == -1)
+
188 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
189
+
190 if (status != 0) {
+
191 res = -1;
+
192 }
+
193
+
194 out_restore:
+
195 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
196 return res;
+
197
+
198}
+
199
+
200int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
201 const char *rel_mnt, int lazy)
+
202{
+
203 int res;
+
204
+
205 if (!mtab_needs_update(abs_mnt)) {
+
206 res = umount2(rel_mnt, lazy ? 2 : 0);
+
207 if (res == -1)
+
208 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
209 progname, abs_mnt, strerror(errno));
+
210 return res;
+
211 }
+
212
+
213 return exec_umount(progname, rel_mnt, lazy);
+
214}
+
215
+
216static int remove_mount(const char *progname, const char *mnt)
+
217{
+
218 int res;
+
219 int status;
+
220 sigset_t blockmask;
+
221 sigset_t oldmask;
+
222
+
223 sigemptyset(&blockmask);
+
224 sigaddset(&blockmask, SIGCHLD);
+
225 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
226 if (res == -1) {
+
227 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
228 return -1;
+
229 }
+
230
+
231 res = fork();
+
232 if (res == -1) {
+
233 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
234 goto out_restore;
+
235 }
+
236 if (res == 0) {
+
237 char *env = NULL;
+
238
+
239 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
240
+
241 if(setuid(geteuid()) == -1) {
+
242 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
243 res = -1;
+
244 goto out_restore;
+
245 }
+
246
+
247 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
248 "--fake", mnt, NULL, &env);
+
249 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
250 progname, strerror(errno));
+
251 exit(1);
+
252 }
+
253 res = waitpid(res, &status, 0);
+
254 if (res == -1)
+
255 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
256
+
257 if (status != 0)
+
258 res = -1;
+
259
+
260 out_restore:
+
261 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
262 return res;
+
263}
+
264
+
265int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
266{
+
267 if (!mtab_needs_update(mnt))
+
268 return 0;
+
269
+
270 return remove_mount(progname, mnt);
+
271}
+
272
+
273char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
274{
+
275 char buf[PATH_MAX];
+
276 char *copy;
+
277 char *dst;
+
278 char *end;
+
279 char *lastcomp;
+
280 const char *toresolv;
+
281
+
282 if (!orig[0]) {
+
283 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
284 orig);
+
285 return NULL;
+
286 }
+
287
+
288 copy = strdup(orig);
+
289 if (copy == NULL) {
+
290 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
291 return NULL;
+
292 }
+
293
+
294 toresolv = copy;
+
295 lastcomp = NULL;
+
296 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
297 if (end[0] != '/') {
+
298 char *tmp;
+
299 end[1] = '\0';
+
300 tmp = strrchr(copy, '/');
+
301 if (tmp == NULL) {
+
302 lastcomp = copy;
+
303 toresolv = ".";
+
304 } else {
+
305 lastcomp = tmp + 1;
+
306 if (tmp == copy)
+
307 toresolv = "/";
+
308 }
+
309 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
310 lastcomp = NULL;
+
311 toresolv = copy;
+
312 }
+
313 else if (tmp)
+
314 tmp[0] = '\0';
+
315 }
+
316 if (realpath(toresolv, buf) == NULL) {
+
317 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
318 strerror(errno));
+
319 free(copy);
+
320 return NULL;
+
321 }
+
322 if (lastcomp == NULL)
+
323 dst = strdup(buf);
+
324 else {
+
325 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
326 if (dst) {
+
327 unsigned buflen = strlen(buf);
+
328 if (buflen && buf[buflen-1] == '/')
+
329 sprintf(dst, "%s%s", buf, lastcomp);
+
330 else
+
331 sprintf(dst, "%s/%s", buf, lastcomp);
+
332 }
+
333 }
+
334 free(copy);
+
335 if (dst == NULL)
+
336 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
337 return dst;
+
338}
+
339
+
340int fuse_mnt_check_fuseblk(void)
+
341{
+
342 char buf[256];
+
343 FILE *f = fopen("/proc/filesystems", "r");
+
344 if (!f)
+
345 return 1;
+
346
+
347 while (fgets(buf, sizeof(buf), f))
+
348 if (strstr(buf, "fuseblk\n")) {
+
349 fclose(f);
+
350 return 1;
+
351 }
+
352
+
353 fclose(f);
+
354 return 0;
+
355}
+
356
+
357int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
358{
+
359 int fd = -1;
+
360 int len = 0;
+
361
+
362 if (mountpoint == NULL) {
+
363 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
364 return -1;
+
365 }
+
366
+
367 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
368 len == strlen(mountpoint)) {
+
369 return fd;
+
370 }
+
371
+
372 return -1;
+
373}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8h_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8h_source.html new file mode 100644 index 0000000..0270456 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2util_8c_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2util_8c_source.html new file mode 100644 index 0000000..8b91672 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2util_8c_source.html @@ -0,0 +1,87 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1#include <stdlib.h>
+
2#include <errno.h>
+
3
+
4#include "util.h"
+
5
+
6int libfuse_strtol(const char *str, long *res)
+
7{
+
8 char *endptr;
+
9 int base = 10;
+
10 long val;
+
11
+
12 errno = 0;
+
13
+
14 if (!str)
+
15 return -EINVAL;
+
16
+
17 val = strtol(str, &endptr, base);
+
18
+
19 if (errno)
+
20 return -errno;
+
21
+
22 if (endptr == str || *endptr != '\0')
+
23 return -EINVAL;
+
24
+
25 *res = val;
+
26 return 0;
+
27}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2lib_2util_8h_source.html b/doc/html/fuse-3_817_81-rc1_2lib_2util_8h_source.html new file mode 100644 index 0000000..f77a9b7 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2lib_2util_8h_source.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
2
+
3int libfuse_strtol(const char *str, long *res);
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2test_2readdir__inode_8c_source.html b/doc/html/fuse-3_817_81-rc1_2test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..dd41603 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test/readdir_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
readdir_inode.c
+
+
+
1/*
+
2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
+
3 * Skips '.' and '..' because readdir is not required to return them and
+
4 * some of our examples don't. However if they are returned, their d_type
+
5 * should be valid.
+
6 */
+
7
+
8#include <stdio.h>
+
9#include <string.h>
+
10#include <sys/types.h>
+
11#include <dirent.h>
+
12#include <errno.h>
+
13
+
14int main(int argc, char* argv[])
+
15{
+
16 DIR* dirp;
+
17 struct dirent* dent;
+
18
+
19 if (argc != 2) {
+
20 fprintf(stderr, "Usage: readdir_inode dir\n");
+
21 return 1;
+
22 }
+
23
+
24 dirp = opendir(argv[1]);
+
25 if (dirp == NULL) {
+
26 perror("failed to open directory");
+
27 return 2;
+
28 }
+
29
+
30 errno = 0;
+
31 dent = readdir(dirp);
+
32 while (dent != NULL) {
+
33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
+
34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
+
35 (int)dent->d_type, dent->d_name);
+
36 if ((long long)dent->d_ino < 0)
+
37 fprintf(stderr,"%s : bad d_ino %llu\n",
+
38 dent->d_name, (unsigned long long)dent->d_ino);
+
39 if ((dent->d_type < 1) || (dent->d_type > 15))
+
40 fprintf(stderr,"%s : bad d_type %d\n",
+
41 dent->d_name, (int)dent->d_type);
+
42 } else {
+
43 if (dent->d_type != DT_DIR)
+
44 fprintf(stderr,"%s : bad d_type %d\n",
+
45 dent->d_name, (int)dent->d_type);
+
46 }
+
47 dent = readdir(dirp);
+
48 }
+
49 if (errno != 0) {
+
50 perror("failed to read directory entry");
+
51 return 3;
+
52 }
+
53
+
54 closedir(dirp);
+
55
+
56 return 0;
+
57}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2test_2release__unlink__race_8c_source.html b/doc/html/fuse-3_817_81-rc1_2test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..b66a6b9 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test/release_unlink_race.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
release_unlink_race.c
+
+
+
1/*
+
2 This program can be distributed under the terms of the GNU GPLv2.
+
3 See the file COPYING.
+
4*/
+
5
+
6#define FUSE_USE_VERSION 31
+
7
+
8#define _GNU_SOURCE
+
9
+
10#include <fuse.h>
+
11
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16
+
17static void *xmp_init(struct fuse_conn_info *conn,
+
18 struct fuse_config *cfg)
+
19{
+
20 (void) conn;
+
21
+
22 cfg->use_ino = 1;
+
23 cfg->nullpath_ok = 1;
+
24 cfg->entry_timeout = 0;
+
25 cfg->attr_timeout = 0;
+
26 cfg->negative_timeout = 0;
+
27
+
28 return NULL;
+
29}
+
30
+
31static int xmp_getattr(const char *path, struct stat *stbuf,
+
32 struct fuse_file_info *fi)
+
33{
+
34 int res;
+
35
+
36 (void) path;
+
37
+
38 if(fi)
+
39 res = fstat(fi->fh, stbuf);
+
40 else
+
41 res = lstat(path, stbuf);
+
42 if (res == -1)
+
43 return -errno;
+
44
+
45 return 0;
+
46}
+
47
+
48static int xmp_unlink(const char *path)
+
49{
+
50 int res;
+
51
+
52 res = unlink(path);
+
53 if (res == -1)
+
54 return -errno;
+
55
+
56 return 0;
+
57}
+
58
+
59static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
60{
+
61 int res;
+
62
+
63 if (flags)
+
64 return -EINVAL;
+
65
+
66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
67
+
68 res = rename(from, to);
+
69 if (res == -1)
+
70 return -errno;
+
71
+
72 return 0;
+
73}
+
74
+
75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
76{
+
77 int fd;
+
78
+
79 fd = open(path, fi->flags, mode);
+
80 if (fd == -1)
+
81 return -errno;
+
82
+
83 fi->fh = fd;
+
84 return 0;
+
85}
+
86
+
87static int xmp_release(const char *path, struct fuse_file_info *fi)
+
88{
+
89 (void) path;
+
90
+
91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
92
+
93 close(fi->fh);
+
94
+
95 return 0;
+
96}
+
97
+
98static const struct fuse_operations xmp_oper = {
+
99 .init = xmp_init,
+
100 .getattr = xmp_getattr,
+
101 .unlink = xmp_unlink,
+
102 .rename = xmp_rename,
+
103 .create = xmp_create,
+
104 .release = xmp_release,
+
105};
+
106
+
107int main(int argc, char *argv[])
+
108{
+
109 umask(0);
+
110 return fuse_main(argc, argv, &xmp_oper, NULL);
+
111}
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2test_2stracedecode_8c_source.html b/doc/html/fuse-3_817_81-rc1_2test_2stracedecode_8c_source.html new file mode 100644 index 0000000..1586c75 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test/stracedecode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
stracedecode.c
+
+
+
1#include <stdio.h>
+
2#include <string.h>
+
3#include "fuse_kernel.h"
+
4
+
5static struct {
+
6 const char *name;
+
7} fuse_ll_ops[] = {
+
8 [FUSE_LOOKUP] = { "LOOKUP" },
+
9 [FUSE_FORGET] = { "FORGET" },
+
10 [FUSE_GETATTR] = { "GETATTR" },
+
11 [FUSE_SETATTR] = { "SETATTR" },
+
12 [FUSE_READLINK] = { "READLINK" },
+
13 [FUSE_SYMLINK] = { "SYMLINK" },
+
14 [FUSE_MKNOD] = { "MKNOD" },
+
15 [FUSE_MKDIR] = { "MKDIR" },
+
16 [FUSE_UNLINK] = { "UNLINK" },
+
17 [FUSE_RMDIR] = { "RMDIR" },
+
18 [FUSE_RENAME] = { "RENAME" },
+
19 [FUSE_LINK] = { "LINK" },
+
20 [FUSE_OPEN] = { "OPEN" },
+
21 [FUSE_READ] = { "READ" },
+
22 [FUSE_WRITE] = { "WRITE" },
+
23 [FUSE_STATFS] = { "STATFS" },
+
24 [FUSE_RELEASE] = { "RELEASE" },
+
25 [FUSE_FSYNC] = { "FSYNC" },
+
26 [FUSE_SETXATTR] = { "SETXATTR" },
+
27 [FUSE_GETXATTR] = { "GETXATTR" },
+
28 [FUSE_LISTXATTR] = { "LISTXATTR" },
+
29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
+
30 [FUSE_FLUSH] = { "FLUSH" },
+
31 [FUSE_INIT] = { "INIT" },
+
32 [FUSE_OPENDIR] = { "OPENDIR" },
+
33 [FUSE_READDIR] = { "READDIR" },
+
34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
+
35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
+
36 [FUSE_GETLK] = { "GETLK" },
+
37 [FUSE_SETLK] = { "SETLK" },
+
38 [FUSE_SETLKW] = { "SETLKW" },
+
39 [FUSE_ACCESS] = { "ACCESS" },
+
40 [FUSE_CREATE] = { "CREATE" },
+
41 [FUSE_TMPFILE] = { "TMPFILE" },
+
42 [FUSE_INTERRUPT] = { "INTERRUPT" },
+
43 [FUSE_BMAP] = { "BMAP" },
+
44 [FUSE_DESTROY] = { "DESTROY" },
+
45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
+
46};
+
47
+
48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
49
+
50static const char *opname(enum fuse_opcode opcode)
+
51{
+
52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
53 return "???";
+
54 else
+
55 return fuse_ll_ops[opcode].name;
+
56}
+
57
+
58
+
59static void process_buf(int dir, char *buf, int len)
+
60{
+
61 static unsigned long long prevuniq = -1;
+
62 static int prevopcode;
+
63
+
64 if (!dir) {
+
65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
+
66 buf += sizeof(struct fuse_in_header);
+
67
+
68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
+
69 (unsigned long long) in->unique,
+
70 opname((enum fuse_opcode) in->opcode), in->opcode,
+
71 (unsigned long) in->nodeid, in->len, len);
+
72
+
73 switch (in->opcode) {
+
74 case FUSE_READ: {
+
75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
+
76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
+
77 arg->fh, arg->offset, arg->size, arg->read_flags,
+
78 arg->lock_owner, arg->flags);
+
79 break;
+
80 }
+
81 case FUSE_WRITE: {
+
82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
+
83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
+
84 arg->fh, arg->offset, arg->size, arg->write_flags,
+
85 arg->lock_owner, arg->flags);
+
86 break;
+
87 }
+
88 }
+
89 prevuniq = in->unique;
+
90 prevopcode = in->opcode;
+
91 } else {
+
92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
+
93 buf += sizeof(struct fuse_out_header);
+
94
+
95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
+
96 (unsigned long long) out->unique, out->error,
+
97 strerror(-out->error), out->len, len);
+
98
+
99 if (out->unique == prevuniq) {
+
100 switch (prevopcode) {
+
101 case FUSE_GETATTR: {
+
102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
+
103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
104 arg->attr_valid, arg->attr_valid_nsec,
+
105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
106 break;
+
107 }
+
108 case FUSE_LOOKUP: {
+
109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
+
110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
+
112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
113 break;
+
114 }
+
115 }
+
116 }
+
117 }
+
118
+
119}
+
120
+
121int main(void)
+
122{
+
123 FILE *in = stdin;
+
124 while (1) {
+
125 int dir;
+
126 int res;
+
127 char buf[1048576];
+
128 unsigned len = 0;
+
129
+
130 memset(buf, 0, sizeof(buf));
+
131 while (1) {
+
132 char str[32];
+
133
+
134 res = fscanf(in, "%30s", str);
+
135 if (res != 1 && feof(in))
+
136 return 0;
+
137
+
138 if (res == 0)
+
139 continue;
+
140
+
141 if (strncmp(str, "read(", 5) == 0) {
+
142 dir = 0;
+
143 break;
+
144 } else if (strncmp(str, "writev(", 7) == 0) {
+
145 dir = 1;
+
146 break;
+
147 }
+
148 }
+
149
+
150 while (1) {
+
151 int c = getc(in);
+
152 if (c == '"') {
+
153 while (1) {
+
154 int val;
+
155
+
156 c = getc(in);
+
157 if (c == EOF) {
+
158 fprintf(stderr, "eof in string\n");
+
159 break;
+
160 }
+
161 if (c == '\n') {
+
162 fprintf(stderr, "eol in string\n");
+
163 break;
+
164 }
+
165 if (c == '"')
+
166 break;
+
167 if (c != '\\') {
+
168 val = c;
+
169 } else {
+
170 c = getc(in);
+
171 switch (c) {
+
172 case 'n': val = '\n'; break;
+
173 case 'r': val = '\r'; break;
+
174 case 't': val = '\t'; break;
+
175 case '"': val = '"'; break;
+
176 case '\\': val = '\\'; break;
+
177 case 'x':
+
178 res = scanf("%x", &val);
+
179 if (res != 1) {
+
180 fprintf(stderr, "parse error\n");
+
181 continue;
+
182 }
+
183 break;
+
184 default:
+
185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
+
186 continue;
+
187 }
+
188 }
+
189 buf[len++] = val;
+
190 }
+
191 }
+
192 if (c == '\n')
+
193 break;
+
194 }
+
195 process_buf(dir, buf, len);
+
196 memset(buf, 0, len);
+
197 len = 0;
+
198 }
+
199}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2test_2test__setattr_8c_source.html b/doc/html/fuse-3_817_81-rc1_2test_2test__setattr_8c_source.html new file mode 100644 index 0000000..7c4910c --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test/test_setattr.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_setattr.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <pthread.h>
+
26
+
27#ifndef __linux__
+
28#include <limits.h>
+
29#else
+
30#include <linux/limits.h>
+
31#endif
+
32
+
33#define FILE_INO 2
+
34#define FILE_NAME "truncate_me"
+
35
+
36static int got_fh;
+
37static mode_t file_mode = S_IFREG | 0644;
+
38
+
39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
40 stbuf->st_ino = ino;
+
41 if (ino == FUSE_ROOT_ID) {
+
42 stbuf->st_mode = S_IFDIR | 0755;
+
43 stbuf->st_nlink = 1;
+
44 }
+
45
+
46 else if (ino == FILE_INO) {
+
47 stbuf->st_mode = file_mode;
+
48 stbuf->st_nlink = 1;
+
49 stbuf->st_size = 0;
+
50 }
+
51
+
52 else
+
53 return -1;
+
54
+
55 return 0;
+
56}
+
57
+
58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
59 const char *name) {
+
60 struct fuse_entry_param e;
+
61 memset(&e, 0, sizeof(e));
+
62
+
63 if (parent != FUSE_ROOT_ID)
+
64 goto err_out;
+
65 else if (strcmp(name, FILE_NAME) == 0)
+
66 e.ino = FILE_INO;
+
67 else
+
68 goto err_out;
+
69
+
70 if (tfs_stat(e.ino, &e.attr) != 0)
+
71 goto err_out;
+
72 fuse_reply_entry(req, &e);
+
73 return;
+
74
+
75err_out:
+
76 fuse_reply_err(req, ENOENT);
+
77}
+
78
+
79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
80 struct fuse_file_info *fi) {
+
81 struct stat stbuf;
+
82
+
83 (void) fi;
+
84
+
85 memset(&stbuf, 0, sizeof(stbuf));
+
86 if (tfs_stat(ino, &stbuf) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else
+
89 fuse_reply_attr(req, &stbuf, 5);
+
90}
+
91
+
92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
93 struct fuse_file_info *fi) {
+
94 if (ino == FUSE_ROOT_ID)
+
95 fuse_reply_err(req, EISDIR);
+
96 else {
+
97 assert(ino == FILE_INO);
+
98 fi->fh = FILE_INO;
+
99 fuse_reply_open(req, fi);
+
100 }
+
101}
+
102
+
103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
104 int to_set, struct fuse_file_info *fi) {
+
105 if(ino != FILE_INO ||
+
106 !(to_set & FUSE_SET_ATTR_MODE)) {
+
107 fuse_reply_err(req, EINVAL);
+
108 return;
+
109 }
+
110
+
111 if(fi == NULL)
+
112 fprintf(stderr, "setattr with fi == NULL\n");
+
113 else if (fi->fh != FILE_INO)
+
114 fprintf(stderr, "setattr with wrong fi->fh\n");
+
115 else {
+
116 fprintf(stderr, "setattr ok\n");
+
117 got_fh = 1;
+
118 file_mode = attr->st_mode;
+
119 }
+
120
+
121 tfs_getattr(req, ino, fi);
+
122}
+
123
+
124static struct fuse_lowlevel_ops tfs_oper = {
+
125 .lookup = tfs_lookup,
+
126 .getattr = tfs_getattr,
+
127 .open = tfs_open,
+
128 .setattr = tfs_setattr,
+
129};
+
130
+
131static void* run_fs(void *data) {
+
132 struct fuse_session *se = (struct fuse_session*) data;
+
133 assert(fuse_session_loop(se) == 0);
+
134 return NULL;
+
135}
+
136
+
137static void test_fs(char *mountpoint) {
+
138 char fname[PATH_MAX];
+
139 int fd;
+
140
+
141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
142 mountpoint) > 0);
+
143 fd = open(fname, O_WRONLY);
+
144 if (fd == -1) {
+
145 perror(fname);
+
146 assert(0);
+
147 }
+
148
+
149 assert(fchmod(fd, 0600) == 0);
+
150 close(fd);
+
151}
+
152
+
153int main(int argc, char *argv[]) {
+
154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
155 struct fuse_session *se;
+
156 struct fuse_cmdline_opts fuse_opts;
+
157 pthread_t fs_thread;
+
158
+
159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
160#ifndef __FreeBSD__
+
161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
162#endif
+
163 se = fuse_session_new(&args, &tfs_oper,
+
164 sizeof(tfs_oper), NULL);
+
165 assert (se != NULL);
+
166 assert(fuse_set_signal_handlers(se) == 0);
+
167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
168
+
169 /* Start file-system thread */
+
170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
171
+
172 /* Do test */
+
173 test_fs(fuse_opts.mountpoint);
+
174
+
175 /* Stop file system */
+
176 assert(pthread_cancel(fs_thread) == 0);
+
177
+ +
179 assert(got_fh == 1);
+ + +
182
+
183 printf("Test completed successfully.\n");
+
184 return 0;
+
185}
+
186
+
187
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
+
fuse_ino_t ino
+ + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2test_2test__syscalls_8c_source.html b/doc/html/fuse-3_817_81-rc1_2test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..7de28c0 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2test_2test__syscalls_8c_source.html @@ -0,0 +1,2258 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test/test_syscalls.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_syscalls.c
+
+
+
1#define _GNU_SOURCE
+
2#include "fuse_config.h"
+
3
+
4#include <stdio.h>
+
5#include <stdlib.h>
+
6#include <stdarg.h>
+
7#include <string.h>
+
8#include <unistd.h>
+
9#include <fcntl.h>
+
10#include <dirent.h>
+
11#include <utime.h>
+
12#include <errno.h>
+
13#include <assert.h>
+
14#include <sys/socket.h>
+
15#include <sys/types.h>
+
16#include <sys/stat.h>
+
17#include <sys/un.h>
+
18
+
19#ifndef ALLPERMS
+
20# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
+
21#endif
+
22
+
23
+
24static const char *basepath;
+
25static const char *basepath_r;
+
26static char testfile[1024];
+
27static char testfile2[1024];
+
28static char testdir[1024];
+
29static char testdir2[1024];
+
30static char testsock[1024];
+
31static char subfile[1280];
+
32
+
33static char testfile_r[1024];
+
34static char testfile2_r[1024];
+
35static char testdir_r[1024];
+
36static char testdir2_r[1024];
+
37static char subfile_r[1280];
+
38
+
39static char testname[256];
+
40static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
+
41static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
+
42static const char *testdir_files[] = { "f1", "f2", NULL};
+
43static long seekdir_offsets[4];
+
44static char zerodata[4096];
+
45static int testdatalen = sizeof(testdata) - 1;
+
46static int testdata2len = sizeof(testdata2) - 1;
+
47static unsigned int testnum = 0;
+
48static unsigned int select_test = 0;
+
49static unsigned int skip_test = 0;
+
50static unsigned int unlinked_test = 0;
+
51
+
52#define MAX_ENTRIES 1024
+
53#define MAX_TESTS 100
+
54
+
55static struct test {
+
56 int fd;
+
57 struct stat stat;
+
58} tests[MAX_TESTS];
+
59
+
60static void test_perror(const char *func, const char *msg)
+
61{
+
62 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
+
63 strerror(errno));
+
64}
+
65
+
66static void test_error(const char *func, const char *msg, ...)
+
67 __attribute__ ((format (printf, 2, 3)));
+
68
+
69static void __start_test(const char *fmt, ...)
+
70 __attribute__ ((format (printf, 1, 2)));
+
71
+
72static void test_error(const char *func, const char *msg, ...)
+
73{
+
74 va_list ap;
+
75 fprintf(stderr, "%s %s() - ", testname, func);
+
76 va_start(ap, msg);
+
77 vfprintf(stderr, msg, ap);
+
78 va_end(ap);
+
79 fprintf(stderr, "\n");
+
80}
+
81
+
82static int is_dot_or_dotdot(const char *name) {
+
83 return name[0] == '.' &&
+
84 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+
85}
+
86
+
87static void success(void)
+
88{
+
89 fprintf(stderr, "%s OK\n", testname);
+
90}
+
91
+
92#define this_test (&tests[testnum-1])
+
93#define next_test (&tests[testnum])
+
94
+
95static void __start_test(const char *fmt, ...)
+
96{
+
97 unsigned int n;
+
98 va_list ap;
+
99 n = sprintf(testname, "%3i [", testnum);
+
100 va_start(ap, fmt);
+
101 n += vsprintf(testname + n, fmt, ap);
+
102 va_end(ap);
+
103 sprintf(testname + n, "]");
+
104 // Use dedicated testfile per test
+
105 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
+
106 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
+
107 if (testnum > MAX_TESTS) {
+
108 fprintf(stderr, "%s - too many tests\n", testname);
+
109 exit(1);
+
110 }
+
111 this_test->fd = -1;
+
112}
+
113
+
114#define start_test(msg, args...) { \
+
115 testnum++; \
+
116 if ((select_test && testnum != select_test) || \
+
117 (testnum == skip_test)) { \
+
118 return 0; \
+
119 } \
+
120 __start_test(msg, ##args); \
+
121}
+
122
+
123#define PERROR(msg) test_perror(__FUNCTION__, msg)
+
124#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
+
125
+
126#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
127
+
128static int st_check_size(struct stat *st, int len)
+
129{
+
130 if (st->st_size != len) {
+
131 ERROR("length %u instead of %u", (int) st->st_size,
+
132 (int) len);
+
133 return -1;
+
134 }
+
135 return 0;
+
136}
+
137
+
138static int check_size(const char *path, int len)
+
139{
+
140 struct stat stbuf;
+
141 int res = stat(path, &stbuf);
+
142 if (res == -1) {
+
143 PERROR("stat");
+
144 return -1;
+
145 }
+
146 return st_check_size(&stbuf, len);
+
147}
+
148
+
149static int check_testfile_size(const char *path, int len)
+
150{
+
151 this_test->stat.st_size = len;
+
152 return check_size(path, len);
+
153}
+
154
+
155static int st_check_type(struct stat *st, mode_t type)
+
156{
+
157 if ((st->st_mode & S_IFMT) != type) {
+
158 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
+
159 return -1;
+
160 }
+
161 return 0;
+
162}
+
163
+
164static int check_type(const char *path, mode_t type)
+
165{
+
166 struct stat stbuf;
+
167 int res = lstat(path, &stbuf);
+
168 if (res == -1) {
+
169 PERROR("lstat");
+
170 return -1;
+
171 }
+
172 return st_check_type(&stbuf, type);
+
173}
+
174
+
175static int st_check_mode(struct stat *st, mode_t mode)
+
176{
+
177 if ((st->st_mode & ALLPERMS) != mode) {
+
178 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
+
179 mode);
+
180 return -1;
+
181 }
+
182 return 0;
+
183}
+
184
+
185static int check_mode(const char *path, mode_t mode)
+
186{
+
187 struct stat stbuf;
+
188 int res = lstat(path, &stbuf);
+
189 if (res == -1) {
+
190 PERROR("lstat");
+
191 return -1;
+
192 }
+
193 return st_check_mode(&stbuf, mode);
+
194}
+
195
+
196static int check_testfile_mode(const char *path, mode_t mode)
+
197{
+
198 this_test->stat.st_mode &= ~ALLPERMS;
+
199 this_test->stat.st_mode |= mode;
+
200 return check_mode(path, mode);
+
201}
+
202
+
203static int check_times(const char *path, time_t atime, time_t mtime)
+
204{
+
205 int err = 0;
+
206 struct stat stbuf;
+
207 int res = lstat(path, &stbuf);
+
208 if (res == -1) {
+
209 PERROR("lstat");
+
210 return -1;
+
211 }
+
212 if (stbuf.st_atime != atime) {
+
213 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
214 err--;
+
215 }
+
216 if (stbuf.st_mtime != mtime) {
+
217 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
218 err--;
+
219 }
+
220 if (err)
+
221 return -1;
+
222
+
223 return 0;
+
224}
+
225
+
226#if 0
+
227static int fcheck_times(int fd, time_t atime, time_t mtime)
+
228{
+
229 int err = 0;
+
230 struct stat stbuf;
+
231 int res = fstat(fd, &stbuf);
+
232 if (res == -1) {
+
233 PERROR("fstat");
+
234 return -1;
+
235 }
+
236 if (stbuf.st_atime != atime) {
+
237 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
238 err--;
+
239 }
+
240 if (stbuf.st_mtime != mtime) {
+
241 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
242 err--;
+
243 }
+
244 if (err)
+
245 return -1;
+
246
+
247 return 0;
+
248}
+
249#endif
+
250
+
251static int st_check_nlink(struct stat *st, nlink_t nlink)
+
252{
+
253 if (st->st_nlink != nlink) {
+
254 ERROR("nlink %li instead of %li", (long) st->st_nlink,
+
255 (long) nlink);
+
256 return -1;
+
257 }
+
258 return 0;
+
259}
+
260
+
261static int check_nlink(const char *path, nlink_t nlink)
+
262{
+
263 struct stat stbuf;
+
264 int res = lstat(path, &stbuf);
+
265 if (res == -1) {
+
266 PERROR("lstat");
+
267 return -1;
+
268 }
+
269 return st_check_nlink(&stbuf, nlink);
+
270}
+
271
+
272static int fcheck_stat(int fd, int flags, struct stat *st)
+
273{
+
274 struct stat stbuf;
+
275 int res = fstat(fd, &stbuf);
+
276 if (res == -1) {
+
277 if (flags & O_PATH) {
+
278 // With O_PATH fd, the server does not have to keep
+
279 // the inode alive so FUSE inode may be stale or bad
+
280 if (errno == ESTALE || errno == EIO ||
+
281 errno == ENOENT || errno == EBADF)
+
282 return 0;
+
283 }
+
284 PERROR("fstat");
+
285 return -1;
+
286 }
+
287
+
288 int err = 0;
+
289 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
+
290 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
+
291 err += st_check_size(&stbuf, st->st_size);
+
292 err += st_check_nlink(&stbuf, st->st_nlink);
+
293
+
294 return err;
+
295}
+
296
+
297static int check_nonexist(const char *path)
+
298{
+
299 struct stat stbuf;
+
300 int res = lstat(path, &stbuf);
+
301 if (res == 0) {
+
302 ERROR("file should not exist");
+
303 return -1;
+
304 }
+
305 if (errno != ENOENT) {
+
306 ERROR("file should not exist: %s", strerror(errno));
+
307 return -1;
+
308 }
+
309 return 0;
+
310}
+
311
+
312static int check_buffer(const char *buf, const char *data, unsigned len)
+
313{
+
314 if (memcmp(buf, data, len) != 0) {
+
315 ERROR("data mismatch");
+
316 return -1;
+
317 }
+
318 return 0;
+
319}
+
320
+
321static int check_data(const char *path, const char *data, int offset,
+
322 unsigned len)
+
323{
+
324 char buf[4096];
+
325 int res;
+
326 int fd = open(path, O_RDONLY);
+
327 if (fd == -1) {
+
328 PERROR("open");
+
329 return -1;
+
330 }
+
331 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
332 PERROR("lseek");
+
333 close(fd);
+
334 return -1;
+
335 }
+
336 while (len) {
+
337 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
338 res = read(fd, buf, rdlen);
+
339 if (res == -1) {
+
340 PERROR("read");
+
341 close(fd);
+
342 return -1;
+
343 }
+
344 if (res != rdlen) {
+
345 ERROR("short read: %u instead of %u", res, rdlen);
+
346 close(fd);
+
347 return -1;
+
348 }
+
349 if (check_buffer(buf, data, rdlen) != 0) {
+
350 close(fd);
+
351 return -1;
+
352 }
+
353 data += rdlen;
+
354 len -= rdlen;
+
355 }
+
356 res = close(fd);
+
357 if (res == -1) {
+
358 PERROR("close");
+
359 return -1;
+
360 }
+
361 return 0;
+
362}
+
363
+
364static int fcheck_data(int fd, const char *data, int offset,
+
365 unsigned len)
+
366{
+
367 char buf[4096];
+
368 int res;
+
369 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
370 PERROR("lseek");
+
371 return -1;
+
372 }
+
373 while (len) {
+
374 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
375 res = read(fd, buf, rdlen);
+
376 if (res == -1) {
+
377 PERROR("read");
+
378 return -1;
+
379 }
+
380 if (res != rdlen) {
+
381 ERROR("short read: %u instead of %u", res, rdlen);
+
382 return -1;
+
383 }
+
384 if (check_buffer(buf, data, rdlen) != 0) {
+
385 return -1;
+
386 }
+
387 data += rdlen;
+
388 len -= rdlen;
+
389 }
+
390 return 0;
+
391}
+
392
+
393static int check_dir_contents(const char *path, const char **contents)
+
394{
+
395 int i;
+
396 int res;
+
397 int err = 0;
+
398 int found[MAX_ENTRIES];
+
399 const char *cont[MAX_ENTRIES];
+
400 DIR *dp;
+
401
+
402 for (i = 0; contents[i]; i++) {
+
403 assert(i < MAX_ENTRIES - 3);
+
404 found[i] = 0;
+
405 cont[i] = contents[i];
+
406 }
+
407 cont[i] = NULL;
+
408
+
409 dp = opendir(path);
+
410 if (dp == NULL) {
+
411 PERROR("opendir");
+
412 return -1;
+
413 }
+
414 memset(found, 0, sizeof(found));
+
415 while(1) {
+
416 struct dirent *de;
+
417 errno = 0;
+
418 de = readdir(dp);
+
419 if (de == NULL) {
+
420 if (errno) {
+
421 PERROR("readdir");
+
422 closedir(dp);
+
423 return -1;
+
424 }
+
425 break;
+
426 }
+
427 if (is_dot_or_dotdot(de->d_name))
+
428 continue;
+
429 for (i = 0; cont[i] != NULL; i++) {
+
430 assert(i < MAX_ENTRIES);
+
431 if (strcmp(cont[i], de->d_name) == 0) {
+
432 if (found[i]) {
+
433 ERROR("duplicate entry <%s>",
+
434 de->d_name);
+
435 err--;
+
436 } else
+
437 found[i] = 1;
+
438 break;
+
439 }
+
440 }
+
441 if (!cont[i]) {
+
442 ERROR("unexpected entry <%s>", de->d_name);
+
443 err --;
+
444 }
+
445 }
+
446 for (i = 0; cont[i] != NULL; i++) {
+
447 if (!found[i]) {
+
448 ERROR("missing entry <%s>", cont[i]);
+
449 err--;
+
450 }
+
451 }
+
452 res = closedir(dp);
+
453 if (res == -1) {
+
454 PERROR("closedir");
+
455 return -1;
+
456 }
+
457 if (err)
+
458 return -1;
+
459
+
460 return 0;
+
461}
+
462
+
463static int create_file(const char *path, const char *data, int len)
+
464{
+
465 int res;
+
466 int fd;
+
467
+
468 unlink(path);
+
469 fd = creat(path, 0644);
+
470 if (fd == -1) {
+
471 PERROR("creat");
+
472 return -1;
+
473 }
+
474 if (len) {
+
475 res = write(fd, data, len);
+
476 if (res == -1) {
+
477 PERROR("write");
+
478 close(fd);
+
479 return -1;
+
480 }
+
481 if (res != len) {
+
482 ERROR("write is short: %u instead of %u", res, len);
+
483 close(fd);
+
484 return -1;
+
485 }
+
486 }
+
487 res = close(fd);
+
488 if (res == -1) {
+
489 PERROR("close");
+
490 return -1;
+
491 }
+
492 res = check_type(path, S_IFREG);
+
493 if (res == -1)
+
494 return -1;
+
495 res = check_mode(path, 0644);
+
496 if (res == -1)
+
497 return -1;
+
498 res = check_nlink(path, 1);
+
499 if (res == -1)
+
500 return -1;
+
501 res = check_size(path, len);
+
502 if (res == -1)
+
503 return -1;
+
504
+
505 if (len) {
+
506 res = check_data(path, data, 0, len);
+
507 if (res == -1)
+
508 return -1;
+
509 }
+
510
+
511 return 0;
+
512}
+
513
+
514static int create_path_fd(const char *path, const char *data, int len)
+
515{
+
516 int path_fd;
+
517 int res;
+
518
+
519 res = create_file(path, data, len);
+
520 if (res == -1)
+
521 return -1;
+
522
+
523 path_fd = open(path, O_PATH);
+
524 if (path_fd == -1)
+
525 PERROR("open(O_PATH)");
+
526
+
527 return path_fd;
+
528}
+
529
+
530// Can be called once per test
+
531static int create_testfile(const char *path, const char *data, int len)
+
532{
+
533 struct test *t = this_test;
+
534 struct stat *st = &t->stat;
+
535 int res, fd;
+
536
+
537 if (t->fd > 0) {
+
538 ERROR("testfile already created");
+
539 return -1;
+
540 }
+
541
+
542 fd = create_path_fd(path, data, len);
+
543 if (fd == -1)
+
544 return -1;
+
545
+
546 t->fd = fd;
+
547
+
548 res = fstat(fd, st);
+
549 if (res == -1) {
+
550 PERROR("fstat");
+
551 return -1;
+
552 }
+
553
+
554 return 0;
+
555}
+
556
+
557static int check_unlinked_testfile(int fd)
+
558{
+
559 struct stat *st = &this_test->stat;
+
560
+
561 st->st_nlink = 0;
+
562 return fcheck_stat(fd, O_PATH, st);
+
563}
+
564
+
565// Check recorded testfiles after all tests completed
+
566static int check_unlinked_testfiles(void)
+
567{
+
568 int fd;
+
569 int res, err = 0;
+
570 int num = testnum;
+
571
+
572 if (!unlinked_test)
+
573 return 0;
+
574
+
575 testnum = 0;
+
576 while (testnum < num) {
+
577 fd = next_test->fd;
+
578 start_test("check_unlinked_testfile");
+
579 if (fd == -1)
+
580 continue;
+
581
+
582 err += check_unlinked_testfile(fd);
+
583 res = close(fd);
+
584 if (res == -1) {
+
585 PERROR("close(test_fd)");
+
586 err--;
+
587 }
+
588 }
+
589
+
590 if (err) {
+
591 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
+
592 return 1;
+
593 }
+
594
+
595 return err;
+
596}
+
597
+
598static int cleanup_dir(const char *path, const char **dir_files, int quiet)
+
599{
+
600 int i;
+
601 int err = 0;
+
602
+
603 for (i = 0; dir_files[i]; i++) {
+
604 int res;
+
605 char fpath[1280];
+
606 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
607 res = unlink(fpath);
+
608 if (res == -1 && !quiet) {
+
609 PERROR("unlink");
+
610 err --;
+
611 }
+
612 }
+
613 if (err)
+
614 return -1;
+
615
+
616 return 0;
+
617}
+
618
+
619static int create_dir(const char *path, const char **dir_files)
+
620{
+
621 int res;
+
622 int i;
+
623
+
624 rmdir(path);
+
625 res = mkdir(path, 0755);
+
626 if (res == -1) {
+
627 PERROR("mkdir");
+
628 return -1;
+
629 }
+
630 res = check_type(path, S_IFDIR);
+
631 if (res == -1)
+
632 return -1;
+
633 res = check_mode(path, 0755);
+
634 if (res == -1)
+
635 return -1;
+
636
+
637 for (i = 0; dir_files[i]; i++) {
+
638 char fpath[1280];
+
639 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
640 res = create_file(fpath, "", 0);
+
641 if (res == -1) {
+
642 cleanup_dir(path, dir_files, 1);
+
643 return -1;
+
644 }
+
645 }
+
646 res = check_dir_contents(path, dir_files);
+
647 if (res == -1) {
+
648 cleanup_dir(path, dir_files, 1);
+
649 return -1;
+
650 }
+
651
+
652 return 0;
+
653}
+
654
+
655static int test_truncate(int len)
+
656{
+
657 const char *data = testdata;
+
658 int datalen = testdatalen;
+
659 int res;
+
660
+
661 start_test("truncate(%u)", (int) len);
+
662 res = create_testfile(testfile, data, datalen);
+
663 if (res == -1)
+
664 return -1;
+
665
+
666 res = truncate(testfile, len);
+
667 if (res == -1) {
+
668 PERROR("truncate");
+
669 return -1;
+
670 }
+
671 res = check_testfile_size(testfile, len);
+
672 if (res == -1)
+
673 return -1;
+
674
+
675 if (len > 0) {
+
676 if (len <= datalen) {
+
677 res = check_data(testfile, data, 0, len);
+
678 if (res == -1)
+
679 return -1;
+
680 } else {
+
681 res = check_data(testfile, data, 0, datalen);
+
682 if (res == -1)
+
683 return -1;
+
684 res = check_data(testfile, zerodata, datalen,
+
685 len - datalen);
+
686 if (res == -1)
+
687 return -1;
+
688 }
+
689 }
+
690 res = unlink(testfile);
+
691 if (res == -1) {
+
692 PERROR("unlink");
+
693 return -1;
+
694 }
+
695 res = check_nonexist(testfile);
+
696 if (res == -1)
+
697 return -1;
+
698
+
699 success();
+
700 return 0;
+
701}
+
702
+
703static int test_ftruncate(int len, int mode)
+
704{
+
705 const char *data = testdata;
+
706 int datalen = testdatalen;
+
707 int res;
+
708 int fd;
+
709
+
710 start_test("ftruncate(%u) mode: 0%03o", len, mode);
+
711 res = create_testfile(testfile, data, datalen);
+
712 if (res == -1)
+
713 return -1;
+
714
+
715 fd = open(testfile, O_WRONLY);
+
716 if (fd == -1) {
+
717 PERROR("open");
+
718 return -1;
+
719 }
+
720
+
721 res = fchmod(fd, mode);
+
722 if (res == -1) {
+
723 PERROR("fchmod");
+
724 close(fd);
+
725 return -1;
+
726 }
+
727 res = check_testfile_mode(testfile, mode);
+
728 if (res == -1) {
+
729 close(fd);
+
730 return -1;
+
731 }
+
732 res = ftruncate(fd, len);
+
733 if (res == -1) {
+
734 PERROR("ftruncate");
+
735 close(fd);
+
736 return -1;
+
737 }
+
738 close(fd);
+
739 res = check_testfile_size(testfile, len);
+
740 if (res == -1)
+
741 return -1;
+
742
+
743 if (len > 0) {
+
744 if (len <= datalen) {
+
745 res = check_data(testfile, data, 0, len);
+
746 if (res == -1)
+
747 return -1;
+
748 } else {
+
749 res = check_data(testfile, data, 0, datalen);
+
750 if (res == -1)
+
751 return -1;
+
752 res = check_data(testfile, zerodata, datalen,
+
753 len - datalen);
+
754 if (res == -1)
+
755 return -1;
+
756 }
+
757 }
+
758 res = unlink(testfile);
+
759 if (res == -1) {
+
760 PERROR("unlink");
+
761 return -1;
+
762 }
+
763 res = check_nonexist(testfile);
+
764 if (res == -1)
+
765 return -1;
+
766
+
767 success();
+
768 return 0;
+
769}
+
770
+
771static int test_seekdir(void)
+
772{
+
773 int i;
+
774 int res;
+
775 DIR *dp;
+
776 struct dirent *de = NULL;
+
777
+
778 start_test("seekdir");
+
779 res = create_dir(testdir, testdir_files);
+
780 if (res == -1)
+
781 return res;
+
782
+
783 dp = opendir(testdir);
+
784 if (dp == NULL) {
+
785 PERROR("opendir");
+
786 return -1;
+
787 }
+
788
+
789 /* Remember dir offsets */
+
790 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
+
791 seekdir_offsets[i] = telldir(dp);
+
792 errno = 0;
+
793 de = readdir(dp);
+
794 if (de == NULL) {
+
795 if (errno) {
+
796 PERROR("readdir");
+
797 goto fail;
+
798 }
+
799 break;
+
800 }
+
801 }
+
802
+
803 /* Walk until the end of directory */
+
804 while (de)
+
805 de = readdir(dp);
+
806
+
807 /* Start from the last valid dir offset and seek backwards */
+
808 for (i--; i >= 0; i--) {
+
809 seekdir(dp, seekdir_offsets[i]);
+
810 de = readdir(dp);
+
811 if (de == NULL) {
+
812 ERROR("Unexpected end of directory after seekdir()");
+
813 goto fail;
+
814 }
+
815 }
+
816
+
817 closedir(dp);
+
818 res = cleanup_dir(testdir, testdir_files, 0);
+
819 if (!res)
+
820 success();
+
821 return res;
+
822fail:
+
823 closedir(dp);
+
824 cleanup_dir(testdir, testdir_files, 1);
+
825 return -1;
+
826}
+
827
+
828#ifdef HAVE_COPY_FILE_RANGE
+
829static int test_copy_file_range(void)
+
830{
+
831 const char *data = testdata;
+
832 int datalen = testdatalen;
+
833 int err = 0;
+
834 int res;
+
835 int fd_in, fd_out;
+
836 off_t pos_in = 0, pos_out = 0;
+
837
+
838 start_test("copy_file_range");
+
839 unlink(testfile);
+
840 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
+
841 if (fd_in == -1) {
+
842 PERROR("creat");
+
843 return -1;
+
844 }
+
845 res = write(fd_in, data, datalen);
+
846 if (res == -1) {
+
847 PERROR("write");
+
848 close(fd_in);
+
849 return -1;
+
850 }
+
851 if (res != datalen) {
+
852 ERROR("write is short: %u instead of %u", res, datalen);
+
853 close(fd_in);
+
854 return -1;
+
855 }
+
856
+
857 unlink(testfile2);
+
858 fd_out = creat(testfile2, 0644);
+
859 if (fd_out == -1) {
+
860 PERROR("creat");
+
861 close(fd_in);
+
862 return -1;
+
863 }
+
864 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
+
865 if (res == -1) {
+
866 PERROR("copy_file_range");
+
867 close(fd_in);
+
868 close(fd_out);
+
869 return -1;
+
870 }
+
871 if (res != datalen) {
+
872 ERROR("copy is short: %u instead of %u", res, datalen);
+
873 close(fd_in);
+
874 close(fd_out);
+
875 return -1;
+
876 }
+
877
+
878 res = close(fd_in);
+
879 if (res == -1) {
+
880 PERROR("close");
+
881 close(fd_out);
+
882 return -1;
+
883 }
+
884 res = close(fd_out);
+
885 if (res == -1) {
+
886 PERROR("close");
+
887 return -1;
+
888 }
+
889
+
890 err = check_data(testfile2, data, 0, datalen);
+
891
+
892 res = unlink(testfile);
+
893 if (res == -1) {
+
894 PERROR("unlink");
+
895 return -1;
+
896 }
+
897 res = check_nonexist(testfile);
+
898 if (res == -1)
+
899 return -1;
+
900 if (err)
+
901 return -1;
+
902
+
903 res = unlink(testfile2);
+
904 if (res == -1) {
+
905 PERROR("unlink");
+
906 return -1;
+
907 }
+
908 res = check_nonexist(testfile2);
+
909 if (res == -1)
+
910 return -1;
+
911 if (err)
+
912 return -1;
+
913
+
914 success();
+
915 return 0;
+
916}
+
917#else
+
918static int test_copy_file_range(void)
+
919{
+
920 return 0;
+
921}
+
922#endif
+
923
+
924static int test_utime(void)
+
925{
+
926 struct utimbuf utm;
+
927 time_t atime = 987631200;
+
928 time_t mtime = 123116400;
+
929 int res;
+
930
+
931 start_test("utime");
+
932 res = create_testfile(testfile, NULL, 0);
+
933 if (res == -1)
+
934 return -1;
+
935
+
936 utm.actime = atime;
+
937 utm.modtime = mtime;
+
938 res = utime(testfile, &utm);
+
939 if (res == -1) {
+
940 PERROR("utime");
+
941 return -1;
+
942 }
+
943 res = check_times(testfile, atime, mtime);
+
944 if (res == -1) {
+
945 return -1;
+
946 }
+
947 res = unlink(testfile);
+
948 if (res == -1) {
+
949 PERROR("unlink");
+
950 return -1;
+
951 }
+
952 res = check_nonexist(testfile);
+
953 if (res == -1)
+
954 return -1;
+
955
+
956 success();
+
957 return 0;
+
958}
+
959
+
960static int test_create(void)
+
961{
+
962 const char *data = testdata;
+
963 int datalen = testdatalen;
+
964 int err = 0;
+
965 int res;
+
966 int fd;
+
967
+
968 start_test("create");
+
969 unlink(testfile);
+
970 fd = creat(testfile, 0644);
+
971 if (fd == -1) {
+
972 PERROR("creat");
+
973 return -1;
+
974 }
+
975 res = write(fd, data, datalen);
+
976 if (res == -1) {
+
977 PERROR("write");
+
978 close(fd);
+
979 return -1;
+
980 }
+
981 if (res != datalen) {
+
982 ERROR("write is short: %u instead of %u", res, datalen);
+
983 close(fd);
+
984 return -1;
+
985 }
+
986 res = close(fd);
+
987 if (res == -1) {
+
988 PERROR("close");
+
989 return -1;
+
990 }
+
991 res = check_type(testfile, S_IFREG);
+
992 if (res == -1)
+
993 return -1;
+
994 err += check_mode(testfile, 0644);
+
995 err += check_nlink(testfile, 1);
+
996 err += check_size(testfile, datalen);
+
997 err += check_data(testfile, data, 0, datalen);
+
998 res = unlink(testfile);
+
999 if (res == -1) {
+
1000 PERROR("unlink");
+
1001 return -1;
+
1002 }
+
1003 res = check_nonexist(testfile);
+
1004 if (res == -1)
+
1005 return -1;
+
1006 if (err)
+
1007 return -1;
+
1008
+
1009 success();
+
1010 return 0;
+
1011}
+
1012
+
1013static int test_create_unlink(void)
+
1014{
+
1015 const char *data = testdata;
+
1016 int datalen = testdatalen;
+
1017 int err = 0;
+
1018 int res;
+
1019 int fd;
+
1020
+
1021 start_test("create+unlink");
+
1022 unlink(testfile);
+
1023 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
+
1024 if (fd == -1) {
+
1025 PERROR("creat");
+
1026 return -1;
+
1027 }
+
1028 res = unlink(testfile);
+
1029 if (res == -1) {
+
1030 PERROR("unlink");
+
1031 close(fd);
+
1032 return -1;
+
1033 }
+
1034 res = check_nonexist(testfile);
+
1035 if (res == -1) {
+
1036 close(fd);
+
1037 return -1;
+
1038 }
+
1039 res = write(fd, data, datalen);
+
1040 if (res == -1) {
+
1041 PERROR("write");
+
1042 close(fd);
+
1043 return -1;
+
1044 }
+
1045 if (res != datalen) {
+
1046 ERROR("write is short: %u instead of %u", res, datalen);
+
1047 close(fd);
+
1048 return -1;
+
1049 }
+
1050 struct stat st = {
+
1051 .st_mode = S_IFREG | 0644,
+
1052 .st_size = datalen,
+
1053 };
+
1054 err = fcheck_stat(fd, O_RDWR, &st);
+
1055 err += fcheck_data(fd, data, 0, datalen);
+
1056 res = close(fd);
+
1057 if (res == -1) {
+
1058 PERROR("close");
+
1059 err--;
+
1060 }
+
1061 if (err)
+
1062 return -1;
+
1063
+
1064 success();
+
1065 return 0;
+
1066}
+
1067
+
1068static int test_mknod(void)
+
1069{
+
1070 int err = 0;
+
1071 int res;
+
1072
+
1073 start_test("mknod");
+
1074 unlink(testfile);
+
1075 res = mknod(testfile, 0644, 0);
+
1076 if (res == -1) {
+
1077 PERROR("mknod");
+
1078 return -1;
+
1079 }
+
1080 res = check_type(testfile, S_IFREG);
+
1081 if (res == -1)
+
1082 return -1;
+
1083 err += check_mode(testfile, 0644);
+
1084 err += check_nlink(testfile, 1);
+
1085 err += check_size(testfile, 0);
+
1086 res = unlink(testfile);
+
1087 if (res == -1) {
+
1088 PERROR("unlink");
+
1089 return -1;
+
1090 }
+
1091 res = check_nonexist(testfile);
+
1092 if (res == -1)
+
1093 return -1;
+
1094 if (err)
+
1095 return -1;
+
1096
+
1097 success();
+
1098 return 0;
+
1099}
+
1100
+
1101#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
+
1102
+
1103static int do_test_open(int exist, int flags, const char *flags_str, int mode)
+
1104{
+
1105 char buf[4096];
+
1106 const char *data = testdata;
+
1107 int datalen = testdatalen;
+
1108 unsigned currlen = 0;
+
1109 int err = 0;
+
1110 int res;
+
1111 int fd;
+
1112 off_t off;
+
1113
+
1114 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
+
1115 unlink(testfile);
+
1116 if (exist) {
+
1117 res = create_file(testfile_r, testdata2, testdata2len);
+
1118 if (res == -1)
+
1119 return -1;
+
1120
+
1121 currlen = testdata2len;
+
1122 }
+
1123
+
1124 fd = open(testfile, flags, mode);
+
1125 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
+
1126 if (fd != -1) {
+
1127 ERROR("open should have failed");
+
1128 close(fd);
+
1129 return -1;
+
1130 } else if (errno == EEXIST)
+
1131 goto succ;
+
1132 }
+
1133 if (!(flags & O_CREAT) && !exist) {
+
1134 if (fd != -1) {
+
1135 ERROR("open should have failed");
+
1136 close(fd);
+
1137 return -1;
+
1138 } else if (errno == ENOENT)
+
1139 goto succ;
+
1140 }
+
1141 if (fd == -1) {
+
1142 PERROR("open");
+
1143 return -1;
+
1144 }
+
1145
+
1146 if (flags & O_TRUNC)
+
1147 currlen = 0;
+
1148
+
1149 err += check_type(testfile, S_IFREG);
+
1150 if (exist)
+
1151 err += check_mode(testfile, 0644);
+
1152 else
+
1153 err += check_mode(testfile, mode);
+
1154 err += check_nlink(testfile, 1);
+
1155 err += check_size(testfile, currlen);
+
1156 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
+
1157 err += check_data(testfile, testdata2, 0, testdata2len);
+
1158
+
1159 res = write(fd, data, datalen);
+
1160 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1161 if (res == -1) {
+
1162 PERROR("write");
+
1163 err --;
+
1164 } else if (res != datalen) {
+
1165 ERROR("write is short: %u instead of %u", res, datalen);
+
1166 err --;
+
1167 } else {
+
1168 if (datalen > (int) currlen)
+
1169 currlen = datalen;
+
1170
+
1171 err += check_size(testfile, currlen);
+
1172
+
1173 if (mode & S_IRUSR) {
+
1174 err += check_data(testfile, data, 0, datalen);
+
1175 if (exist && !(flags & O_TRUNC) &&
+
1176 testdata2len > datalen)
+
1177 err += check_data(testfile,
+
1178 testdata2 + datalen,
+
1179 datalen,
+
1180 testdata2len - datalen);
+
1181 }
+
1182 }
+
1183 } else {
+
1184 if (res != -1) {
+
1185 ERROR("write should have failed");
+
1186 err --;
+
1187 } else if (errno != EBADF) {
+
1188 PERROR("write");
+
1189 err --;
+
1190 }
+
1191 }
+
1192 off = lseek(fd, SEEK_SET, 0);
+
1193 if (off == (off_t) -1) {
+
1194 PERROR("lseek");
+
1195 err--;
+
1196 } else if (off != 0) {
+
1197 ERROR("offset should have returned 0");
+
1198 err --;
+
1199 }
+
1200 res = read(fd, buf, sizeof(buf));
+
1201 if ((flags & O_ACCMODE) != O_WRONLY) {
+
1202 if (res == -1) {
+
1203 PERROR("read");
+
1204 err--;
+
1205 } else {
+
1206 int readsize =
+
1207 currlen < sizeof(buf) ? currlen : sizeof(buf);
+
1208 if (res != readsize) {
+
1209 ERROR("read is short: %i instead of %u",
+
1210 res, readsize);
+
1211 err--;
+
1212 } else {
+
1213 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1214 err += check_buffer(buf, data, datalen);
+
1215 if (exist && !(flags & O_TRUNC) &&
+
1216 testdata2len > datalen)
+
1217 err += check_buffer(buf + datalen,
+
1218 testdata2 + datalen,
+
1219 testdata2len - datalen);
+
1220 } else if (exist)
+
1221 err += check_buffer(buf, testdata2,
+
1222 testdata2len);
+
1223 }
+
1224 }
+
1225 } else {
+
1226 if (res != -1) {
+
1227 ERROR("read should have failed");
+
1228 err --;
+
1229 } else if (errno != EBADF) {
+
1230 PERROR("read");
+
1231 err --;
+
1232 }
+
1233 }
+
1234
+
1235 res = close(fd);
+
1236 if (res == -1) {
+
1237 PERROR("close");
+
1238 return -1;
+
1239 }
+
1240 res = unlink(testfile);
+
1241 if (res == -1) {
+
1242 PERROR("unlink");
+
1243 return -1;
+
1244 }
+
1245 res = check_nonexist(testfile);
+
1246 if (res == -1)
+
1247 return -1;
+
1248 res = check_nonexist(testfile_r);
+
1249 if (res == -1)
+
1250 return -1;
+
1251 if (err)
+
1252 return -1;
+
1253
+
1254succ:
+
1255 success();
+
1256 return 0;
+
1257}
+
1258
+
1259#define test_open_acc(flags, mode, err) \
+
1260 do_test_open_acc(flags, #flags, mode, err)
+
1261
+
1262static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
+
1263{
+
1264 const char *data = testdata;
+
1265 int datalen = testdatalen;
+
1266 int res;
+
1267 int fd;
+
1268
+
1269 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
+
1270 strerror(err));
+
1271 unlink(testfile);
+
1272 res = create_testfile(testfile, data, datalen);
+
1273 if (res == -1)
+
1274 return -1;
+
1275
+
1276 res = chmod(testfile, mode);
+
1277 if (res == -1) {
+
1278 PERROR("chmod");
+
1279 return -1;
+
1280 }
+
1281
+
1282 res = check_testfile_mode(testfile, mode);
+
1283 if (res == -1)
+
1284 return -1;
+
1285
+
1286 fd = open(testfile, flags);
+
1287 if (fd == -1) {
+
1288 if (err != errno) {
+
1289 PERROR("open");
+
1290 return -1;
+
1291 }
+
1292 } else {
+
1293 if (err) {
+
1294 ERROR("open should have failed");
+
1295 close(fd);
+
1296 return -1;
+
1297 }
+
1298 close(fd);
+
1299 }
+
1300
+
1301 res = unlink(testfile);
+
1302 if (res == -1) {
+
1303 PERROR("unlink");
+
1304 return -1;
+
1305 }
+
1306 res = check_nonexist(testfile);
+
1307 if (res == -1)
+
1308 return -1;
+
1309 res = check_nonexist(testfile_r);
+
1310 if (res == -1)
+
1311 return -1;
+
1312
+
1313 success();
+
1314 return 0;
+
1315}
+
1316
+
1317static int test_symlink(void)
+
1318{
+
1319 char buf[1024];
+
1320 const char *data = testdata;
+
1321 int datalen = testdatalen;
+
1322 int linklen = strlen(testfile);
+
1323 int err = 0;
+
1324 int res;
+
1325
+
1326 start_test("symlink");
+
1327 res = create_testfile(testfile, data, datalen);
+
1328 if (res == -1)
+
1329 return -1;
+
1330
+
1331 unlink(testfile2);
+
1332 res = symlink(testfile, testfile2);
+
1333 if (res == -1) {
+
1334 PERROR("symlink");
+
1335 return -1;
+
1336 }
+
1337 res = check_type(testfile2, S_IFLNK);
+
1338 if (res == -1)
+
1339 return -1;
+
1340 err += check_mode(testfile2, 0777);
+
1341 err += check_nlink(testfile2, 1);
+
1342 res = readlink(testfile2, buf, sizeof(buf));
+
1343 if (res == -1) {
+
1344 PERROR("readlink");
+
1345 err--;
+
1346 }
+
1347 if (res != linklen) {
+
1348 ERROR("short readlink: %u instead of %u", res, linklen);
+
1349 err--;
+
1350 }
+
1351 if (memcmp(buf, testfile, linklen) != 0) {
+
1352 ERROR("link mismatch");
+
1353 err--;
+
1354 }
+
1355 err += check_size(testfile2, datalen);
+
1356 err += check_data(testfile2, data, 0, datalen);
+
1357 res = unlink(testfile2);
+
1358 if (res == -1) {
+
1359 PERROR("unlink");
+
1360 return -1;
+
1361 }
+
1362 res = check_nonexist(testfile2);
+
1363 if (res == -1)
+
1364 return -1;
+
1365 if (err)
+
1366 return -1;
+
1367
+
1368 res = unlink(testfile);
+
1369 if (res == -1) {
+
1370 PERROR("unlink");
+
1371 return -1;
+
1372 }
+
1373 res = check_nonexist(testfile);
+
1374 if (res == -1)
+
1375 return -1;
+
1376
+
1377 success();
+
1378 return 0;
+
1379}
+
1380
+
1381static int test_link(void)
+
1382{
+
1383 const char *data = testdata;
+
1384 int datalen = testdatalen;
+
1385 int err = 0;
+
1386 int res;
+
1387
+
1388 start_test("link");
+
1389 res = create_testfile(testfile, data, datalen);
+
1390 if (res == -1)
+
1391 return -1;
+
1392
+
1393 unlink(testfile2);
+
1394 res = link(testfile, testfile2);
+
1395 if (res == -1) {
+
1396 PERROR("link");
+
1397 return -1;
+
1398 }
+
1399 res = check_type(testfile2, S_IFREG);
+
1400 if (res == -1)
+
1401 return -1;
+
1402 err += check_mode(testfile2, 0644);
+
1403 err += check_nlink(testfile2, 2);
+
1404 err += check_size(testfile2, datalen);
+
1405 err += check_data(testfile2, data, 0, datalen);
+
1406 res = unlink(testfile);
+
1407 if (res == -1) {
+
1408 PERROR("unlink");
+
1409 return -1;
+
1410 }
+
1411 res = check_nonexist(testfile);
+
1412 if (res == -1)
+
1413 return -1;
+
1414
+
1415 err += check_nlink(testfile2, 1);
+
1416 res = unlink(testfile2);
+
1417 if (res == -1) {
+
1418 PERROR("unlink");
+
1419 return -1;
+
1420 }
+
1421 res = check_nonexist(testfile2);
+
1422 if (res == -1)
+
1423 return -1;
+
1424 if (err)
+
1425 return -1;
+
1426
+
1427 success();
+
1428 return 0;
+
1429}
+
1430
+
1431static int test_link2(void)
+
1432{
+
1433 const char *data = testdata;
+
1434 int datalen = testdatalen;
+
1435 int err = 0;
+
1436 int res;
+
1437
+
1438 start_test("link-unlink-link");
+
1439 res = create_testfile(testfile, data, datalen);
+
1440 if (res == -1)
+
1441 return -1;
+
1442
+
1443 unlink(testfile2);
+
1444 res = link(testfile, testfile2);
+
1445 if (res == -1) {
+
1446 PERROR("link");
+
1447 return -1;
+
1448 }
+
1449 res = unlink(testfile);
+
1450 if (res == -1) {
+
1451 PERROR("unlink");
+
1452 return -1;
+
1453 }
+
1454 res = check_nonexist(testfile);
+
1455 if (res == -1)
+
1456 return -1;
+
1457 res = link(testfile2, testfile);
+
1458 if (res == -1) {
+
1459 PERROR("link");
+
1460 }
+
1461 res = check_type(testfile, S_IFREG);
+
1462 if (res == -1)
+
1463 return -1;
+
1464 err += check_mode(testfile, 0644);
+
1465 err += check_nlink(testfile, 2);
+
1466 err += check_size(testfile, datalen);
+
1467 err += check_data(testfile, data, 0, datalen);
+
1468
+
1469 res = unlink(testfile2);
+
1470 if (res == -1) {
+
1471 PERROR("unlink");
+
1472 return -1;
+
1473 }
+
1474 err += check_nlink(testfile, 1);
+
1475 res = unlink(testfile);
+
1476 if (res == -1) {
+
1477 PERROR("unlink");
+
1478 return -1;
+
1479 }
+
1480 res = check_nonexist(testfile);
+
1481 if (res == -1)
+
1482 return -1;
+
1483 if (err)
+
1484 return -1;
+
1485
+
1486 success();
+
1487 return 0;
+
1488}
+
1489
+
1490static int test_rename_file(void)
+
1491{
+
1492 const char *data = testdata;
+
1493 int datalen = testdatalen;
+
1494 int err = 0;
+
1495 int res;
+
1496
+
1497 start_test("rename file");
+
1498 res = create_testfile(testfile, data, datalen);
+
1499 if (res == -1)
+
1500 return -1;
+
1501
+
1502 unlink(testfile2);
+
1503 res = rename(testfile, testfile2);
+
1504 if (res == -1) {
+
1505 PERROR("rename");
+
1506 return -1;
+
1507 }
+
1508 res = check_nonexist(testfile);
+
1509 if (res == -1)
+
1510 return -1;
+
1511 res = check_type(testfile2, S_IFREG);
+
1512 if (res == -1)
+
1513 return -1;
+
1514 err += check_mode(testfile2, 0644);
+
1515 err += check_nlink(testfile2, 1);
+
1516 err += check_size(testfile2, datalen);
+
1517 err += check_data(testfile2, data, 0, datalen);
+
1518 res = unlink(testfile2);
+
1519 if (res == -1) {
+
1520 PERROR("unlink");
+
1521 return -1;
+
1522 }
+
1523 res = check_nonexist(testfile2);
+
1524 if (res == -1)
+
1525 return -1;
+
1526 if (err)
+
1527 return -1;
+
1528
+
1529 success();
+
1530 return 0;
+
1531}
+
1532
+
1533static int test_rename_dir(void)
+
1534{
+
1535 int err = 0;
+
1536 int res;
+
1537
+
1538 start_test("rename dir");
+
1539 res = create_dir(testdir, testdir_files);
+
1540 if (res == -1)
+
1541 return -1;
+
1542
+
1543 rmdir(testdir2);
+
1544 res = rename(testdir, testdir2);
+
1545 if (res == -1) {
+
1546 PERROR("rename");
+
1547 cleanup_dir(testdir, testdir_files, 1);
+
1548 return -1;
+
1549 }
+
1550 res = check_nonexist(testdir);
+
1551 if (res == -1) {
+
1552 cleanup_dir(testdir, testdir_files, 1);
+
1553 return -1;
+
1554 }
+
1555 res = check_type(testdir2, S_IFDIR);
+
1556 if (res == -1) {
+
1557 cleanup_dir(testdir2, testdir_files, 1);
+
1558 return -1;
+
1559 }
+
1560 err += check_mode(testdir2, 0755);
+
1561 err += check_dir_contents(testdir2, testdir_files);
+
1562 err += cleanup_dir(testdir2, testdir_files, 0);
+
1563 res = rmdir(testdir2);
+
1564 if (res == -1) {
+
1565 PERROR("rmdir");
+
1566 return -1;
+
1567 }
+
1568 res = check_nonexist(testdir2);
+
1569 if (res == -1)
+
1570 return -1;
+
1571 if (err)
+
1572 return -1;
+
1573
+
1574 success();
+
1575 return 0;
+
1576}
+
1577
+
1578static int test_rename_dir_loop(void)
+
1579{
+
1580#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
+
1581#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
+
1582
+
1583 char path[1280], path2[1280];
+
1584 int err = 0;
+
1585 int res;
+
1586
+
1587 start_test("rename dir loop");
+
1588
+
1589 res = create_dir(testdir, testdir_files);
+
1590 if (res == -1)
+
1591 return -1;
+
1592
+
1593 res = mkdir(PATH("a"), 0755);
+
1594 if (res == -1) {
+
1595 PERROR("mkdir");
+
1596 goto fail;
+
1597 }
+
1598
+
1599 res = rename(PATH("a"), PATH2("a"));
+
1600 if (res == -1) {
+
1601 PERROR("rename");
+
1602 goto fail;
+
1603 }
+
1604
+
1605 errno = 0;
+
1606 res = rename(PATH("a"), PATH2("a/b"));
+
1607 if (res == 0 || errno != EINVAL) {
+
1608 PERROR("rename");
+
1609 goto fail;
+
1610 }
+
1611
+
1612 res = mkdir(PATH("a/b"), 0755);
+
1613 if (res == -1) {
+
1614 PERROR("mkdir");
+
1615 goto fail;
+
1616 }
+
1617
+
1618 res = mkdir(PATH("a/b/c"), 0755);
+
1619 if (res == -1) {
+
1620 PERROR("mkdir");
+
1621 goto fail;
+
1622 }
+
1623
+
1624 errno = 0;
+
1625 res = rename(PATH("a"), PATH2("a/b/c"));
+
1626 if (res == 0 || errno != EINVAL) {
+
1627 PERROR("rename");
+
1628 goto fail;
+
1629 }
+
1630
+
1631 errno = 0;
+
1632 res = rename(PATH("a"), PATH2("a/b/c/a"));
+
1633 if (res == 0 || errno != EINVAL) {
+
1634 PERROR("rename");
+
1635 goto fail;
+
1636 }
+
1637
+
1638 errno = 0;
+
1639 res = rename(PATH("a/b/c"), PATH2("a"));
+
1640 if (res == 0 || errno != ENOTEMPTY) {
+
1641 PERROR("rename");
+
1642 goto fail;
+
1643 }
+
1644
+
1645 res = open(PATH("a/foo"), O_CREAT, 0644);
+
1646 if (res == -1) {
+
1647 PERROR("open");
+
1648 goto fail;
+
1649 }
+
1650 close(res);
+
1651
+
1652 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1653 if (res == -1) {
+
1654 PERROR("rename");
+
1655 goto fail;
+
1656 }
+
1657
+
1658 res = rename(PATH("a/bar"), PATH2("a/foo"));
+
1659 if (res == -1) {
+
1660 PERROR("rename");
+
1661 goto fail;
+
1662 }
+
1663
+
1664 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
+
1665 if (res == -1) {
+
1666 PERROR("rename");
+
1667 goto fail;
+
1668 }
+
1669
+
1670 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
+
1671 if (res == -1) {
+
1672 PERROR("rename");
+
1673 goto fail;
+
1674 }
+
1675
+
1676 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
+
1677 if (res == -1) {
+
1678 PERROR("rename");
+
1679 goto fail;
+
1680 }
+
1681
+
1682 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
+
1683 if (res == -1) {
+
1684 PERROR("rename");
+
1685 goto fail;
+
1686 }
+
1687
+
1688 res = open(PATH("a/bar"), O_CREAT, 0644);
+
1689 if (res == -1) {
+
1690 PERROR("open");
+
1691 goto fail;
+
1692 }
+
1693 close(res);
+
1694
+
1695 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1696 if (res == -1) {
+
1697 PERROR("rename");
+
1698 goto fail;
+
1699 }
+
1700
+
1701 unlink(PATH("a/bar"));
+
1702
+
1703 res = rename(PATH("a/b"), PATH2("a/d"));
+
1704 if (res == -1) {
+
1705 PERROR("rename");
+
1706 goto fail;
+
1707 }
+
1708
+
1709 res = rename(PATH("a/d"), PATH2("a/b"));
+
1710 if (res == -1) {
+
1711 PERROR("rename");
+
1712 goto fail;
+
1713 }
+
1714
+
1715 res = mkdir(PATH("a/d"), 0755);
+
1716 if (res == -1) {
+
1717 PERROR("mkdir");
+
1718 goto fail;
+
1719 }
+
1720
+
1721 res = rename(PATH("a/b"), PATH2("a/d"));
+
1722 if (res == -1) {
+
1723 PERROR("rename");
+
1724 goto fail;
+
1725 }
+
1726
+
1727 res = rename(PATH("a/d"), PATH2("a/b"));
+
1728 if (res == -1) {
+
1729 PERROR("rename");
+
1730 goto fail;
+
1731 }
+
1732
+
1733 res = mkdir(PATH("a/d"), 0755);
+
1734 if (res == -1) {
+
1735 PERROR("mkdir");
+
1736 goto fail;
+
1737 }
+
1738
+
1739 res = mkdir(PATH("a/d/e"), 0755);
+
1740 if (res == -1) {
+
1741 PERROR("mkdir");
+
1742 goto fail;
+
1743 }
+
1744
+
1745 errno = 0;
+
1746 res = rename(PATH("a/b"), PATH2("a/d"));
+
1747 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
+
1748 PERROR("rename");
+
1749 goto fail;
+
1750 }
+
1751
+
1752 rmdir(PATH("a/d/e"));
+
1753 rmdir(PATH("a/d"));
+
1754
+
1755 rmdir(PATH("a/b/c"));
+
1756 rmdir(PATH("a/b"));
+
1757 rmdir(PATH("a"));
+
1758
+
1759 err += cleanup_dir(testdir, testdir_files, 0);
+
1760 res = rmdir(testdir);
+
1761 if (res == -1) {
+
1762 PERROR("rmdir");
+
1763 goto fail;
+
1764 }
+
1765 res = check_nonexist(testdir);
+
1766 if (res == -1)
+
1767 return -1;
+
1768 if (err)
+
1769 return -1;
+
1770
+
1771 success();
+
1772 return 0;
+
1773
+
1774fail:
+
1775 unlink(PATH("a/bar"));
+
1776
+
1777 rmdir(PATH("a/d/e"));
+
1778 rmdir(PATH("a/d"));
+
1779
+
1780 rmdir(PATH("a/b/c"));
+
1781 rmdir(PATH("a/b"));
+
1782 rmdir(PATH("a"));
+
1783
+
1784 cleanup_dir(testdir, testdir_files, 1);
+
1785 rmdir(testdir);
+
1786
+
1787 return -1;
+
1788
+
1789#undef PATH2
+
1790#undef PATH
+
1791}
+
1792
+
1793static int test_mkfifo(void)
+
1794{
+
1795 int res;
+
1796 int err = 0;
+
1797
+
1798 start_test("mkfifo");
+
1799 unlink(testfile);
+
1800 res = mkfifo(testfile, 0644);
+
1801 if (res == -1) {
+
1802 PERROR("mkfifo");
+
1803 return -1;
+
1804 }
+
1805 res = check_type(testfile, S_IFIFO);
+
1806 if (res == -1)
+
1807 return -1;
+
1808 err += check_mode(testfile, 0644);
+
1809 err += check_nlink(testfile, 1);
+
1810 res = unlink(testfile);
+
1811 if (res == -1) {
+
1812 PERROR("unlink");
+
1813 return -1;
+
1814 }
+
1815 res = check_nonexist(testfile);
+
1816 if (res == -1)
+
1817 return -1;
+
1818 if (err)
+
1819 return -1;
+
1820
+
1821 success();
+
1822 return 0;
+
1823}
+
1824
+
1825static int test_mkdir(void)
+
1826{
+
1827 int res;
+
1828 int err = 0;
+
1829 const char *dir_contents[] = {NULL};
+
1830
+
1831 start_test("mkdir");
+
1832 rmdir(testdir);
+
1833 res = mkdir(testdir, 0755);
+
1834 if (res == -1) {
+
1835 PERROR("mkdir");
+
1836 return -1;
+
1837 }
+
1838 res = check_type(testdir, S_IFDIR);
+
1839 if (res == -1)
+
1840 return -1;
+
1841 err += check_mode(testdir, 0755);
+
1842 /* Some file systems (like btrfs) don't track link
+
1843 count for directories */
+
1844 //err += check_nlink(testdir, 2);
+
1845 err += check_dir_contents(testdir, dir_contents);
+
1846 res = rmdir(testdir);
+
1847 if (res == -1) {
+
1848 PERROR("rmdir");
+
1849 return -1;
+
1850 }
+
1851 res = check_nonexist(testdir);
+
1852 if (res == -1)
+
1853 return -1;
+
1854 if (err)
+
1855 return -1;
+
1856
+
1857 success();
+
1858 return 0;
+
1859}
+
1860
+
1861static int test_socket(void)
+
1862{
+
1863 struct sockaddr_un su;
+
1864 int fd;
+
1865 int res;
+
1866 int err = 0;
+
1867 const size_t test_sock_len = strlen(testsock) + 1;
+
1868
+
1869 start_test("socket");
+
1870 if (test_sock_len > sizeof(su.sun_path)) {
+
1871 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
+
1872 strlen(testsock) + 1 - sizeof(su.sun_path));
+
1873 return -1;
+
1874 }
+
1875 unlink(testsock);
+
1876 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
1877 if (fd < 0) {
+
1878 PERROR("socket");
+
1879 return -1;
+
1880 }
+
1881 su.sun_family = AF_UNIX;
+
1882
+
1883 strncpy(su.sun_path, testsock, test_sock_len);
+
1884 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
+
1885 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
+
1886 if (res == -1) {
+
1887 PERROR("bind");
+
1888 return -1;
+
1889 }
+
1890
+
1891 res = check_type(testsock, S_IFSOCK);
+
1892 if (res == -1) {
+
1893 close(fd);
+
1894 return -1;
+
1895 }
+
1896 err += check_nlink(testsock, 1);
+
1897 close(fd);
+
1898 res = unlink(testsock);
+
1899 if (res == -1) {
+
1900 PERROR("unlink");
+
1901 return -1;
+
1902 }
+
1903 res = check_nonexist(testsock);
+
1904 if (res == -1)
+
1905 return -1;
+
1906 if (err)
+
1907 return -1;
+
1908
+
1909 success();
+
1910 return 0;
+
1911}
+
1912
+
1913#define test_create_ro_dir(flags) \
+
1914 do_test_create_ro_dir(flags, #flags)
+
1915
+
1916static int do_test_create_ro_dir(int flags, const char *flags_str)
+
1917{
+
1918 int res;
+
1919 int err = 0;
+
1920 int fd;
+
1921
+
1922 start_test("open(%s) in read-only directory", flags_str);
+
1923 rmdir(testdir);
+
1924 res = mkdir(testdir, 0555);
+
1925 if (res == -1) {
+
1926 PERROR("mkdir");
+
1927 return -1;
+
1928 }
+
1929 fd = open(subfile, flags, 0644);
+
1930 if (fd != -1) {
+
1931 close(fd);
+
1932 unlink(subfile);
+
1933 ERROR("open should have failed");
+
1934 err--;
+
1935 } else {
+
1936 res = check_nonexist(subfile);
+
1937 if (res == -1)
+
1938 err--;
+
1939 }
+
1940 unlink(subfile);
+
1941 res = rmdir(testdir);
+
1942 if (res == -1) {
+
1943 PERROR("rmdir");
+
1944 return -1;
+
1945 }
+
1946 res = check_nonexist(testdir);
+
1947 if (res == -1)
+
1948 return -1;
+
1949 if (err)
+
1950 return -1;
+
1951
+
1952 success();
+
1953 return 0;
+
1954}
+
1955
+
1956#ifndef __FreeBSD__
+
1957/* this tests open with O_TMPFILE
+
1958 note that this will only work with the fuse low level api
+
1959 you will get ENOTSUP with the high level api */
+
1960static int test_create_tmpfile(void)
+
1961{
+
1962 rmdir(testdir);
+
1963 int res = mkdir(testdir, 0777);
+
1964 if (res)
+
1965 return -1;
+
1966
+
1967 start_test("create tmpfile");
+
1968
+
1969 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
1970 if(fd == -1) {
+
1971 if (errno == ENOTSUP) {
+
1972 /* don't bother if we're working on an old kernel
+
1973 or on the high level API */
+
1974 return 0;
+
1975 }
+
1976
+
1977 PERROR("open O_TMPFILE | O_RDWR");
+
1978 return -1;
+
1979 }
+
1980 close(fd);
+
1981
+
1982 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
+
1983 if(fd == -1){
+
1984 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
+
1985 return -1;
+
1986 };
+
1987 close(fd);
+
1988
+
1989 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
+
1990 if (fd != -1) {
+
1991 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
+
1992 return -1;
+
1993 }
+
1994
+
1995 success();
+
1996 return 0;
+
1997}
+
1998
+
1999static int test_create_and_link_tmpfile(void)
+
2000{
+
2001 /* skip this test for now since the github runner will fail in the linkat call below */
+
2002 return 0;
+
2003
+
2004 rmdir(testdir);
+
2005 unlink(testfile);
+
2006
+
2007 int res = mkdir(testdir, 0777);
+
2008 if (res)
+
2009 return -1;
+
2010
+
2011 start_test("create and link tmpfile");
+
2012
+
2013 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
+
2014 if(fd == -1) {
+
2015 if (errno == ENOTSUP) {
+
2016 /* don't bother if we're working on an old kernel
+
2017 or on the high level API */
+
2018 return 0;
+
2019 }
+
2020 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
+
2021 return -1;
+
2022 }
+
2023
+
2024 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2025 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
+
2026 return -1;
+
2027 }
+
2028 close(fd);
+
2029
+
2030 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2031 if(fd == -1) {
+
2032 PERROR("open O_TMPFILE");
+
2033 return -1;
+
2034 }
+
2035
+
2036 if (check_nonexist(testfile)) {
+
2037 return -1;
+
2038 }
+
2039
+
2040 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2041 PERROR("linkat tempfile");
+
2042 return -1;
+
2043 }
+
2044 close(fd);
+
2045
+
2046 if (check_nlink(testfile, 1)) {
+
2047 return -1;
+
2048 }
+
2049 unlink(testfile);
+
2050
+
2051 success();
+
2052 return 0;
+
2053}
+
2054#endif
+
2055
+
2056int main(int argc, char *argv[])
+
2057{
+
2058 int err = 0;
+
2059 int a;
+
2060 int is_root;
+
2061
+
2062 umask(0);
+
2063 if (argc < 2 || argc > 4) {
+
2064 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
+
2065 return 1;
+
2066 }
+
2067 basepath = argv[1];
+
2068 basepath_r = basepath;
+
2069 for (a = 2; a < argc; a++) {
+
2070 char *endptr;
+
2071 char *arg = argv[a];
+
2072 if (arg[0] == ':') {
+
2073 basepath_r = arg + 1;
+
2074 } else {
+
2075 if (arg[0] == '-') {
+
2076 arg++;
+
2077 if (arg[0] == 'u') {
+
2078 unlinked_test = 1;
+
2079 endptr = arg + 1;
+
2080 } else {
+
2081 skip_test = strtoul(arg, &endptr, 10);
+
2082 }
+
2083 } else {
+
2084 select_test = strtoul(arg, &endptr, 10);
+
2085 }
+
2086 if (arg[0] == '\0' || *endptr != '\0') {
+
2087 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
+
2088 return 1;
+
2089 }
+
2090 }
+
2091 }
+
2092 assert(strlen(basepath) < 512);
+
2093 assert(strlen(basepath_r) < 512);
+
2094 if (basepath[0] != '/') {
+
2095 fprintf(stderr, "testdir must be an absolute path\n");
+
2096 return 1;
+
2097 }
+
2098
+
2099 sprintf(testfile, "%s/testfile", basepath);
+
2100 sprintf(testfile2, "%s/testfile2", basepath);
+
2101 sprintf(testdir, "%s/testdir", basepath);
+
2102 sprintf(testdir2, "%s/testdir2", basepath);
+
2103 sprintf(subfile, "%s/subfile", testdir2);
+
2104 sprintf(testsock, "%s/testsock", basepath);
+
2105
+
2106 sprintf(testfile_r, "%s/testfile", basepath_r);
+
2107 sprintf(testfile2_r, "%s/testfile2", basepath_r);
+
2108 sprintf(testdir_r, "%s/testdir", basepath_r);
+
2109 sprintf(testdir2_r, "%s/testdir2", basepath_r);
+
2110 sprintf(subfile_r, "%s/subfile", testdir2_r);
+
2111
+
2112 is_root = (geteuid() == 0);
+
2113
+
2114 err += test_create();
+
2115 err += test_create_unlink();
+
2116 err += test_symlink();
+
2117 err += test_link();
+
2118 err += test_link2();
+
2119 err += test_mknod();
+
2120 err += test_mkfifo();
+
2121 err += test_mkdir();
+
2122 err += test_rename_file();
+
2123 err += test_rename_dir();
+
2124 err += test_rename_dir_loop();
+
2125 err += test_seekdir();
+
2126 err += test_socket();
+
2127 err += test_utime();
+
2128 err += test_truncate(0);
+
2129 err += test_truncate(testdatalen / 2);
+
2130 err += test_truncate(testdatalen);
+
2131 err += test_truncate(testdatalen + 100);
+
2132 err += test_ftruncate(0, 0600);
+
2133 err += test_ftruncate(testdatalen / 2, 0600);
+
2134 err += test_ftruncate(testdatalen, 0600);
+
2135 err += test_ftruncate(testdatalen + 100, 0600);
+
2136 err += test_ftruncate(0, 0400);
+
2137 err += test_ftruncate(0, 0200);
+
2138 err += test_ftruncate(0, 0000);
+
2139 err += test_open(0, O_RDONLY, 0);
+
2140 err += test_open(1, O_RDONLY, 0);
+
2141 err += test_open(1, O_RDWR, 0);
+
2142 err += test_open(1, O_WRONLY, 0);
+
2143 err += test_open(0, O_RDWR | O_CREAT, 0600);
+
2144 err += test_open(1, O_RDWR | O_CREAT, 0600);
+
2145 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2146 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2147 err += test_open(0, O_RDONLY | O_CREAT, 0600);
+
2148 err += test_open(0, O_RDONLY | O_CREAT, 0400);
+
2149 err += test_open(0, O_RDONLY | O_CREAT, 0200);
+
2150 err += test_open(0, O_RDONLY | O_CREAT, 0000);
+
2151 err += test_open(0, O_WRONLY | O_CREAT, 0600);
+
2152 err += test_open(0, O_WRONLY | O_CREAT, 0400);
+
2153 err += test_open(0, O_WRONLY | O_CREAT, 0200);
+
2154 err += test_open(0, O_WRONLY | O_CREAT, 0000);
+
2155 err += test_open(0, O_RDWR | O_CREAT, 0400);
+
2156 err += test_open(0, O_RDWR | O_CREAT, 0200);
+
2157 err += test_open(0, O_RDWR | O_CREAT, 0000);
+
2158 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2159 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2160 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2161 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2162 err += test_open_acc(O_RDONLY, 0600, 0);
+
2163 err += test_open_acc(O_WRONLY, 0600, 0);
+
2164 err += test_open_acc(O_RDWR, 0600, 0);
+
2165 err += test_open_acc(O_RDONLY, 0400, 0);
+
2166 err += test_open_acc(O_WRONLY, 0200, 0);
+
2167 if(!is_root) {
+
2168 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
+
2169 err += test_open_acc(O_WRONLY, 0400, EACCES);
+
2170 err += test_open_acc(O_RDWR, 0400, EACCES);
+
2171 err += test_open_acc(O_RDONLY, 0200, EACCES);
+
2172 err += test_open_acc(O_RDWR, 0200, EACCES);
+
2173 err += test_open_acc(O_RDONLY, 0000, EACCES);
+
2174 err += test_open_acc(O_WRONLY, 0000, EACCES);
+
2175 err += test_open_acc(O_RDWR, 0000, EACCES);
+
2176 }
+
2177 err += test_create_ro_dir(O_CREAT);
+
2178 err += test_create_ro_dir(O_CREAT | O_EXCL);
+
2179 err += test_create_ro_dir(O_CREAT | O_WRONLY);
+
2180 err += test_create_ro_dir(O_CREAT | O_TRUNC);
+
2181 err += test_copy_file_range();
+
2182#ifndef __FreeBSD__
+
2183 err += test_create_tmpfile();
+
2184 err += test_create_and_link_tmpfile();
+
2185#endif
+
2186
+
2187 unlink(testfile2);
+
2188 unlink(testsock);
+
2189 rmdir(testdir);
+
2190 rmdir(testdir2);
+
2191
+
2192 if (err) {
+
2193 fprintf(stderr, "%i tests failed\n", -err);
+
2194 return 1;
+
2195 }
+
2196
+
2197 return check_unlinked_testfiles();
+
2198}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2test_2test__write__cache_8c_source.html b/doc/html/fuse-3_817_81-rc1_2test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..1d90e6d --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2test_2test__write__cache_8c_source.html @@ -0,0 +1,403 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test/test_write_cache.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_write_cache.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <sys/stat.h>
+
26#include <pthread.h>
+
27#include <stdatomic.h>
+
28
+
29#ifndef __linux__
+
30#include <limits.h>
+
31#else
+
32#include <linux/limits.h>
+
33#endif
+
34
+
35#define FILE_INO 2
+
36#define FILE_NAME "write_me"
+
37
+
38/* Command line parsing */
+
39struct options {
+
40 int writeback;
+
41 int data_size;
+
42 int delay_ms;
+
43} options = {
+
44 .writeback = 0,
+
45 .data_size = 2048,
+
46 .delay_ms = 0,
+
47};
+
48
+
49#define WRITE_SYSCALLS 64
+
50
+
51#define OPTION(t, p) \
+
52 { t, offsetof(struct options, p), 1 }
+
53static const struct fuse_opt option_spec[] = {
+
54 OPTION("writeback_cache", writeback),
+
55 OPTION("--data-size=%d", data_size),
+
56 OPTION("--delay_ms=%d", delay_ms),
+ +
58};
+
59static int got_write;
+
60static atomic_int write_cnt;
+
61
+
62pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
63pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
64static int write_start, write_done;
+
65
+
66static void tfs_init (void *userdata, struct fuse_conn_info *conn)
+
67{
+
68 (void) userdata;
+
69
+
70 if(options.writeback) {
+
71 assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE));
+ +
73 }
+
74}
+
75
+
76static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
77 stbuf->st_ino = ino;
+
78 if (ino == FUSE_ROOT_ID) {
+
79 stbuf->st_mode = S_IFDIR | 0755;
+
80 stbuf->st_nlink = 1;
+
81 }
+
82
+
83 else if (ino == FILE_INO) {
+
84 stbuf->st_mode = S_IFREG | 0222;
+
85 stbuf->st_nlink = 1;
+
86 stbuf->st_size = 0;
+
87 }
+
88
+
89 else
+
90 return -1;
+
91
+
92 return 0;
+
93}
+
94
+
95static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
96 const char *name) {
+
97 struct fuse_entry_param e;
+
98 memset(&e, 0, sizeof(e));
+
99
+
100 if (parent != FUSE_ROOT_ID)
+
101 goto err_out;
+
102 else if (strcmp(name, FILE_NAME) == 0)
+
103 e.ino = FILE_INO;
+
104 else
+
105 goto err_out;
+
106
+
107 if (tfs_stat(e.ino, &e.attr) != 0)
+
108 goto err_out;
+
109 fuse_reply_entry(req, &e);
+
110 return;
+
111
+
112err_out:
+
113 fuse_reply_err(req, ENOENT);
+
114}
+
115
+
116static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
117 struct fuse_file_info *fi) {
+
118 struct stat stbuf;
+
119
+
120 (void) fi;
+
121
+
122 memset(&stbuf, 0, sizeof(stbuf));
+
123 if (tfs_stat(ino, &stbuf) != 0)
+
124 fuse_reply_err(req, ENOENT);
+
125 else
+
126 fuse_reply_attr(req, &stbuf, 5);
+
127}
+
128
+
129static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
130 struct fuse_file_info *fi) {
+
131 if (ino == FUSE_ROOT_ID)
+
132 fuse_reply_err(req, EISDIR);
+
133 else {
+
134 assert(ino == FILE_INO);
+
135 /* Test close(rofd) does not block waiting for pending writes */
+
136 fi->noflush = !options.writeback && options.delay_ms &&
+
137 (fi->flags & O_ACCMODE) == O_RDONLY;
+
138 fuse_reply_open(req, fi);
+
139 }
+
140}
+
141
+
142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
143 size_t size, off_t off, struct fuse_file_info *fi) {
+
144 (void) fi; (void) buf; (void) off;
+
145 size_t expected;
+
146
+
147 assert(ino == FILE_INO);
+
148 expected = options.data_size;
+
149 if(options.writeback)
+
150 expected *= 2;
+
151
+
152 write_cnt++;
+
153
+
154 if(size != expected && !options.writeback)
+
155 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
+
156 expected, size);
+
157 else
+
158 got_write = 1;
+
159
+
160 /* Simulate waiting for pending writes */
+
161 if (options.delay_ms) {
+
162 pthread_mutex_lock(&lock);
+
163 write_start = 1;
+
164 pthread_cond_signal(&cond);
+
165 pthread_mutex_unlock(&lock);
+
166
+
167 usleep(options.delay_ms * 1000);
+
168
+
169 pthread_mutex_lock(&lock);
+
170 write_done = 1;
+
171 pthread_cond_signal(&cond);
+
172 pthread_mutex_unlock(&lock);
+
173 }
+
174
+
175 fuse_reply_write(req, size);
+
176}
+
177
+
178static struct fuse_lowlevel_ops tfs_oper = {
+
179 .init = tfs_init,
+
180 .lookup = tfs_lookup,
+
181 .getattr = tfs_getattr,
+
182 .open = tfs_open,
+
183 .write = tfs_write,
+
184};
+
185
+
186static void* close_rofd(void *data) {
+
187 int rofd = (int)(long) data;
+
188
+
189 /* Wait for first write to start */
+
190 pthread_mutex_lock(&lock);
+
191 while (!write_start && !write_done)
+
192 pthread_cond_wait(&cond, &lock);
+
193 pthread_mutex_unlock(&lock);
+
194
+
195 close(rofd);
+
196 printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
197
+
198 /* First write should not have been completed */
+
199 if (write_done)
+
200 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
+
201
+
202 return NULL;
+
203}
+
204
+
205static void* run_fs(void *data) {
+
206 struct fuse_session *se = (struct fuse_session*) data;
+
207 assert(fuse_session_loop(se) == 0);
+
208 return NULL;
+
209}
+
210
+
211static void test_fs(char *mountpoint) {
+
212 char fname[PATH_MAX];
+
213 char *buf;
+
214 const size_t iosize = options.data_size;
+
215 const size_t dsize = options.data_size * WRITE_SYSCALLS;
+
216 int fd, rofd;
+
217 pthread_t rofd_thread;
+
218 off_t off = 0;
+
219
+
220 buf = malloc(dsize);
+
221 assert(buf != NULL);
+
222 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
+
223 assert(read(fd, buf, dsize) == dsize);
+
224 close(fd);
+
225
+
226 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
227 mountpoint) > 0);
+
228 fd = open(fname, O_WRONLY);
+
229 if (fd == -1) {
+
230 perror(fname);
+
231 assert(0);
+
232 }
+
233
+
234 if (options.delay_ms) {
+
235 /* Verify that close(rofd) does not block waiting for pending writes */
+
236 rofd = open(fname, O_RDONLY);
+
237 assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0);
+
238 /* Give close_rofd time to start */
+
239 usleep(options.delay_ms * 1000);
+
240 }
+
241
+
242 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
+
243 assert(pwrite(fd, buf + off, iosize, off) == iosize);
+
244 off += iosize;
+
245 assert(off <= dsize);
+
246 }
+
247 free(buf);
+
248 close(fd);
+
249
+
250 if (options.delay_ms) {
+
251 printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
252 assert(pthread_join(rofd_thread, NULL) == 0);
+
253 }
+
254}
+
255
+
256int main(int argc, char *argv[]) {
+
257 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
258 struct fuse_session *se;
+
259 struct fuse_cmdline_opts fuse_opts;
+
260 pthread_t fs_thread;
+
261
+
262 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
+
263 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
264#ifndef __FreeBSD__
+
265 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
266#endif
+
267 se = fuse_session_new(&args, &tfs_oper,
+
268 sizeof(tfs_oper), NULL);
+
269 fuse_opt_free_args(&args);
+
270 assert (se != NULL);
+
271 assert(fuse_set_signal_handlers(se) == 0);
+
272 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
273
+
274 /* Start file-system thread */
+
275 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
276
+
277 /* Write test data */
+
278 test_fs(fuse_opts.mountpoint);
+
279 free(fuse_opts.mountpoint);
+
280
+
281 /* Stop file system */
+ + +
284 assert(pthread_join(fs_thread, NULL) == 0);
+
285
+
286 assert(got_write == 1);
+
287
+
288 /*
+
289 * when writeback cache is enabled, kernel side can merge requests, but
+
290 * memory pressure, system 'sync' might trigger data flushes before - flush
+
291 * might happen in between write syscalls - merging subpage writes into
+
292 * a single page and pages into large fuse requests might or might not work.
+
293 * Though we can expect that that at least some (but maybe all) write
+
294 * system calls can be merged.
+
295 */
+
296 if (options.writeback)
+
297 assert(write_cnt < WRITE_SYSCALLS);
+
298 else
+
299 assert(write_cnt == WRITE_SYSCALLS);
+
300
+ + +
303
+
304 printf("Test completed successfully.\n");
+
305 return 0;
+
306}
+
307
+
308
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
@ FUSE_CAP_WRITEBACK_CACHE
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
+
fuse_ino_t ino
+ +
uint32_t noflush
+ + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2test_2wrong__command_8c_source.html b/doc/html/fuse-3_817_81-rc1_2test_2wrong__command_8c_source.html new file mode 100644 index 0000000..78c87f2 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/test/wrong_command.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
wrong_command.c
+
+
+
1#include <stdio.h>
+
2
+
3int main(void) {
+
4#ifdef MESON_IS_SUBPROJECT
+
5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
+
6 "If you wish to run them try:\n"
+
7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
+
8 return 77; /* report as a skipped test */
+
9#else
+
10 fprintf(stderr, "\x1B[31m\e[1m"
+
11 "This is not the command you are looking for.\n"
+
12 "You probably want to run 'python3 -m pytest test/' instead"
+
13 "\e[0m\n");
+
14 return 1;
+
15#endif
+
16}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2util_2fusermount_8c_source.html b/doc/html/fuse-3_817_81-rc1_2util_2fusermount_8c_source.html new file mode 100644 index 0000000..4613b61 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2util_2fusermount_8c_source.html @@ -0,0 +1,1769 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/util/fusermount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone and strchrnul */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
40
+
41#define FUSE_DEV "/dev/fuse"
+
42
+
43static const char *progname;
+
44
+
45static int user_allow_other = 0;
+
46static int mount_max = 1000;
+
47
+
48static int auto_unmount = 0;
+
49
+
50#ifdef GETMNTENT_NEEDS_UNESCAPING
+
51// Older versions of musl libc don't unescape entries in /etc/mtab
+
52
+
53// unescapes octal sequences like \040 in-place
+
54// That's ok, because unescaping can not extend the length of the string.
+
55static void unescape(char *buf) {
+
56 char *src = buf;
+
57 char *dest = buf;
+
58 while (1) {
+
59 char *next_src = strchrnul(src, '\\');
+
60 int offset = next_src - src;
+
61 memmove(dest, src, offset);
+
62 src = next_src;
+
63 dest += offset;
+
64
+
65 if(*src == '\0') {
+
66 *dest = *src;
+
67 return;
+
68 }
+
69 src++;
+
70
+
71 if('0' <= src[0] && src[0] < '2' &&
+
72 '0' <= src[1] && src[1] < '8' &&
+
73 '0' <= src[2] && src[2] < '8') {
+
74 *dest++ = (src[0] - '0') << 6
+
75 | (src[1] - '0') << 3
+
76 | (src[2] - '0') << 0;
+
77 src += 3;
+
78 } else if (src[0] == '\\') {
+
79 *dest++ = '\\';
+
80 src += 1;
+
81 } else {
+
82 *dest++ = '\\';
+
83 }
+
84 }
+
85}
+
86
+
87static struct mntent *GETMNTENT(FILE *stream)
+
88{
+
89 struct mntent *entp = getmntent(stream);
+
90 if(entp != NULL) {
+
91 unescape(entp->mnt_fsname);
+
92 unescape(entp->mnt_dir);
+
93 unescape(entp->mnt_type);
+
94 unescape(entp->mnt_opts);
+
95 }
+
96 return entp;
+
97}
+
98#else
+
99#define GETMNTENT getmntent
+
100#endif // GETMNTENT_NEEDS_UNESCAPING
+
101
+
102/*
+
103 * Take a ',' separated option string and extract "x-" options
+
104 */
+
105static int extract_x_options(const char *original, char **non_x_opts,
+
106 char **x_opts)
+
107{
+
108 size_t orig_len;
+
109 const char *opt, *opt_end;
+
110
+
111 orig_len = strlen(original) + 1;
+
112
+
113 *non_x_opts = calloc(1, orig_len);
+
114 *x_opts = calloc(1, orig_len);
+
115
+
116 size_t non_x_opts_len = orig_len;
+
117 size_t x_opts_len = orig_len;
+
118
+
119 if (*non_x_opts == NULL || *x_opts == NULL) {
+
120 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
121 __func__, orig_len);
+
122 return -ENOMEM;
+
123 }
+
124
+
125 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
126 char *opt_buf;
+
127
+
128 opt_end = strchr(opt, ',');
+
129 if (opt_end == NULL)
+
130 opt_end = original + orig_len;
+
131
+
132 size_t opt_len = opt_end - opt;
+
133 size_t opt_len_left = orig_len - (opt - original);
+
134 size_t buf_len;
+
135 bool is_x_opts;
+
136
+
137 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
138 buf_len = x_opts_len;
+
139 is_x_opts = true;
+
140 opt_buf = *x_opts;
+
141 } else {
+
142 buf_len = non_x_opts_len;
+
143 is_x_opts = false;
+
144 opt_buf = *non_x_opts;
+
145 }
+
146
+
147 if (buf_len < orig_len) {
+
148 strncat(opt_buf, ",", 2);
+
149 buf_len -= 1;
+
150 }
+
151
+
152 /* omits ',' */
+
153 if ((ssize_t)(buf_len - opt_len) < 0) {
+
154 /* This would be a bug */
+
155 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
156 __func__, original);
+
157 return -EIO;
+
158 }
+
159
+
160 strncat(opt_buf, opt, opt_end - opt);
+
161 buf_len -= opt_len;
+
162
+
163 if (is_x_opts)
+
164 x_opts_len = buf_len;
+
165 else
+
166 non_x_opts_len = buf_len;
+
167 }
+
168
+
169 return 0;
+
170}
+
171
+
172static const char *get_user_name(void)
+
173{
+
174 struct passwd *pw = getpwuid(getuid());
+
175 if (pw != NULL && pw->pw_name != NULL)
+
176 return pw->pw_name;
+
177 else {
+
178 fprintf(stderr, "%s: could not determine username\n", progname);
+
179 return NULL;
+
180 }
+
181}
+
182
+
183static uid_t oldfsuid;
+
184static gid_t oldfsgid;
+
185
+
186static void drop_privs(void)
+
187{
+
188 if (getuid() != 0) {
+
189 oldfsuid = setfsuid(getuid());
+
190 oldfsgid = setfsgid(getgid());
+
191 }
+
192}
+
193
+
194static void restore_privs(void)
+
195{
+
196 if (getuid() != 0) {
+
197 setfsuid(oldfsuid);
+
198 setfsgid(oldfsgid);
+
199 }
+
200}
+
201
+
202#ifndef IGNORE_MTAB
+
203/*
+
204 * Make sure that /etc/mtab is checked and updated atomically
+
205 */
+
206static int lock_umount(void)
+
207{
+
208 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
209 int mtablock;
+
210 int res;
+
211 struct stat mtab_stat;
+
212
+
213 /* /etc/mtab could be a symlink to /proc/mounts */
+
214 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
215 return -1;
+
216
+
217 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
218 if (mtablock == -1) {
+
219 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
220 progname, strerror(errno));
+
221 return -1;
+
222 }
+
223 res = lockf(mtablock, F_LOCK, 0);
+
224 if (res < 0) {
+
225 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
226 strerror(errno));
+
227 close(mtablock);
+
228 return -1;
+
229 }
+
230
+
231 return mtablock;
+
232}
+
233
+
234static void unlock_umount(int mtablock)
+
235{
+
236 if (mtablock >= 0) {
+
237 int res;
+
238
+
239 res = lockf(mtablock, F_ULOCK, 0);
+
240 if (res < 0) {
+
241 fprintf(stderr, "%s: error releasing lock: %s\n",
+
242 progname, strerror(errno));
+
243 }
+
244 close(mtablock);
+
245 }
+
246}
+
247
+
248static int add_mount(const char *source, const char *mnt, const char *type,
+
249 const char *opts)
+
250{
+
251 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
252}
+
253
+
254static int may_unmount(const char *mnt, int quiet)
+
255{
+
256 struct mntent *entp;
+
257 FILE *fp;
+
258 const char *user = NULL;
+
259 char uidstr[32];
+
260 unsigned uidlen = 0;
+
261 int found;
+
262 const char *mtab = _PATH_MOUNTED;
+
263
+
264 user = get_user_name();
+
265 if (user == NULL)
+
266 return -1;
+
267
+
268 fp = setmntent(mtab, "r");
+
269 if (fp == NULL) {
+
270 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
271 strerror(errno));
+
272 return -1;
+
273 }
+
274
+
275 uidlen = sprintf(uidstr, "%u", getuid());
+
276
+
277 found = 0;
+
278 while ((entp = GETMNTENT(fp)) != NULL) {
+
279 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
280 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
281 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
282 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
283 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
284 char *p = strstr(entp->mnt_opts, "user=");
+
285 if (p &&
+
286 (p == entp->mnt_opts || *(p-1) == ',') &&
+
287 strcmp(p + 5, user) == 0) {
+
288 found = 1;
+
289 break;
+
290 }
+
291 /* /etc/mtab is a link pointing to
+
292 /proc/mounts: */
+
293 else if ((p =
+
294 strstr(entp->mnt_opts, "user_id=")) &&
+
295 (p == entp->mnt_opts ||
+
296 *(p-1) == ',') &&
+
297 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
298 (*(p+8+uidlen) == ',' ||
+
299 *(p+8+uidlen) == '\0')) {
+
300 found = 1;
+
301 break;
+
302 }
+
303 }
+
304 }
+
305 endmntent(fp);
+
306
+
307 if (!found) {
+
308 if (!quiet)
+
309 fprintf(stderr,
+
310 "%s: entry for %s not found in %s\n",
+
311 progname, mnt, mtab);
+
312 return -1;
+
313 }
+
314
+
315 return 0;
+
316}
+
317#endif
+
318
+
319/*
+
320 * Check whether the file specified in "fusermount3 -u" is really a
+
321 * mountpoint and not a symlink. This is necessary otherwise the user
+
322 * could move the mountpoint away and replace it with a symlink
+
323 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
324 * unmounting that (umount(2) will follow symlinks).
+
325 *
+
326 * This is the child process running in a separate mount namespace, so
+
327 * we don't mess with the global namespace and if the process is
+
328 * killed for any reason, mounts are automatically cleaned up.
+
329 *
+
330 * First make sure nothing is propagated back into the parent
+
331 * namespace by marking all mounts "private".
+
332 *
+
333 * Then bind mount parent onto a stable base where the user can't move
+
334 * it around.
+
335 *
+
336 * Finally check /proc/mounts for an entry matching the requested
+
337 * mountpoint. If it's found then we are OK, and the user can't move
+
338 * it around within the parent directory as rename() will return
+
339 * EBUSY. Be careful to ignore any mounts that existed before the
+
340 * bind.
+
341 */
+
342static int check_is_mount_child(void *p)
+
343{
+
344 const char **a = p;
+
345 const char *last = a[0];
+
346 const char *mnt = a[1];
+
347 const char *type = a[2];
+
348 int res;
+
349 const char *procmounts = "/proc/mounts";
+
350 int found;
+
351 FILE *fp;
+
352 struct mntent *entp;
+
353 int count;
+
354
+
355 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
356 if (res == -1) {
+
357 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
358 progname, strerror(errno));
+
359 return 1;
+
360 }
+
361
+
362 fp = setmntent(procmounts, "r");
+
363 if (fp == NULL) {
+
364 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
365 procmounts, strerror(errno));
+
366 return 1;
+
367 }
+
368
+
369 count = 0;
+
370 while (GETMNTENT(fp) != NULL)
+
371 count++;
+
372 endmntent(fp);
+
373
+
374 fp = setmntent(procmounts, "r");
+
375 if (fp == NULL) {
+
376 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
377 procmounts, strerror(errno));
+
378 return 1;
+
379 }
+
380
+
381 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
382 if (res == -1) {
+
383 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
384 progname, strerror(errno));
+
385 return 1;
+
386 }
+
387
+
388 found = 0;
+
389 while ((entp = GETMNTENT(fp)) != NULL) {
+
390 if (count > 0) {
+
391 count--;
+
392 continue;
+
393 }
+
394 if (entp->mnt_dir[0] == '/' &&
+
395 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
396 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
397 found = 1;
+
398 break;
+
399 }
+
400 }
+
401 endmntent(fp);
+
402
+
403 if (!found) {
+
404 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
405 return 1;
+
406 }
+
407
+
408 return 0;
+
409}
+
410
+
411static pid_t clone_newns(void *a)
+
412{
+
413 char buf[131072];
+
414 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
415
+
416#ifdef __ia64__
+
417 extern int __clone2(int (*fn)(void *),
+
418 void *child_stack_base, size_t stack_size,
+
419 int flags, void *arg, pid_t *ptid,
+
420 void *tls, pid_t *ctid);
+
421
+
422 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
423 CLONE_NEWNS, a, NULL, NULL, NULL);
+
424#else
+
425 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
426#endif
+
427}
+
428
+
429static int check_is_mount(const char *last, const char *mnt, const char *type)
+
430{
+
431 pid_t pid, p;
+
432 int status;
+
433 const char *a[3] = { last, mnt, type };
+
434
+
435 pid = clone_newns((void *) a);
+
436 if (pid == (pid_t) -1) {
+
437 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
438 progname, strerror(errno));
+
439 return -1;
+
440 }
+
441 p = waitpid(pid, &status, __WCLONE);
+
442 if (p == (pid_t) -1) {
+
443 fprintf(stderr, "%s: waitpid failed: %s\n",
+
444 progname, strerror(errno));
+
445 return -1;
+
446 }
+
447 if (!WIFEXITED(status)) {
+
448 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
449 progname, status);
+
450 return -1;
+
451 }
+
452 if (WEXITSTATUS(status) != 0)
+
453 return -1;
+
454
+
455 return 0;
+
456}
+
457
+
458static int chdir_to_parent(char *copy, const char **lastp)
+
459{
+
460 char *tmp;
+
461 const char *parent;
+
462 char buf[65536];
+
463 int res;
+
464
+
465 tmp = strrchr(copy, '/');
+
466 if (tmp == NULL || tmp[1] == '\0') {
+
467 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
468 progname, copy);
+
469 return -1;
+
470 }
+
471 if (tmp != copy) {
+
472 *tmp = '\0';
+
473 parent = copy;
+
474 *lastp = tmp + 1;
+
475 } else if (tmp[1] != '\0') {
+
476 *lastp = tmp + 1;
+
477 parent = "/";
+
478 } else {
+
479 *lastp = ".";
+
480 parent = "/";
+
481 }
+
482
+
483 res = chdir(parent);
+
484 if (res == -1) {
+
485 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
486 progname, parent, strerror(errno));
+
487 return -1;
+
488 }
+
489
+
490 if (getcwd(buf, sizeof(buf)) == NULL) {
+
491 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
492 progname, strerror(errno));
+
493 return -1;
+
494 }
+
495 if (strcmp(buf, parent) != 0) {
+
496 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
497 parent, buf);
+
498 return -1;
+
499
+
500 }
+
501
+
502 return 0;
+
503}
+
504
+
505#ifndef IGNORE_MTAB
+
506static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
507{
+
508 int res;
+
509 char *copy;
+
510 const char *last;
+
511 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
512
+
513 if (getuid() != 0) {
+
514 res = may_unmount(mnt, quiet);
+
515 if (res == -1)
+
516 return -1;
+
517 }
+
518
+
519 copy = strdup(mnt);
+
520 if (copy == NULL) {
+
521 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
522 return -1;
+
523 }
+
524
+
525 drop_privs();
+
526 res = chdir_to_parent(copy, &last);
+
527 if (res == -1) {
+
528 restore_privs();
+
529 goto out;
+
530 }
+
531
+
532 res = umount2(last, umount_flags);
+
533 restore_privs();
+
534 if (res == -1 && !quiet) {
+
535 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
536 progname, mnt, strerror(errno));
+
537 }
+
538
+
539out:
+
540 free(copy);
+
541 if (res == -1)
+
542 return -1;
+
543
+
544 res = chdir("/");
+
545 if (res == -1) {
+
546 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
547 return -1;
+
548 }
+
549
+
550 return fuse_mnt_remove_mount(progname, mnt);
+
551}
+
552
+
553static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
554{
+
555 int res;
+
556 int mtablock = lock_umount();
+
557
+
558 res = unmount_fuse_locked(mnt, quiet, lazy);
+
559 unlock_umount(mtablock);
+
560
+
561 return res;
+
562}
+
563
+
564static int count_fuse_fs(void)
+
565{
+
566 struct mntent *entp;
+
567 int count = 0;
+
568 const char *mtab = _PATH_MOUNTED;
+
569 FILE *fp = setmntent(mtab, "r");
+
570 if (fp == NULL) {
+
571 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
572 strerror(errno));
+
573 return -1;
+
574 }
+
575 while ((entp = GETMNTENT(fp)) != NULL) {
+
576 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
577 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
578 count ++;
+
579 }
+
580 endmntent(fp);
+
581 return count;
+
582}
+
583
+
584
+
585#else /* IGNORE_MTAB */
+
586static int count_fuse_fs(void)
+
587{
+
588 return 0;
+
589}
+
590
+
591static int add_mount(const char *source, const char *mnt, const char *type,
+
592 const char *opts)
+
593{
+
594 (void) source;
+
595 (void) mnt;
+
596 (void) type;
+
597 (void) opts;
+
598 return 0;
+
599}
+
600
+
601static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
602{
+
603 (void) quiet;
+
604 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
605}
+
606#endif /* IGNORE_MTAB */
+
607
+
608static void strip_line(char *line)
+
609{
+
610 char *s = strchr(line, '#');
+
611 if (s != NULL)
+
612 s[0] = '\0';
+
613 for (s = line + strlen(line) - 1;
+
614 s >= line && isspace((unsigned char) *s); s--);
+
615 s[1] = '\0';
+
616 for (s = line; isspace((unsigned char) *s); s++);
+
617 if (s != line)
+
618 memmove(line, s, strlen(s)+1);
+
619}
+
620
+
621static void parse_line(char *line, int linenum)
+
622{
+
623 int tmp;
+
624 if (strcmp(line, "user_allow_other") == 0)
+
625 user_allow_other = 1;
+
626 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+
627 mount_max = tmp;
+
628 else if(line[0])
+
629 fprintf(stderr,
+
630 "%s: unknown parameter in %s at line %i: '%s'\n",
+
631 progname, FUSE_CONF, linenum, line);
+
632}
+
633
+
634static void read_conf(void)
+
635{
+
636 FILE *fp = fopen(FUSE_CONF, "r");
+
637 if (fp != NULL) {
+
638 int linenum = 1;
+
639 char line[256];
+
640 int isnewline = 1;
+
641 while (fgets(line, sizeof(line), fp) != NULL) {
+
642 if (isnewline) {
+
643 if (line[strlen(line)-1] == '\n') {
+
644 strip_line(line);
+
645 parse_line(line, linenum);
+
646 } else {
+
647 isnewline = 0;
+
648 }
+
649 } else if(line[strlen(line)-1] == '\n') {
+
650 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
651
+
652 isnewline = 1;
+
653 }
+
654 if (isnewline)
+
655 linenum ++;
+
656 }
+
657 if (!isnewline) {
+
658 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
659
+
660 }
+
661 if (ferror(fp)) {
+
662 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
663 exit(1);
+
664 }
+
665 fclose(fp);
+
666 } else if (errno != ENOENT) {
+
667 bool fatal = (errno != EACCES && errno != ELOOP &&
+
668 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
669 errno != EOVERFLOW);
+
670 fprintf(stderr, "%s: failed to open %s: %s\n",
+
671 progname, FUSE_CONF, strerror(errno));
+
672 if (fatal)
+
673 exit(1);
+
674 }
+
675}
+
676
+
677static int begins_with(const char *s, const char *beg)
+
678{
+
679 if (strncmp(s, beg, strlen(beg)) == 0)
+
680 return 1;
+
681 else
+
682 return 0;
+
683}
+
684
+
685struct mount_flags {
+
686 const char *opt;
+
687 unsigned long flag;
+
688 int on;
+
689 int safe;
+
690};
+
691
+
692static struct mount_flags mount_flags[] = {
+
693 {"rw", MS_RDONLY, 0, 1},
+
694 {"ro", MS_RDONLY, 1, 1},
+
695 {"suid", MS_NOSUID, 0, 0},
+
696 {"nosuid", MS_NOSUID, 1, 1},
+
697 {"dev", MS_NODEV, 0, 0},
+
698 {"nodev", MS_NODEV, 1, 1},
+
699 {"exec", MS_NOEXEC, 0, 1},
+
700 {"noexec", MS_NOEXEC, 1, 1},
+
701 {"async", MS_SYNCHRONOUS, 0, 1},
+
702 {"sync", MS_SYNCHRONOUS, 1, 1},
+
703 {"atime", MS_NOATIME, 0, 1},
+
704 {"noatime", MS_NOATIME, 1, 1},
+
705 {"diratime", MS_NODIRATIME, 0, 1},
+
706 {"nodiratime", MS_NODIRATIME, 1, 1},
+
707 {"lazytime", MS_LAZYTIME, 1, 1},
+
708 {"nolazytime", MS_LAZYTIME, 0, 1},
+
709 {"relatime", MS_RELATIME, 1, 1},
+
710 {"norelatime", MS_RELATIME, 0, 1},
+
711 {"strictatime", MS_STRICTATIME, 1, 1},
+
712 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
713 {"dirsync", MS_DIRSYNC, 1, 1},
+
714 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
715 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
716 {NULL, 0, 0, 0}
+
717};
+
718
+
719static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
720{
+
721 int i;
+
722
+
723 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
724 const char *opt = mount_flags[i].opt;
+
725 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
726 *on = mount_flags[i].on;
+
727 *flag = mount_flags[i].flag;
+
728 if (!mount_flags[i].safe && getuid() != 0) {
+
729 *flag = 0;
+
730 fprintf(stderr,
+
731 "%s: unsafe option %s ignored\n",
+
732 progname, opt);
+
733 }
+
734 return 1;
+
735 }
+
736 }
+
737 return 0;
+
738}
+
739
+
740static int add_option(char **optsp, const char *opt, unsigned expand)
+
741{
+
742 char *newopts;
+
743 if (*optsp == NULL)
+
744 newopts = strdup(opt);
+
745 else {
+
746 unsigned oldsize = strlen(*optsp);
+
747 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
748 newopts = (char *) realloc(*optsp, newsize);
+
749 if (newopts)
+
750 sprintf(newopts + oldsize, ",%s", opt);
+
751 }
+
752 if (newopts == NULL) {
+
753 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
754 return -1;
+
755 }
+
756 *optsp = newopts;
+
757 return 0;
+
758}
+
759
+
760static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
761{
+
762 int i;
+
763 int l;
+
764
+
765 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
766 return -1;
+
767
+
768 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
769 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
770 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
771 return -1;
+
772 }
+
773
+
774 if (add_option(mnt_optsp, opts, 0) == -1)
+
775 return -1;
+
776 /* remove comma from end of opts*/
+
777 l = strlen(*mnt_optsp);
+
778 if ((*mnt_optsp)[l-1] == ',')
+
779 (*mnt_optsp)[l-1] = '\0';
+
780 if (getuid() != 0) {
+
781 const char *user = get_user_name();
+
782 if (user == NULL)
+
783 return -1;
+
784
+
785 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
786 return -1;
+
787 strcat(*mnt_optsp, user);
+
788 }
+
789 return 0;
+
790}
+
791
+
792static int opt_eq(const char *s, unsigned len, const char *opt)
+
793{
+
794 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
795 return 1;
+
796 else
+
797 return 0;
+
798}
+
799
+
800static int get_string_opt(const char *s, unsigned len, const char *opt,
+
801 char **val)
+
802{
+
803 int i;
+
804 unsigned opt_len = strlen(opt);
+
805 char *d;
+
806
+
807 if (*val)
+
808 free(*val);
+
809 *val = (char *) malloc(len - opt_len + 1);
+
810 if (!*val) {
+
811 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
812 return 0;
+
813 }
+
814
+
815 d = *val;
+
816 s += opt_len;
+
817 len -= opt_len;
+
818 for (i = 0; i < len; i++) {
+
819 if (s[i] == '\\' && i + 1 < len)
+
820 i++;
+
821 *d++ = s[i];
+
822 }
+
823 *d = '\0';
+
824 return 1;
+
825}
+
826
+
827/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
828 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
829 * "group_id=1".
+
830 * This wrapper detects this case and bails out with an error.
+
831 */
+
832static int mount_notrunc(const char *source, const char *target,
+
833 const char *filesystemtype, unsigned long mountflags,
+
834 const char *data) {
+
835 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
836 fprintf(stderr, "%s: mount options too long\n", progname);
+
837 errno = EINVAL;
+
838 return -1;
+
839 }
+
840 return mount(source, target, filesystemtype, mountflags, data);
+
841}
+
842
+
843
+
844static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
845 int fd, const char *opts, const char *dev, char **sourcep,
+
846 char **mnt_optsp)
+
847{
+
848 int res;
+
849 int flags = MS_NOSUID | MS_NODEV;
+
850 char *optbuf;
+
851 char *mnt_opts = NULL;
+
852 const char *s;
+
853 char *d;
+
854 char *fsname = NULL;
+
855 char *subtype = NULL;
+
856 char *source = NULL;
+
857 char *type = NULL;
+
858 int blkdev = 0;
+
859
+
860 optbuf = (char *) malloc(strlen(opts) + 128);
+
861 if (!optbuf) {
+
862 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
863 return -1;
+
864 }
+
865
+
866 for (s = opts, d = optbuf; *s;) {
+
867 unsigned len;
+
868 const char *fsname_str = "fsname=";
+
869 const char *subtype_str = "subtype=";
+
870 bool escape_ok = begins_with(s, fsname_str) ||
+
871 begins_with(s, subtype_str);
+
872 for (len = 0; s[len]; len++) {
+
873 if (escape_ok && s[len] == '\\' && s[len + 1])
+
874 len++;
+
875 else if (s[len] == ',')
+
876 break;
+
877 }
+
878 if (begins_with(s, fsname_str)) {
+
879 if (!get_string_opt(s, len, fsname_str, &fsname))
+
880 goto err;
+
881 } else if (begins_with(s, subtype_str)) {
+
882 if (!get_string_opt(s, len, subtype_str, &subtype))
+
883 goto err;
+
884 } else if (opt_eq(s, len, "blkdev")) {
+
885 if (getuid() != 0) {
+
886 fprintf(stderr,
+
887 "%s: option blkdev is privileged\n",
+
888 progname);
+
889 goto err;
+
890 }
+
891 blkdev = 1;
+
892 } else if (opt_eq(s, len, "auto_unmount")) {
+
893 auto_unmount = 1;
+
894 } else if (!opt_eq(s, len, "nonempty") &&
+
895 !begins_with(s, "fd=") &&
+
896 !begins_with(s, "rootmode=") &&
+
897 !begins_with(s, "user_id=") &&
+
898 !begins_with(s, "group_id=")) {
+
899 int on;
+
900 int flag;
+
901 int skip_option = 0;
+
902 if (opt_eq(s, len, "large_read")) {
+
903 struct utsname utsname;
+
904 unsigned kmaj, kmin;
+
905 res = uname(&utsname);
+
906 if (res == 0 &&
+
907 sscanf(utsname.release, "%u.%u",
+
908 &kmaj, &kmin) == 2 &&
+
909 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
910 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
911 skip_option = 1;
+
912 }
+
913 }
+
914 if (getuid() != 0 && !user_allow_other &&
+
915 (opt_eq(s, len, "allow_other") ||
+
916 opt_eq(s, len, "allow_root"))) {
+
917 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
918 goto err;
+
919 }
+
920 if (!skip_option) {
+
921 if (find_mount_flag(s, len, &on, &flag)) {
+
922 if (on)
+
923 flags |= flag;
+
924 else
+
925 flags &= ~flag;
+
926 } else if (opt_eq(s, len, "default_permissions") ||
+
927 opt_eq(s, len, "allow_other") ||
+
928 begins_with(s, "max_read=") ||
+
929 begins_with(s, "blksize=")) {
+
930 memcpy(d, s, len);
+
931 d += len;
+
932 *d++ = ',';
+
933 } else {
+
934 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
935 exit(1);
+
936 }
+
937 }
+
938 }
+
939 s += len;
+
940 if (*s)
+
941 s++;
+
942 }
+
943 *d = '\0';
+
944 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
945 if (res == -1)
+
946 goto err;
+
947
+
948 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
949 fd, rootmode, getuid(), getgid());
+
950
+
951 source = malloc((fsname ? strlen(fsname) : 0) +
+
952 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
953
+
954 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
955 if (!type || !source) {
+
956 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
957 goto err;
+
958 }
+
959
+
960 if (subtype)
+
961 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
962 else
+
963 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
964
+
965 if (fsname)
+
966 strcpy(source, fsname);
+
967 else
+
968 strcpy(source, subtype ? subtype : dev);
+
969
+
970 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
971 if (res == -1 && errno == ENODEV && subtype) {
+
972 /* Probably missing subtype support */
+
973 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
974 if (fsname) {
+
975 if (!blkdev)
+
976 sprintf(source, "%s#%s", subtype, fsname);
+
977 } else {
+
978 strcpy(source, type);
+
979 }
+
980
+
981 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
982 }
+
983 if (res == -1 && errno == EINVAL) {
+
984 /* It could be an old version not supporting group_id */
+
985 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
986 fd, rootmode, getuid());
+
987 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
988 }
+
989 if (res == -1) {
+
990 int errno_save = errno;
+
991 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
992 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
993 progname);
+
994 else
+
995 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
996 strerror(errno_save));
+
997 goto err;
+
998 }
+
999 *sourcep = source;
+
1000 *typep = type;
+
1001 *mnt_optsp = mnt_opts;
+
1002 free(fsname);
+
1003 free(optbuf);
+
1004
+
1005 return 0;
+
1006
+
1007err:
+
1008 free(fsname);
+
1009 free(subtype);
+
1010 free(source);
+
1011 free(type);
+
1012 free(mnt_opts);
+
1013 free(optbuf);
+
1014 return -1;
+
1015}
+
1016
+
1017static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1018{
+
1019 int res;
+
1020 const char *mnt = *mntp;
+
1021 const char *origmnt = mnt;
+
1022 struct statfs fs_buf;
+
1023 size_t i;
+
1024
+
1025 res = lstat(mnt, stbuf);
+
1026 if (res == -1) {
+
1027 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1028 progname, mnt, strerror(errno));
+
1029 return -1;
+
1030 }
+
1031
+
1032 /* No permission checking is done for root */
+
1033 if (getuid() == 0)
+
1034 return 0;
+
1035
+
1036 if (S_ISDIR(stbuf->st_mode)) {
+
1037 res = chdir(mnt);
+
1038 if (res == -1) {
+
1039 fprintf(stderr,
+
1040 "%s: failed to chdir to mountpoint: %s\n",
+
1041 progname, strerror(errno));
+
1042 return -1;
+
1043 }
+
1044 mnt = *mntp = ".";
+
1045 res = lstat(mnt, stbuf);
+
1046 if (res == -1) {
+
1047 fprintf(stderr,
+
1048 "%s: failed to access mountpoint %s: %s\n",
+
1049 progname, origmnt, strerror(errno));
+
1050 return -1;
+
1051 }
+
1052
+
1053 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1054 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1055 progname, origmnt);
+
1056 return -1;
+
1057 }
+
1058
+
1059 res = access(mnt, W_OK);
+
1060 if (res == -1) {
+
1061 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1062 progname, origmnt);
+
1063 return -1;
+
1064 }
+
1065 } else if (S_ISREG(stbuf->st_mode)) {
+
1066 static char procfile[256];
+
1067 *mountpoint_fd = open(mnt, O_WRONLY);
+
1068 if (*mountpoint_fd == -1) {
+
1069 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1070 progname, mnt, strerror(errno));
+
1071 return -1;
+
1072 }
+
1073 res = fstat(*mountpoint_fd, stbuf);
+
1074 if (res == -1) {
+
1075 fprintf(stderr,
+
1076 "%s: failed to access mountpoint %s: %s\n",
+
1077 progname, mnt, strerror(errno));
+
1078 return -1;
+
1079 }
+
1080 if (!S_ISREG(stbuf->st_mode)) {
+
1081 fprintf(stderr,
+
1082 "%s: mountpoint %s is no longer a regular file\n",
+
1083 progname, mnt);
+
1084 return -1;
+
1085 }
+
1086
+
1087 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+
1088 *mntp = procfile;
+
1089 } else {
+
1090 fprintf(stderr,
+
1091 "%s: mountpoint %s is not a directory or a regular file\n",
+
1092 progname, mnt);
+
1093 return -1;
+
1094 }
+
1095
+
1096 /* Do not permit mounting over anything in procfs - it has a couple
+
1097 * places to which we have "write access" without being supposed to be
+
1098 * able to just put anything we want there.
+
1099 * Luckily, without allow_other, we can't get other users to actually
+
1100 * use any fake information we try to put there anyway.
+
1101 * Use a whitelist to be safe. */
+
1102 if (statfs(*mntp, &fs_buf)) {
+
1103 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1104 progname, mnt, strerror(errno));
+
1105 return -1;
+
1106 }
+
1107
+
1108 /* Define permitted filesystems for the mount target. This was
+
1109 * originally the same list as used by the ecryptfs mount helper
+
1110 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1111 * but got expanded as we found more filesystems that needed to be
+
1112 * overlaid. */
+
1113 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1114 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1115 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1116 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1117 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1118 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1119 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1120 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1121 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1122 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1123 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1124 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1125 0x01161970 /* GFS2_MAGIC */,
+
1126 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1127 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1128 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1129 0x3153464A /* JFS_SUPER_MAGIC */,
+
1130 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1131 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1132 0x0000564C /* NCP_SUPER_MAGIC */,
+
1133 0x00006969 /* NFS_SUPER_MAGIC */,
+
1134 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1135 0x5346544E /* NTFS_SB_MAGIC */,
+
1136 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1137 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1138 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1139 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1140 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1141 0x73717368 /* SQUASHFS_MAGIC */,
+
1142 0x01021994 /* TMPFS_MAGIC */,
+
1143 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1144#if __SIZEOF_LONG__ > 4
+
1145 0x736675005346544e /* UFSD */,
+
1146#endif
+
1147 0x58465342 /* XFS_SB_MAGIC */,
+
1148 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1149 0x858458f6 /* RAMFS_MAGIC */,
+
1150 };
+
1151 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1152 if (f_type_whitelist[i] == fs_buf.f_type)
+
1153 return 0;
+
1154 }
+
1155
+
1156 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1157 progname, (unsigned long)fs_buf.f_type);
+
1158 return -1;
+
1159}
+
1160
+
1161static int try_open(const char *dev, char **devp, int silent)
+
1162{
+
1163 int fd = open(dev, O_RDWR);
+
1164 if (fd != -1) {
+
1165 *devp = strdup(dev);
+
1166 if (*devp == NULL) {
+
1167 fprintf(stderr, "%s: failed to allocate memory\n",
+
1168 progname);
+
1169 close(fd);
+
1170 fd = -1;
+
1171 }
+
1172 } else if (errno == ENODEV ||
+
1173 errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1174 return -2;
+
1175 else if (!silent) {
+
1176 fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
+
1177 strerror(errno));
+
1178 }
+
1179 return fd;
+
1180}
+
1181
+
1182static int try_open_fuse_device(char **devp)
+
1183{
+
1184 int fd;
+
1185
+
1186 drop_privs();
+
1187 fd = try_open(FUSE_DEV, devp, 0);
+
1188 restore_privs();
+
1189 return fd;
+
1190}
+
1191
+
1192static int open_fuse_device(char **devp)
+
1193{
+
1194 int fd = try_open_fuse_device(devp);
+
1195 if (fd >= -1)
+
1196 return fd;
+
1197
+
1198 fprintf(stderr,
+
1199 "%s: fuse device not found, try 'modprobe fuse' first\n",
+
1200 progname);
+
1201
+
1202 return -1;
+
1203}
+
1204
+
1205
+
1206static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1207{
+
1208 int res;
+
1209 int fd;
+
1210 char *dev;
+
1211 struct stat stbuf;
+
1212 char *source = NULL;
+
1213 char *mnt_opts = NULL;
+
1214 const char *real_mnt = mnt;
+
1215 int mountpoint_fd = -1;
+
1216 char *do_mount_opts = NULL;
+
1217 char *x_opts = NULL;
+
1218
+
1219 fd = open_fuse_device(&dev);
+
1220 if (fd == -1)
+
1221 return -1;
+
1222
+
1223 drop_privs();
+
1224 read_conf();
+
1225
+
1226 if (getuid() != 0 && mount_max != -1) {
+
1227 int mount_count = count_fuse_fs();
+
1228 if (mount_count >= mount_max) {
+
1229 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1230 goto fail_close_fd;
+
1231 }
+
1232 }
+
1233
+
1234 // Extract any options starting with "x-"
+
1235 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1236 if (res)
+
1237 goto fail_close_fd;
+
1238
+
1239 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1240 restore_privs();
+
1241 if (res != -1)
+
1242 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1243 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1244
+
1245 if (mountpoint_fd != -1)
+
1246 close(mountpoint_fd);
+
1247
+
1248 if (res == -1)
+
1249 goto fail_close_fd;
+
1250
+
1251 res = chdir("/");
+
1252 if (res == -1) {
+
1253 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1254 goto fail_close_fd;
+
1255 }
+
1256
+
1257 if (geteuid() == 0) {
+
1258 if (x_opts && strlen(x_opts) > 0) {
+
1259 /*
+
1260 * Add back the options starting with "x-" to opts from
+
1261 * do_mount. +2 for ',' and '\0'
+
1262 */
+
1263 size_t mnt_opts_len = strlen(mnt_opts);
+
1264 size_t x_mnt_opts_len = mnt_opts_len+
+
1265 strlen(x_opts) + 2;
+
1266 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1267
+
1268 if (mnt_opts_len) {
+
1269 strcpy(x_mnt_opts, mnt_opts);
+
1270 strncat(x_mnt_opts, ",", 2);
+
1271 }
+
1272
+
1273 strncat(x_mnt_opts, x_opts,
+
1274 x_mnt_opts_len - mnt_opts_len - 2);
+
1275
+
1276 free(mnt_opts);
+
1277 mnt_opts = x_mnt_opts;
+
1278 }
+
1279
+
1280 res = add_mount(source, mnt, *type, mnt_opts);
+
1281 if (res == -1) {
+
1282 /* Can't clean up mount in a non-racy way */
+
1283 goto fail_close_fd;
+
1284 }
+
1285 }
+
1286
+
1287out_free:
+
1288 free(source);
+
1289 free(mnt_opts);
+
1290 free(dev);
+
1291 free(x_opts);
+
1292 free(do_mount_opts);
+
1293
+
1294 return fd;
+
1295
+
1296fail_close_fd:
+
1297 close(fd);
+
1298 fd = -1;
+
1299 goto out_free;
+
1300}
+
1301
+
1302static int send_fd(int sock_fd, int fd)
+
1303{
+
1304 int retval;
+
1305 struct msghdr msg;
+
1306 struct cmsghdr *p_cmsg;
+
1307 struct iovec vec;
+
1308 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1309 int *p_fds;
+
1310 char sendchar = 0;
+
1311
+
1312 msg.msg_control = cmsgbuf;
+
1313 msg.msg_controllen = sizeof(cmsgbuf);
+
1314 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1315 p_cmsg->cmsg_level = SOL_SOCKET;
+
1316 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1317 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1318 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1319 *p_fds = fd;
+
1320 msg.msg_controllen = p_cmsg->cmsg_len;
+
1321 msg.msg_name = NULL;
+
1322 msg.msg_namelen = 0;
+
1323 msg.msg_iov = &vec;
+
1324 msg.msg_iovlen = 1;
+
1325 msg.msg_flags = 0;
+
1326 /* "To pass file descriptors or credentials you need to send/read at
+
1327 * least one byte" (man 7 unix) */
+
1328 vec.iov_base = &sendchar;
+
1329 vec.iov_len = sizeof(sendchar);
+
1330 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1331 if (retval != 1) {
+
1332 perror("sending file descriptor");
+
1333 return -1;
+
1334 }
+
1335 return 0;
+
1336}
+
1337
+
1338/* Helper for should_auto_unmount
+
1339 *
+
1340 * fusermount typically has the s-bit set - initial open of `mnt` was as root
+
1341 * and got EACCESS as 'allow_other' was not specified.
+
1342 * Try opening `mnt` again with uid and guid of the calling process.
+
1343 */
+
1344static int recheck_ENOTCONN_as_owner(const char *mnt)
+
1345{
+
1346 int pid = fork();
+
1347 if(pid == -1) {
+
1348 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1349 _exit(EXIT_FAILURE);
+
1350 } else if(pid == 0) {
+
1351 uid_t uid = getuid();
+
1352 gid_t gid = getgid();
+
1353 if(setresgid(gid, gid, gid) == -1) {
+
1354 perror("fuse: can't set resgid");
+
1355 _exit(EXIT_FAILURE);
+
1356 }
+
1357 if(setresuid(uid, uid, uid) == -1) {
+
1358 perror("fuse: can't set resuid");
+
1359 _exit(EXIT_FAILURE);
+
1360 }
+
1361
+
1362 int fd = open(mnt, O_RDONLY);
+
1363 if(fd == -1 && errno == ENOTCONN)
+
1364 _exit(EXIT_SUCCESS);
+
1365 else
+
1366 _exit(EXIT_FAILURE);
+
1367 } else {
+
1368 int status;
+
1369 int res = waitpid(pid, &status, 0);
+
1370 if (res == -1) {
+
1371 perror("fuse: waiting for child failed");
+
1372 _exit(EXIT_FAILURE);
+
1373 }
+
1374 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1375 }
+
1376}
+
1377
+
1378/* The parent fuse process has died: decide whether to auto_unmount.
+
1379 *
+
1380 * In the normal case (umount or fusermount -u), the filesystem
+
1381 * has already been unmounted. If we simply unmount again we can
+
1382 * cause problems with stacked mounts (e.g. autofs).
+
1383 *
+
1384 * So we unmount here only in abnormal case where fuse process has
+
1385 * died without unmount happening. To detect this, we first look in
+
1386 * the mount table to make sure the mountpoint is still mounted and
+
1387 * has proper type. If so, we then see if opening the mount dir is
+
1388 * returning 'Transport endpoint is not connected'.
+
1389 *
+
1390 * The order of these is important, because if autofs is in use,
+
1391 * opening the dir to check for ENOTCONN will cause a new mount
+
1392 * in the normal case where filesystem has been unmounted cleanly.
+
1393 */
+
1394static int should_auto_unmount(const char *mnt, const char *type)
+
1395{
+
1396 char *copy;
+
1397 const char *last;
+
1398 int result = 0;
+
1399 int fd;
+
1400
+
1401 copy = strdup(mnt);
+
1402 if (copy == NULL) {
+
1403 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1404 return 0;
+
1405 }
+
1406
+
1407 if (chdir_to_parent(copy, &last) == -1)
+
1408 goto out;
+
1409 if (check_is_mount(last, mnt, type) == -1)
+
1410 goto out;
+
1411
+
1412 fd = open(mnt, O_RDONLY);
+
1413
+
1414 if (fd != -1) {
+
1415 close(fd);
+
1416 } else {
+
1417 switch(errno) {
+
1418 case ENOTCONN:
+
1419 result = 1;
+
1420 break;
+
1421 case EACCES:
+
1422 result = recheck_ENOTCONN_as_owner(mnt);
+
1423 break;
+
1424 default:
+
1425 result = 0;
+
1426 break;
+
1427 }
+
1428 }
+
1429out:
+
1430 free(copy);
+
1431 return result;
+
1432}
+
1433
+
1434static void usage(void)
+
1435{
+
1436 printf("%s: [options] mountpoint\n"
+
1437 "Options:\n"
+
1438 " -h print help\n"
+
1439 " -V print version\n"
+
1440 " -o opt[,opt...] mount options\n"
+
1441 " -u unmount\n"
+
1442 " -q quiet\n"
+
1443 " -z lazy unmount\n",
+
1444 progname);
+
1445 exit(1);
+
1446}
+
1447
+
1448static void show_version(void)
+
1449{
+
1450 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1451 exit(0);
+
1452}
+
1453
+
1454/*
+
1455 * Close all inherited fds that are not needed
+
1456 * Ideally these wouldn't come up at all, applications should better
+
1457 * use FD_CLOEXEC / O_CLOEXEC
+
1458 */
+
1459static void close_inherited_fds(int cfd)
+
1460{
+
1461 int max_fd = sysconf(_SC_OPEN_MAX);
+
1462 int rc;
+
1463
+
1464#ifdef CLOSE_RANGE_CLOEXEC
+
1465 /* high range first to be able to log errors through stdout/err*/
+
1466 rc = close_range(cfd + 1, ~0U, 0);
+
1467 if (rc < 0) {
+
1468 fprintf(stderr, "Failed to close high range of FDs: %s",
+
1469 strerror(errno));
+
1470 goto fallback;
+
1471 }
+
1472
+
1473 rc = close_range(0, cfd - 1, 0);
+
1474 if (rc < 0) {
+
1475 fprintf(stderr, "Failed to close low range of FDs: %s",
+
1476 strerror(errno));
+
1477 goto fallback;
+
1478 }
+
1479#endif
+
1480
+
1481fallback:
+
1482 /*
+
1483 * This also needs to close stdout/stderr, as the application
+
1484 * using libfuse might have closed these FDs and might be using
+
1485 * it. Although issue is now that logging errors won't be possible
+
1486 * after that.
+
1487 */
+
1488 for (int fd = 0; fd <= max_fd; fd++) {
+
1489 if (fd != cfd)
+
1490 close(fd);
+
1491 }
+
1492}
+
1493
+
1494int main(int argc, char *argv[])
+
1495{
+
1496 sigset_t sigset;
+
1497 int ch;
+
1498 int fd;
+
1499 int res;
+
1500 char *origmnt;
+
1501 char *mnt;
+
1502 static int unmount = 0;
+
1503 static int lazy = 0;
+
1504 static int quiet = 0;
+
1505 char *commfd = NULL;
+
1506 long cfd;
+
1507 const char *opts = "";
+
1508 const char *type = NULL;
+
1509 int setup_auto_unmount_only = 0;
+
1510
+
1511 static const struct option long_opts[] = {
+
1512 {"unmount", no_argument, NULL, 'u'},
+
1513 {"lazy", no_argument, NULL, 'z'},
+
1514 {"quiet", no_argument, NULL, 'q'},
+
1515 {"help", no_argument, NULL, 'h'},
+
1516 {"version", no_argument, NULL, 'V'},
+
1517 {"options", required_argument, NULL, 'o'},
+
1518 // Note: auto-unmount and comm-fd don't have short versions.
+
1519 // They'ne meant for internal use by mount.c
+
1520 {"auto-unmount", no_argument, NULL, 'U'},
+
1521 {"comm-fd", required_argument, NULL, 'c'},
+
1522 {0, 0, 0, 0}};
+
1523
+
1524 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1525 if (progname == NULL) {
+
1526 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1527 exit(1);
+
1528 }
+
1529
+
1530 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1531 NULL)) != -1) {
+
1532 switch (ch) {
+
1533 case 'h':
+
1534 usage();
+
1535 break;
+
1536
+
1537 case 'V':
+
1538 show_version();
+
1539 break;
+
1540
+
1541 case 'o':
+
1542 opts = optarg;
+
1543 break;
+
1544
+
1545 case 'u':
+
1546 unmount = 1;
+
1547 break;
+
1548 case 'U':
+
1549 unmount = 1;
+
1550 auto_unmount = 1;
+
1551 setup_auto_unmount_only = 1;
+
1552 break;
+
1553 case 'c':
+
1554 commfd = optarg;
+
1555 break;
+
1556 case 'z':
+
1557 lazy = 1;
+
1558 break;
+
1559
+
1560 case 'q':
+
1561 quiet = 1;
+
1562 break;
+
1563
+
1564 default:
+
1565 exit(1);
+
1566 }
+
1567 }
+
1568
+
1569 if (lazy && !unmount) {
+
1570 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1571 exit(1);
+
1572 }
+
1573
+
1574 if (optind >= argc) {
+
1575 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1576 exit(1);
+
1577 } else if (argc > optind + 1) {
+
1578 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1579 progname);
+
1580 exit(1);
+
1581 }
+
1582
+
1583 origmnt = argv[optind];
+
1584
+
1585 drop_privs();
+
1586 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1587 if (mnt != NULL) {
+
1588 res = chdir("/");
+
1589 if (res == -1) {
+
1590 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1591 goto err_out;
+
1592 }
+
1593 }
+
1594 restore_privs();
+
1595 if (mnt == NULL)
+
1596 exit(1);
+
1597
+
1598 umask(033);
+
1599 if (!setup_auto_unmount_only && unmount)
+
1600 goto do_unmount;
+
1601
+
1602 if(commfd == NULL)
+
1603 commfd = getenv(FUSE_COMMFD_ENV);
+
1604 if (commfd == NULL) {
+
1605 fprintf(stderr, "%s: old style mounting not supported\n",
+
1606 progname);
+
1607 goto err_out;
+
1608 }
+
1609
+
1610 res = libfuse_strtol(commfd, &cfd);
+
1611 if (res) {
+
1612 fprintf(stderr,
+
1613 "%s: invalid _FUSE_COMMFD: %s\n",
+
1614 progname, commfd);
+
1615 goto err_out;
+
1616
+
1617 }
+
1618 {
+
1619 struct stat statbuf;
+
1620 fstat(cfd, &statbuf);
+
1621 if(!S_ISSOCK(statbuf.st_mode)) {
+
1622 fprintf(stderr,
+
1623 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1624 progname, cfd);
+
1625 goto err_out;
+
1626 }
+
1627 }
+
1628
+
1629 if (setup_auto_unmount_only)
+
1630 goto wait_for_auto_unmount;
+
1631
+
1632 fd = mount_fuse(mnt, opts, &type);
+
1633 if (fd == -1)
+
1634 goto err_out;
+
1635
+
1636 res = send_fd(cfd, fd);
+
1637 if (res != 0) {
+
1638 umount2(mnt, MNT_DETACH); /* lazy umount */
+
1639 goto err_out;
+
1640 }
+
1641 close(fd);
+
1642
+
1643 if (!auto_unmount) {
+
1644 free(mnt);
+
1645 free((void*) type);
+
1646 return 0;
+
1647 }
+
1648
+
1649wait_for_auto_unmount:
+
1650 /* Become a daemon and wait for the parent to exit or die.
+
1651 ie For the control socket to get closed.
+
1652 Btw, we don't want to use daemon() function here because
+
1653 it forks and messes with the file descriptors. */
+
1654
+
1655 close_inherited_fds(cfd);
+
1656
+
1657 setsid();
+
1658 res = chdir("/");
+
1659 if (res == -1) {
+
1660 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1661 goto err_out;
+
1662 }
+
1663
+
1664 sigfillset(&sigset);
+
1665 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1666
+
1667 lazy = 1;
+
1668 quiet = 1;
+
1669
+
1670 while (1) {
+
1671 unsigned char buf[16];
+
1672 int n = recv(cfd, buf, sizeof(buf), 0);
+
1673 if (!n)
+
1674 break;
+
1675
+
1676 if (n < 0) {
+
1677 if (errno == EINTR)
+
1678 continue;
+
1679 break;
+
1680 }
+
1681 }
+
1682
+
1683 if (!should_auto_unmount(mnt, type)) {
+
1684 goto success_out;
+
1685 }
+
1686
+
1687do_unmount:
+
1688 if (geteuid() == 0)
+
1689 res = unmount_fuse(mnt, quiet, lazy);
+
1690 else {
+
1691 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+
1692 if (res == -1 && !quiet)
+
1693 fprintf(stderr,
+
1694 "%s: failed to unmount %s: %s\n",
+
1695 progname, mnt, strerror(errno));
+
1696 }
+
1697 if (res == -1)
+
1698 goto err_out;
+
1699
+
1700success_out:
+
1701 free((void*) type);
+
1702 free(mnt);
+
1703 return 0;
+
1704
+
1705err_out:
+
1706 free((void*) type);
+
1707 free(mnt);
+
1708 exit(1);
+
1709}
+
+ + + + diff --git a/doc/html/fuse-3_817_81-rc1_2util_2mount_8fuse_8c_source.html b/doc/html/fuse-3_817_81-rc1_2util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..9126da2 --- /dev/null +++ b/doc/html/fuse-3_817_81-rc1_2util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: fuse-3.17.1-rc1/util/mount.fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9#include "fuse_config.h"
+
10
+
11#include <stdio.h>
+
12#include <stdlib.h>
+
13#include <string.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16#include <stdint.h>
+
17#include <fcntl.h>
+
18#include <pwd.h>
+
19#include <sys/wait.h>
+
20
+
21#ifdef linux
+
22#include <sys/prctl.h>
+
23#include <sys/syscall.h>
+
24#include <linux/capability.h>
+
25#include <linux/securebits.h>
+
26/* for 2.6 kernels */
+
27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
+
28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
+
29#endif
+
30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
+
31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
+
32#endif
+
33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
+
34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
+
35#endif
+
36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
+
37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
+
38#endif
+
39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
+
40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
+
41#endif
+
42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
+
43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
+
44#endif
+
45#endif
+
46/* linux < 3.5 */
+
47#ifndef PR_SET_NO_NEW_PRIVS
+
48#define PR_SET_NO_NEW_PRIVS 38
+
49#endif
+
50
+
51#include "fuse.h"
+
52
+
53static char *progname;
+
54
+
55static char *xstrdup(const char *s)
+
56{
+
57 char *t = strdup(s);
+
58 if (!t) {
+
59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
60 exit(1);
+
61 }
+
62 return t;
+
63}
+
64
+
65static void *xrealloc(void *oldptr, size_t size)
+
66{
+
67 void *ptr = realloc(oldptr, size);
+
68 if (!ptr) {
+
69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
70 exit(1);
+
71 }
+
72 return ptr;
+
73}
+
74
+
75static void add_arg(char **cmdp, const char *opt)
+
76{
+
77 size_t optlen = strlen(opt);
+
78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
+
79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
+
80 fprintf(stderr, "%s: argument too long\n", progname);
+
81 exit(1);
+
82 }
+
83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
+
84 char *s;
+
85 s = cmd + cmdlen;
+
86 if (*cmdp)
+
87 *s++ = ' ';
+
88
+
89 *s++ = '\'';
+
90 for (; *opt; opt++) {
+
91 if (*opt == '\'') {
+
92 *s++ = '\'';
+
93 *s++ = '\\';
+
94 *s++ = '\'';
+
95 *s++ = '\'';
+
96 } else
+
97 *s++ = *opt;
+
98 }
+
99 *s++ = '\'';
+
100 *s = '\0';
+
101 *cmdp = cmd;
+
102}
+
103
+
104static char *add_option(const char *opt, char *options)
+
105{
+
106 int oldlen = options ? strlen(options) : 0;
+
107
+
108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
+
109 if (!oldlen)
+
110 strcpy(options, opt);
+
111 else {
+
112 strcat(options, ",");
+
113 strcat(options, opt);
+
114 }
+
115 return options;
+
116}
+
117
+
118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
+
119 const char *options)
+
120{
+
121 int fuse_fd = -1;
+
122 int flags = -1;
+
123 int subtype_len = strlen(subtype) + 9;
+
124 char* options_copy = xrealloc(NULL, subtype_len);
+
125
+
126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
+
127 options_copy = add_option(options, options_copy);
+
128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
+
129 if (fuse_fd == -1) {
+
130 exit(1);
+
131 }
+
132
+
133 flags = fcntl(fuse_fd, F_GETFD);
+
134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
+
135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
+
136 progname, strerror(errno));
+
137 exit(1);
+
138 }
+
139
+
140 return fuse_fd;
+
141}
+
142
+
143#ifdef linux
+
144static uint64_t get_capabilities(void)
+
145{
+
146 /*
+
147 * This invokes the capset syscall directly to avoid the libcap
+
148 * dependency, which isn't really justified just for this.
+
149 */
+
150 struct __user_cap_header_struct header = {
+
151 .version = _LINUX_CAPABILITY_VERSION_3,
+
152 .pid = 0,
+
153 };
+
154 struct __user_cap_data_struct data[2];
+
155 memset(data, 0, sizeof(data));
+
156 if (syscall(SYS_capget, &header, data) == -1) {
+
157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
+
158 progname, strerror(errno));
+
159 exit(1);
+
160 }
+
161
+
162 return data[0].effective | ((uint64_t) data[1].effective << 32);
+
163}
+
164
+
165static void set_capabilities(uint64_t caps)
+
166{
+
167 /*
+
168 * This invokes the capset syscall directly to avoid the libcap
+
169 * dependency, which isn't really justified just for this.
+
170 */
+
171 struct __user_cap_header_struct header = {
+
172 .version = _LINUX_CAPABILITY_VERSION_3,
+
173 .pid = 0,
+
174 };
+
175 struct __user_cap_data_struct data[2];
+
176 memset(data, 0, sizeof(data));
+
177 data[0].effective = data[0].permitted = caps;
+
178 data[1].effective = data[1].permitted = caps >> 32;
+
179 if (syscall(SYS_capset, &header, data) == -1) {
+
180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
+
181 progname, strerror(errno));
+
182 exit(1);
+
183 }
+
184}
+
185
+
186static void drop_and_lock_capabilities(void)
+
187{
+
188 /* Set and lock securebits. */
+
189 if (prctl(PR_SET_SECUREBITS,
+
190 SECBIT_KEEP_CAPS_LOCKED |
+
191 SECBIT_NO_SETUID_FIXUP |
+
192 SECBIT_NO_SETUID_FIXUP_LOCKED |
+
193 SECBIT_NOROOT |
+
194 SECBIT_NOROOT_LOCKED) == -1) {
+
195 fprintf(stderr, "%s: Failed to set securebits %s\n",
+
196 progname, strerror(errno));
+
197 exit(1);
+
198 }
+
199
+
200 /* Clear the capability bounding set. */
+
201 int cap;
+
202 for (cap = 0; ; cap++) {
+
203 int cap_status = prctl(PR_CAPBSET_READ, cap);
+
204 if (cap_status == 0) {
+
205 continue;
+
206 }
+
207 if (cap_status == -1 && errno == EINVAL) {
+
208 break;
+
209 }
+
210
+
211 if (cap_status != 1) {
+
212 fprintf(stderr,
+
213 "%s: Failed to get capability %u: %s\n",
+
214 progname, cap, strerror(errno));
+
215 exit(1);
+
216 }
+
217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
+
218 fprintf(stderr,
+
219 "%s: Failed to drop capability %u: %s\n",
+
220 progname, cap, strerror(errno));
+
221 }
+
222 }
+
223
+
224 /* Drop capabilities. */
+
225 set_capabilities(0);
+
226
+
227 /* Prevent re-acquisition of privileges. */
+
228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+
229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
+
230 progname, strerror(errno));
+
231 exit(1);
+
232 }
+
233}
+
234#endif
+
235
+
236int main(int argc, char *argv[])
+
237{
+
238 char *type = NULL;
+
239 char *source;
+
240 char *dup_source = NULL;
+
241 const char *mountpoint;
+
242 char *basename;
+
243 char *options = NULL;
+
244 char *command = NULL;
+
245 char *setuid_name = NULL;
+
246 int i;
+
247 int dev = 1;
+
248 int suid = 1;
+
249 int pass_fuse_fd = 0;
+
250 int fuse_fd = 0;
+
251 int drop_privileges = 0;
+
252 char *dev_fd_mountpoint = NULL;
+
253
+
254 progname = argv[0];
+
255 basename = strrchr(argv[0], '/');
+
256 if (basename)
+
257 basename++;
+
258 else
+
259 basename = argv[0];
+
260
+
261 if (strncmp(basename, "mount.fuse.", 11) == 0)
+
262 type = basename + 11;
+
263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
+
264 type = basename + 14;
+
265
+
266 if (type && !type[0])
+
267 type = NULL;
+
268
+
269 if (argc < 3) {
+
270 fprintf(stderr,
+
271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
+
272 progname, type ? "source" : "type#[source]");
+
273 exit(1);
+
274 }
+
275
+
276 source = argv[1];
+
277 if (!source[0])
+
278 source = NULL;
+
279
+
280 mountpoint = argv[2];
+
281
+
282 for (i = 3; i < argc; i++) {
+
283 if (strcmp(argv[i], "-v") == 0) {
+
284 continue;
+
285 } else if (strcmp(argv[i], "-t") == 0) {
+
286 i++;
+
287
+
288 if (i == argc) {
+
289 fprintf(stderr,
+
290 "%s: missing argument to option '-t'\n",
+
291 progname);
+
292 exit(1);
+
293 }
+
294 type = argv[i];
+
295 if (strncmp(type, "fuse.", 5) == 0)
+
296 type += 5;
+
297 else if (strncmp(type, "fuseblk.", 8) == 0)
+
298 type += 8;
+
299
+
300 if (!type[0]) {
+
301 fprintf(stderr,
+
302 "%s: empty type given as argument to option '-t'\n",
+
303 progname);
+
304 exit(1);
+
305 }
+
306 } else if (strcmp(argv[i], "-o") == 0) {
+
307 char *opts;
+
308 char *opt;
+
309 i++;
+
310 if (i == argc)
+
311 break;
+
312
+
313 opts = xstrdup(argv[i]);
+
314 opt = strtok(opts, ",");
+
315 while (opt) {
+
316 int j;
+
317 int ignore = 0;
+
318 const char *ignore_opts[] = { "",
+
319 "user",
+
320 "nofail",
+
321 "nouser",
+
322 "users",
+
323 "auto",
+
324 "noauto",
+
325 "_netdev",
+
326 NULL};
+
327 if (strncmp(opt, "setuid=", 7) == 0) {
+
328 setuid_name = xstrdup(opt + 7);
+
329 ignore = 1;
+
330 } else if (strcmp(opt,
+
331 "drop_privileges") == 0) {
+
332 pass_fuse_fd = 1;
+
333 drop_privileges = 1;
+
334 ignore = 1;
+
335 }
+
336 for (j = 0; ignore_opts[j]; j++)
+
337 if (strcmp(opt, ignore_opts[j]) == 0)
+
338 ignore = 1;
+
339
+
340 if (!ignore) {
+
341 if (strcmp(opt, "nodev") == 0)
+
342 dev = 0;
+
343 else if (strcmp(opt, "nosuid") == 0)
+
344 suid = 0;
+
345
+
346 options = add_option(opt, options);
+
347 }
+
348 opt = strtok(NULL, ",");
+
349 }
+
350 free(opts);
+
351 }
+
352 }
+
353
+
354 if (drop_privileges) {
+
355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
+
356 CAP_TO_MASK(CAP_SYS_ADMIN);
+
357 if ((get_capabilities() & required_caps) != required_caps) {
+
358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
+
359 progname, progname);
+
360 exit(1);
+
361 }
+
362 }
+
363
+
364 if (dev)
+
365 options = add_option("dev", options);
+
366 if (suid)
+
367 options = add_option("suid", options);
+
368
+
369 if (!type) {
+
370 if (source) {
+
371 dup_source = xstrdup(source);
+
372 type = dup_source;
+
373 source = strchr(type, '#');
+
374 if (source)
+
375 *source++ = '\0';
+
376 if (!type[0]) {
+
377 fprintf(stderr, "%s: empty filesystem type\n",
+
378 progname);
+
379 exit(1);
+
380 }
+
381 } else {
+
382 fprintf(stderr, "%s: empty source\n", progname);
+
383 exit(1);
+
384 }
+
385 }
+
386
+
387 if (setuid_name && setuid_name[0]) {
+
388#ifdef linux
+
389 if (drop_privileges) {
+
390 /*
+
391 * Make securebits more permissive before calling
+
392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
+
393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
+
394 * have the side effect of dropping all capabilities,
+
395 * and we need to retain CAP_SETPCAP in order to drop
+
396 * all privileges before exec().
+
397 */
+
398 if (prctl(PR_SET_SECUREBITS,
+
399 SECBIT_KEEP_CAPS |
+
400 SECBIT_NO_SETUID_FIXUP) == -1) {
+
401 fprintf(stderr,
+
402 "%s: Failed to set securebits %s\n",
+
403 progname, strerror(errno));
+
404 exit(1);
+
405 }
+
406 }
+
407#endif
+
408
+
409 struct passwd *pwd = getpwnam(setuid_name);
+
410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
+
411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
+
412 progname, setuid_name, strerror(errno));
+
413 exit(1);
+
414 }
+
415 } else if (!getenv("HOME")) {
+
416 /* Hack to make filesystems work in the boot environment */
+
417 setenv("HOME", "/root", 0);
+
418 }
+
419
+
420 if (pass_fuse_fd) {
+
421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
+
422 dev_fd_mountpoint = xrealloc(NULL, 20);
+
423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
+
424 mountpoint = dev_fd_mountpoint;
+
425 }
+
426
+
427#ifdef linux
+
428 if (drop_privileges) {
+
429 drop_and_lock_capabilities();
+
430 }
+
431#endif
+
432 add_arg(&command, type);
+
433 if (source)
+
434 add_arg(&command, source);
+
435 add_arg(&command, mountpoint);
+
436 if (options) {
+
437 add_arg(&command, "-o");
+
438 add_arg(&command, options);
+
439 }
+
440
+
441 free(options);
+
442 free(dev_fd_mountpoint);
+
443 free(dup_source);
+
444 free(setuid_name);
+
445
+
446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+
447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+
448 strerror(errno));
+
449
+
450 if (pass_fuse_fd)
+
451 close(fuse_fd);
+
452 free(command);
+
453 return 1;
+
454}
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:479
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2build_2fuse__config_8h_source.html b/doc/html/fuse-3_817_81_8dir_2build_2fuse__config_8h_source.html new file mode 100644 index 0000000..95f552a --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2build_2fuse__config_8h_source.html @@ -0,0 +1,105 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/build/fuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define HAVE_BACKTRACE
+
9
+
10#define HAVE_CLOSE_RANGE
+
11
+
12#define HAVE_COPY_FILE_RANGE
+
13
+
14#define HAVE_FALLOCATE
+
15
+
16#define HAVE_FDATASYNC
+
17
+
18#define HAVE_FORK
+
19
+
20#define HAVE_FSTATAT
+
21
+
22#define HAVE_ICONV
+
23
+
24#define HAVE_OPENAT
+
25
+
26#define HAVE_PIPE2
+
27
+
28#define HAVE_POSIX_FALLOCATE
+
29
+
30#define HAVE_READLINKAT
+
31
+
32#define HAVE_SETXATTR
+
33
+
34#define HAVE_SPLICE
+
35
+
36#define HAVE_STRUCT_STAT_ST_ATIM
+
37
+
38#undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
39
+
40#define HAVE_UTIMENSAT
+
41
+
42#define HAVE_VMSPLICE
+
43
+
44#define PACKAGE_VERSION "3.17.1"
+
45
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2build_2libfuse__config_8h_source.html b/doc/html/fuse-3_817_81_8dir_2build_2libfuse__config_8h_source.html new file mode 100644 index 0000000..b5fee0f --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2build_2libfuse__config_8h_source.html @@ -0,0 +1,77 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/build/libfuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
libfuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define FUSE_HOTFIX_VERSION 1
+
9
+
10#define FUSE_MAJOR_VERSION 3
+
11
+
12#define FUSE_MINOR_VERSION 17
+
13
+
14#define FUSE_RC_VERSION
+
15
+
16#define LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS 1
+
17
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2build_2meson-private_2sanitycheckc_8c_source.html b/doc/html/fuse-3_817_81_8dir_2build_2meson-private_2sanitycheckc_8c_source.html new file mode 100644 index 0000000..433ac14 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2build_2meson-private_2sanitycheckc_8c_source.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/build/meson-private/sanitycheckc.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
sanitycheckc.c
+
+
+
1int main(void) { int class=0; return class; }
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2cuse_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2cuse_8c.html new file mode 100644 index 0000000..d48919c --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2cuse_8c.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/cuse.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2cuse_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2cuse_8c_source.html new file mode 100644 index 0000000..6306647 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2cuse_8c_source.html @@ -0,0 +1,395 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/cuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8
+
9*/
+
10
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+ +
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c.html new file mode 100644 index 0000000..fd72fc2 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/cuse_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+
+

Definition in file cuse_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c_source.html new file mode 100644 index 0000000..dd6ed4d --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2cuse__client_8c_source.html @@ -0,0 +1,188 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/cuse_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51const char *usage =
+
52"Usage: cuse_client FIOC_FILE COMMAND\n"
+
53"\n"
+
54"COMMANDS\n"
+
55" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
56" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
57" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
58"\n";
+
59
+
60static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
61 size_t *prev_size, size_t *new_size)
+
62{
+
63 struct fioc_rw_arg arg = { .offset = offset };
+
64 ssize_t ret;
+
65
+
66 arg.buf = calloc(1, size);
+
67 if (!arg.buf) {
+
68 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
69 return -1;
+
70 }
+
71
+
72 if (is_read) {
+
73 arg.size = size;
+
74 ret = ioctl(fd, FIOC_READ, &arg);
+
75 if (ret >= 0)
+
76 fwrite(arg.buf, 1, ret, stdout);
+
77 } else {
+
78 arg.size = fread(arg.buf, 1, size, stdin);
+
79 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
80 ret = ioctl(fd, FIOC_WRITE, &arg);
+
81 }
+
82
+
83 if (ret >= 0) {
+
84 *prev_size = arg.prev_size;
+
85 *new_size = arg.new_size;
+
86 } else
+
87 perror("ioctl");
+
88
+
89 free(arg.buf);
+
90 return ret;
+
91}
+
92
+
93int main(int argc, char **argv)
+
94{
+
95 size_t param[2] = { };
+
96 size_t size, prev_size = 0, new_size = 0;
+
97 char cmd;
+
98 int fd, i, rc;
+
99
+
100 if (argc < 3)
+
101 goto usage;
+
102
+
103 fd = open(argv[1], O_RDWR);
+
104 if (fd < 0) {
+
105 perror("open");
+
106 return 1;
+
107 }
+
108
+
109 cmd = tolower(argv[2][0]);
+
110 argc -= 3;
+
111 argv += 3;
+
112
+
113 for (i = 0; i < argc; i++) {
+
114 char *endp;
+
115 param[i] = strtoul(argv[i], &endp, 0);
+
116 if (endp == argv[i] || *endp != '\0')
+
117 goto usage;
+
118 }
+
119
+
120 switch (cmd) {
+
121 case 's':
+
122 if (!argc) {
+
123 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
124 perror("ioctl");
+
125 goto error;
+
126 }
+
127 printf("%zu\n", size);
+
128 } else {
+
129 size = param[0];
+
130 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
131 perror("ioctl");
+
132 goto error;
+
133 }
+
134 }
+
135 close(fd);
+
136 return 0;
+
137
+
138 case 'r':
+
139 case 'w':
+
140 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
141 &prev_size, &new_size);
+
142 if (rc < 0)
+
143 goto error;
+
144 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
145 rc, prev_size, new_size);
+
146 close(fd);
+
147 return 0;
+
148 }
+
149
+
150usage:
+
151 fprintf(stderr, "%s", usage);
+
152 return 1;
+
153
+
154error:
+
155 close(fd);
+
156 return 1;
+
157}
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2hello_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2hello_8c.html new file mode 100644 index 0000000..4b4f38f --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2hello_8c.html @@ -0,0 +1,263 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+
fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+
fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ);
+
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2hello_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2hello_8c_source.html new file mode 100644 index 0000000..da1da46 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2hello_8c_source.html @@ -0,0 +1,253 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60
+
61 /* Test setting flags the old way */
+ +
63 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
64
+
65 return NULL;
+
66}
+
67
+
68static int hello_getattr(const char *path, struct stat *stbuf,
+
69 struct fuse_file_info *fi)
+
70{
+
71 (void) fi;
+
72 int res = 0;
+
73
+
74 memset(stbuf, 0, sizeof(struct stat));
+
75 if (strcmp(path, "/") == 0) {
+
76 stbuf->st_mode = S_IFDIR | 0755;
+
77 stbuf->st_nlink = 2;
+
78 } else if (strcmp(path+1, options.filename) == 0) {
+
79 stbuf->st_mode = S_IFREG | 0444;
+
80 stbuf->st_nlink = 1;
+
81 stbuf->st_size = strlen(options.contents);
+
82 } else
+
83 res = -ENOENT;
+
84
+
85 return res;
+
86}
+
87
+
88static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
89 off_t offset, struct fuse_file_info *fi,
+
90 enum fuse_readdir_flags flags)
+
91{
+
92 (void) offset;
+
93 (void) fi;
+
94 (void) flags;
+
95
+
96 if (strcmp(path, "/") != 0)
+
97 return -ENOENT;
+
98
+
99 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
100 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
101 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
102
+
103 return 0;
+
104}
+
105
+
106static int hello_open(const char *path, struct fuse_file_info *fi)
+
107{
+
108 if (strcmp(path+1, options.filename) != 0)
+
109 return -ENOENT;
+
110
+
111 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
112 return -EACCES;
+
113
+
114 return 0;
+
115}
+
116
+
117static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
118 struct fuse_file_info *fi)
+
119{
+
120 size_t len;
+
121 (void) fi;
+
122 if(strcmp(path+1, options.filename) != 0)
+
123 return -ENOENT;
+
124
+
125 len = strlen(options.contents);
+
126 if (offset < len) {
+
127 if (offset + size > len)
+
128 size = len - offset;
+
129 memcpy(buf, options.contents + offset, size);
+
130 } else
+
131 size = 0;
+
132
+
133 return size;
+
134}
+
135
+
136static const struct fuse_operations hello_oper = {
+
137 .init = hello_init,
+
138 .getattr = hello_getattr,
+
139 .readdir = hello_readdir,
+
140 .open = hello_open,
+
141 .read = hello_read,
+
142};
+
143
+
144static void show_help(const char *progname)
+
145{
+
146 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
147 printf("File-system specific options:\n"
+
148 " --name=<s> Name of the \"hello\" file\n"
+
149 " (default: \"hello\")\n"
+
150 " --contents=<s> Contents \"hello\" file\n"
+
151 " (default \"Hello, World!\\n\")\n"
+
152 "\n");
+
153}
+
154
+
155int main(int argc, char *argv[])
+
156{
+
157 int ret;
+
158 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
159
+
160 /* Set defaults -- we have to use strdup so that
+
161 fuse_opt_parse can free the defaults if other
+
162 values are specified */
+
163 options.filename = strdup("hello");
+
164 options.contents = strdup("Hello World!\n");
+
165
+
166 /* Parse options */
+
167 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
168 return 1;
+
169
+
170 /* When --help is specified, first print our own file-system
+
171 specific help text, then signal fuse_main to show
+
172 additional help (by adding `--help` to the options again)
+
173 without usage: line (by setting argv[0] to the empty
+
174 string) */
+
175 if (options.show_help) {
+
176 show_help(argv[0]);
+
177 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
178 args.argv[0][0] = '\0';
+
179 }
+
180
+
181 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
182 fuse_opt_free_args(&args);
+
183 return ret;
+
184}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c.html new file mode 100644 index 0000000..ea85b0c --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c.html @@ -0,0 +1,388 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c_source.html new file mode 100644 index 0000000..5efb5e8 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll_8c_source.html @@ -0,0 +1,376 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
21#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
22
+
23#include <fuse_lowlevel.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <unistd.h>
+
30#include <assert.h>
+
31
+
32static const char *hello_str = "Hello World!\n";
+
33static const char *hello_name = "hello";
+
34
+
35static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
36{
+
37 stbuf->st_ino = ino;
+
38 switch (ino) {
+
39 case 1:
+
40 stbuf->st_mode = S_IFDIR | 0755;
+
41 stbuf->st_nlink = 2;
+
42 break;
+
43
+
44 case 2:
+
45 stbuf->st_mode = S_IFREG | 0444;
+
46 stbuf->st_nlink = 1;
+
47 stbuf->st_size = strlen(hello_str);
+
48 break;
+
49
+
50 default:
+
51 return -1;
+
52 }
+
53 return 0;
+
54}
+
55
+
56static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
57{
+
58 (void)userdata;
+
59
+
60 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
61 conn->no_interrupt = 1;
+
62
+
63 /* Test setting flags the old way */
+ +
65 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
66}
+
67
+
68static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
69 struct fuse_file_info *fi)
+
70{
+
71 struct stat stbuf;
+
72
+
73 (void) fi;
+
74
+
75 memset(&stbuf, 0, sizeof(stbuf));
+
76 if (hello_stat(ino, &stbuf) == -1)
+
77 fuse_reply_err(req, ENOENT);
+
78 else
+
79 fuse_reply_attr(req, &stbuf, 1.0);
+
80}
+
81
+
82static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
83{
+
84 struct fuse_entry_param e;
+
85
+
86 if (parent != 1 || strcmp(name, hello_name) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else {
+
89 memset(&e, 0, sizeof(e));
+
90 e.ino = 2;
+
91 e.attr_timeout = 1.0;
+
92 e.entry_timeout = 1.0;
+
93 hello_stat(e.ino, &e.attr);
+
94
+
95 fuse_reply_entry(req, &e);
+
96 }
+
97}
+
98
+
99struct dirbuf {
+
100 char *p;
+
101 size_t size;
+
102};
+
103
+
104static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
105 fuse_ino_t ino)
+
106{
+
107 struct stat stbuf;
+
108 size_t oldsize = b->size;
+
109 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
110 b->p = (char *) realloc(b->p, b->size);
+
111 memset(&stbuf, 0, sizeof(stbuf));
+
112 stbuf.st_ino = ino;
+
113 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
114 b->size);
+
115}
+
116
+
117#define min(x, y) ((x) < (y) ? (x) : (y))
+
118
+
119static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
120 off_t off, size_t maxsize)
+
121{
+
122 if (off < bufsize)
+
123 return fuse_reply_buf(req, buf + off,
+
124 min(bufsize - off, maxsize));
+
125 else
+
126 return fuse_reply_buf(req, NULL, 0);
+
127}
+
128
+
129static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
130 off_t off, struct fuse_file_info *fi)
+
131{
+
132 (void) fi;
+
133
+
134 if (ino != 1)
+
135 fuse_reply_err(req, ENOTDIR);
+
136 else {
+
137 struct dirbuf b;
+
138
+
139 memset(&b, 0, sizeof(b));
+
140 dirbuf_add(req, &b, ".", 1);
+
141 dirbuf_add(req, &b, "..", 1);
+
142 dirbuf_add(req, &b, hello_name, 2);
+
143 reply_buf_limited(req, b.p, b.size, off, size);
+
144 free(b.p);
+
145 }
+
146}
+
147
+
148static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
149 struct fuse_file_info *fi)
+
150{
+
151 if (ino != 2)
+
152 fuse_reply_err(req, EISDIR);
+
153 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
154 fuse_reply_err(req, EACCES);
+
155 else
+
156 fuse_reply_open(req, fi);
+
157}
+
158
+
159static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
160 off_t off, struct fuse_file_info *fi)
+
161{
+
162 (void) fi;
+
163
+
164 assert(ino == 2);
+
165 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
166}
+
167
+
168static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
169 size_t size)
+
170{
+
171 (void)size;
+
172 assert(ino == 1 || ino == 2);
+
173 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
174 {
+
175 const char *buf = "hello_ll_getxattr_value";
+
176 fuse_reply_buf(req, buf, strlen(buf));
+
177 }
+
178 else
+
179 {
+
180 fuse_reply_err(req, ENOTSUP);
+
181 }
+
182}
+
183
+
184static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
185 const char *value, size_t size, int flags)
+
186{
+
187 (void)flags;
+
188 (void)size;
+
189 assert(ino == 1 || ino == 2);
+
190 const char* exp_val = "hello_ll_setxattr_value";
+
191 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
192 strlen(exp_val) == size &&
+
193 strncmp(value, exp_val, size) == 0)
+
194 {
+
195 fuse_reply_err(req, 0);
+
196 }
+
197 else
+
198 {
+
199 fuse_reply_err(req, ENOTSUP);
+
200 }
+
201}
+
202
+
203static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
204{
+
205 assert(ino == 1 || ino == 2);
+
206 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
207 {
+
208 fuse_reply_err(req, 0);
+
209 }
+
210 else
+
211 {
+
212 fuse_reply_err(req, ENOTSUP);
+
213 }
+
214}
+
215
+
216static const struct fuse_lowlevel_ops hello_ll_oper = {
+
217 .init = hello_ll_init,
+
218 .lookup = hello_ll_lookup,
+
219 .getattr = hello_ll_getattr,
+
220 .readdir = hello_ll_readdir,
+
221 .open = hello_ll_open,
+
222 .read = hello_ll_read,
+
223 .setxattr = hello_ll_setxattr,
+
224 .getxattr = hello_ll_getxattr,
+
225 .removexattr = hello_ll_removexattr,
+
226};
+
227
+
228int main(int argc, char *argv[])
+
229{
+
230 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
231 struct fuse_session *se;
+
232 struct fuse_cmdline_opts opts;
+
233 struct fuse_loop_config *config;
+
234 int ret = -1;
+
235
+
236 if (fuse_parse_cmdline(&args, &opts) != 0)
+
237 return 1;
+
238 if (opts.show_help) {
+
239 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
242 ret = 0;
+
243 goto err_out1;
+
244 } else if (opts.show_version) {
+
245 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
247 ret = 0;
+
248 goto err_out1;
+
249 }
+
250
+
251 if(opts.mountpoint == NULL) {
+
252 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
253 printf(" %s --help\n", argv[0]);
+
254 ret = 1;
+
255 goto err_out1;
+
256 }
+
257
+
258 se = fuse_session_new(&args, &hello_ll_oper,
+
259 sizeof(hello_ll_oper), NULL);
+
260 if (se == NULL)
+
261 goto err_out1;
+
262
+
263 if (fuse_set_signal_handlers(se) != 0)
+
264 goto err_out2;
+
265
+
266 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
267 goto err_out3;
+
268
+
269 fuse_daemonize(opts.foreground);
+
270
+
271 /* Block until ctrl+c or fusermount -u */
+
272 if (opts.singlethread)
+
273 ret = fuse_session_loop(se);
+
274 else {
+
275 config = fuse_loop_cfg_create();
+
276 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
277 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
278 ret = fuse_session_loop_mt(se, config);
+
279 fuse_loop_cfg_destroy(config);
+
280 config = NULL;
+
281 }
+
282
+ +
284err_out3:
+ +
286err_out2:
+ +
288err_out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291
+
292 return ret ? 1 : 0;
+
293}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c.html new file mode 100644 index 0000000..b45aded --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c.html @@ -0,0 +1,391 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c_source.html new file mode 100644 index 0000000..9e4f8b7 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2hello__ll__uds_8c_source.html @@ -0,0 +1,442 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %zu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c.html new file mode 100644 index 0000000..f2bbd94 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c.html @@ -0,0 +1,390 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c_source.html new file mode 100644 index 0000000..e804a24 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2invalidate__path_8c_source.html @@ -0,0 +1,370 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:77
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c.html new file mode 100644 index 0000000..60f2a97 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c.html @@ -0,0 +1,294 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c_source.html new file mode 100644 index 0000000..47245fd --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h.html b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h.html new file mode 100644 index 0000000..2c5ea50 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h_source.html b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h_source.html new file mode 100644 index 0000000..fc1a894 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c.html new file mode 100644 index 0000000..4e96fe5 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c.html @@ -0,0 +1,138 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program tests the ioctl.c example file systsem.
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c_source.html new file mode 100644 index 0000000..b56754b --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2ioctl__client_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program tests the ioctl.c example file systsem.
+
7
+
8 This program can be distributed under the terms of the GNU GPLv2.
+
9 See the file COPYING.
+
10*/
+
11
+
24#include <sys/types.h>
+
25#include <fcntl.h>
+
26#include <sys/stat.h>
+
27#include <sys/ioctl.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <ctype.h>
+
31#include <errno.h>
+
32#include <unistd.h>
+
33#include "ioctl.h"
+
34
+
35const char *usage =
+
36"Usage: fioclient FIOC_FILE [size]\n"
+
37"\n"
+
38"Get size if <size> is omitted, set size otherwise\n"
+
39"\n";
+
40
+
41int main(int argc, char **argv)
+
42{
+
43 size_t size;
+
44 int fd;
+
45 int ret = 0;
+
46
+
47 if (argc < 2) {
+
48 fprintf(stderr, "%s", usage);
+
49 return 1;
+
50 }
+
51
+
52 fd = open(argv[1], O_RDWR);
+
53 if (fd < 0) {
+
54 perror("open");
+
55 return 1;
+
56 }
+
57
+
58 if (argc == 2) {
+
59 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
60 perror("ioctl");
+
61 ret = 1;
+
62 goto out;
+
63 }
+
64 printf("%zu\n", size);
+
65 } else {
+
66 size = strtoul(argv[2], NULL, 0);
+
67 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
68 perror("ioctl");
+
69 ret = 1;
+
70 goto out;
+
71 }
+
72 }
+
73out:
+
74 close(fd);
+
75 return ret;
+
76}
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c.html new file mode 100644 index 0000000..63cad16 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c.html @@ -0,0 +1,474 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/notify_inval_entry.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_entry.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

+

It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

+

To see the effect, first start the file system with the --no-notify

$ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
+

Observe that ls always prints the correct directory contents (since readdir output is not cached)::

$ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+Time_is_15h_48m_33s  current_time
+Time_is_15h_48m_34s  current_time
+Time_is_15h_48m_35s  current_time
+

However, if you try to access a file by name the kernel will report that it still exists:

$ file=$(ls mnt/); echo $file
+Time_is_15h_50m_09s
+$ sleep 5; stat mnt/$file
+  File: ‘mnt/Time_is_15h_50m_09s’
+  Size: 32                Blocks: 0          IO Block: 4096   regular file
+Device: 2ah/42d     Inode: 3           Links: 1
+Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+

Only once the kernel cache timeout has been reached will the stat call fail:

$ sleep 30; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+

In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

$ notify_inval_entry --update-interval=1 --timeout=30 mnt/
+$ file=$(ls mnt/); stat mnt/$file
+  File: ‘mnt/Time_is_20h_42m_11s’
+  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+Device: 2ah/42d     Inode: 2           Links: 1
+Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+Access: 1969-12-31 16:00:00.000000000 -0800
+Modify: 1969-12-31 16:00:00.000000000 -0800
+Change: 1969-12-31 16:00:00.000000000 -0800
+ Birth: -
+$ sleep 1; stat mnt/$file
+stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+

To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

+

+Compilation

+
gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <signal.h>
+
#include <stddef.h>
+
#include <sys/stat.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
#define MAX_STR_LEN 128
+
static char file_name[MAX_STR_LEN];
+
static fuse_ino_t file_ino = 2;
+
static int lookup_cnt = 0;
+
static pthread_t main_thread;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
float timeout;
+
int update_interval;
+
int only_expire;
+
};
+
static struct options options = {
+
.timeout = 5,
+
.no_notify = 0,
+
.update_interval = 1,
+
.only_expire = 0,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+
OPTION("--timeout=%f", timeout),
+
OPTION("--only-expire", only_expire),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == file_ino) {
+
stbuf->st_mode = S_IFREG | 0000;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = 0;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, file_name) == 0) {
+
e.ino = file_ino;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = options.timeout;
+
e.entry_timeout = options.timeout;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == file_ino)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, options.timeout);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, file_name, file_ino);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
time_t t;
+
struct tm *now;
+
ssize_t ret;
+
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
ret = strftime(file_name, MAX_STR_LEN,
+
"Time_is_%Hh_%Mm_%Ss", now);
+
assert(ret != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
char *old_name;
+
+
+
while(!fuse_session_exited(se)) {
+
old_name = strdup(file_name);
+
update_fs();
+
+
if (!options.no_notify && lookup_cnt) {
+
if(options.only_expire) { // expire entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
+
// no kernel support
+
if (ret == -ENOSYS) {
+
printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
printf("Exiting...\n");
+
+ +
// Make sure to exit now, rather than on next request from userspace
+
pthread_kill(main_thread, SIGPIPE);
+
+
break;
+
}
+
// 1) ret == 0: successful expire of an existing entry
+
// 2) ret == -ENOENT: kernel has already expired the entry /
+
// entry does not exist anymore in the kernel
+
assert(ret == 0 || ret == -ENOENT);
+
} else { // invalidate entry
+ +
(se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
}
+
}
+
free(old_name);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --timeout=<secs> Timeout for kernel caches\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
" --only-expire Expire entries instead of invalidating them\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), &se);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
// Needed to ensure that the main thread continues/restarts processing as soon
+
// as the fuse session ends (immediately after calling fuse_session_exit() )
+
// and not only on the next request from userspace
+
main_thread = pthread_self();
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread) {
+
ret = fuse_session_loop(se);
+
} else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_entry.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c_source.html new file mode 100644 index 0000000..c789670 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__entry_8c_source.html @@ -0,0 +1,424 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/notify_inval_entry.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_entry.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
79#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
80
+
81#include <fuse_lowlevel.h>
+
82#include <stdio.h>
+
83#include <stdlib.h>
+
84#include <string.h>
+
85#include <errno.h>
+
86#include <fcntl.h>
+
87#include <assert.h>
+
88#include <signal.h>
+
89#include <stddef.h>
+
90#include <sys/stat.h>
+
91#include <unistd.h>
+
92#include <pthread.h>
+
93
+
94#define MAX_STR_LEN 128
+
95static char file_name[MAX_STR_LEN];
+
96static fuse_ino_t file_ino = 2;
+
97static int lookup_cnt = 0;
+
98static pthread_t main_thread;
+
99
+
100/* Command line parsing */
+
101struct options {
+
102 int no_notify;
+
103 float timeout;
+
104 int update_interval;
+
105 int only_expire;
+
106};
+
107static struct options options = {
+
108 .timeout = 5,
+
109 .no_notify = 0,
+
110 .update_interval = 1,
+
111 .only_expire = 0,
+
112};
+
113
+
114#define OPTION(t, p) \
+
115 { t, offsetof(struct options, p), 1 }
+
116static const struct fuse_opt option_spec[] = {
+
117 OPTION("--no-notify", no_notify),
+
118 OPTION("--update-interval=%d", update_interval),
+
119 OPTION("--timeout=%f", timeout),
+
120 OPTION("--only-expire", only_expire),
+ +
122};
+
123
+
124static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
125 stbuf->st_ino = ino;
+
126 if (ino == FUSE_ROOT_ID) {
+
127 stbuf->st_mode = S_IFDIR | 0755;
+
128 stbuf->st_nlink = 1;
+
129 }
+
130
+
131 else if (ino == file_ino) {
+
132 stbuf->st_mode = S_IFREG | 0000;
+
133 stbuf->st_nlink = 1;
+
134 stbuf->st_size = 0;
+
135 }
+
136
+
137 else
+
138 return -1;
+
139
+
140 return 0;
+
141}
+
142
+
143static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
144 (void)userdata;
+
145
+
146 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
147 conn->no_interrupt = 1;
+
148}
+
149
+
150static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
151 const char *name) {
+
152 struct fuse_entry_param e;
+
153 memset(&e, 0, sizeof(e));
+
154
+
155 if (parent != FUSE_ROOT_ID)
+
156 goto err_out;
+
157 else if (strcmp(name, file_name) == 0) {
+
158 e.ino = file_ino;
+
159 lookup_cnt++;
+
160 } else
+
161 goto err_out;
+
162
+
163 e.attr_timeout = options.timeout;
+
164 e.entry_timeout = options.timeout;
+
165 if (tfs_stat(e.ino, &e.attr) != 0)
+
166 goto err_out;
+
167 fuse_reply_entry(req, &e);
+
168 return;
+
169
+
170err_out:
+
171 fuse_reply_err(req, ENOENT);
+
172}
+
173
+
174static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
175 uint64_t nlookup) {
+
176 (void) req;
+
177 if(ino == file_ino)
+
178 lookup_cnt -= nlookup;
+
179 else
+
180 assert(ino == FUSE_ROOT_ID);
+
181 fuse_reply_none(req);
+
182}
+
183
+
184static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
185 struct fuse_file_info *fi) {
+
186 struct stat stbuf;
+
187
+
188 (void) fi;
+
189
+
190 memset(&stbuf, 0, sizeof(stbuf));
+
191 if (tfs_stat(ino, &stbuf) != 0)
+
192 fuse_reply_err(req, ENOENT);
+
193 else
+
194 fuse_reply_attr(req, &stbuf, options.timeout);
+
195}
+
196
+
197struct dirbuf {
+
198 char *p;
+
199 size_t size;
+
200};
+
201
+
202static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
203 fuse_ino_t ino) {
+
204 struct stat stbuf;
+
205 size_t oldsize = b->size;
+
206 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
207 b->p = (char *) realloc(b->p, b->size);
+
208 memset(&stbuf, 0, sizeof(stbuf));
+
209 stbuf.st_ino = ino;
+
210 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
211 b->size);
+
212}
+
213
+
214#define min(x, y) ((x) < (y) ? (x) : (y))
+
215
+
216static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
217 off_t off, size_t maxsize) {
+
218 if (off < bufsize)
+
219 return fuse_reply_buf(req, buf + off,
+
220 min(bufsize - off, maxsize));
+
221 else
+
222 return fuse_reply_buf(req, NULL, 0);
+
223}
+
224
+
225static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
226 off_t off, struct fuse_file_info *fi) {
+
227 (void) fi;
+
228
+
229 if (ino != FUSE_ROOT_ID)
+
230 fuse_reply_err(req, ENOTDIR);
+
231 else {
+
232 struct dirbuf b;
+
233
+
234 memset(&b, 0, sizeof(b));
+
235 dirbuf_add(req, &b, file_name, file_ino);
+
236 reply_buf_limited(req, b.p, b.size, off, size);
+
237 free(b.p);
+
238 }
+
239}
+
240
+
241static const struct fuse_lowlevel_ops tfs_oper = {
+
242 .init = tfs_init,
+
243 .lookup = tfs_lookup,
+
244 .getattr = tfs_getattr,
+
245 .readdir = tfs_readdir,
+
246 .forget = tfs_forget,
+
247};
+
248
+
249static void update_fs(void) {
+
250 time_t t;
+
251 struct tm *now;
+
252 ssize_t ret;
+
253
+
254 t = time(NULL);
+
255 now = localtime(&t);
+
256 assert(now != NULL);
+
257
+
258 ret = strftime(file_name, MAX_STR_LEN,
+
259 "Time_is_%Hh_%Mm_%Ss", now);
+
260 assert(ret != 0);
+
261}
+
262
+
263static void* update_fs_loop(void *data) {
+
264 struct fuse_session *se = (struct fuse_session*) data;
+
265 char *old_name;
+
266
+
267
+
268 while(!fuse_session_exited(se)) {
+
269 old_name = strdup(file_name);
+
270 update_fs();
+
271
+
272 if (!options.no_notify && lookup_cnt) {
+
273 if(options.only_expire) { // expire entry
+ +
275 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
+
276
+
277 // no kernel support
+
278 if (ret == -ENOSYS) {
+
279 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
+
280 printf("Exiting...\n");
+
281
+ +
283 // Make sure to exit now, rather than on next request from userspace
+
284 pthread_kill(main_thread, SIGPIPE);
+
285
+
286 break;
+
287 }
+
288 // 1) ret == 0: successful expire of an existing entry
+
289 // 2) ret == -ENOENT: kernel has already expired the entry /
+
290 // entry does not exist anymore in the kernel
+
291 assert(ret == 0 || ret == -ENOENT);
+
292 } else { // invalidate entry
+ +
294 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+
295 }
+
296 }
+
297 free(old_name);
+
298 sleep(options.update_interval);
+
299 }
+
300 return NULL;
+
301}
+
302
+
303static void show_help(const char *progname)
+
304{
+
305 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
306 printf("File-system specific options:\n"
+
307 " --timeout=<secs> Timeout for kernel caches\n"
+
308 " --update-interval=<secs> Update-rate of file system contents\n"
+
309 " --no-notify Disable kernel notifications\n"
+
310 " --only-expire Expire entries instead of invalidating them\n"
+
311 "\n");
+
312}
+
313
+
314int main(int argc, char *argv[]) {
+
315 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
316 struct fuse_session *se;
+
317 struct fuse_cmdline_opts opts;
+
318 struct fuse_loop_config *config;
+
319 pthread_t updater;
+
320 int ret = -1;
+
321
+
322 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
323 return 1;
+
324
+
325 if (fuse_parse_cmdline(&args, &opts) != 0)
+
326 return 1;
+
327 if (opts.show_help) {
+
328 show_help(argv[0]);
+ + +
331 ret = 0;
+
332 goto err_out1;
+
333 } else if (opts.show_version) {
+
334 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
336 ret = 0;
+
337 goto err_out1;
+
338 }
+
339
+
340 /* Initial contents */
+
341 update_fs();
+
342
+
343 se = fuse_session_new(&args, &tfs_oper,
+
344 sizeof(tfs_oper), &se);
+
345 if (se == NULL)
+
346 goto err_out1;
+
347
+
348 if (fuse_set_signal_handlers(se) != 0)
+
349 goto err_out2;
+
350
+
351 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
352 goto err_out3;
+
353
+
354 fuse_daemonize(opts.foreground);
+
355
+
356 // Needed to ensure that the main thread continues/restarts processing as soon
+
357 // as the fuse session ends (immediately after calling fuse_session_exit() )
+
358 // and not only on the next request from userspace
+
359 main_thread = pthread_self();
+
360
+
361 /* Start thread to update file contents */
+
362 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
363 if (ret != 0) {
+
364 fprintf(stderr, "pthread_create failed with %s\n",
+
365 strerror(ret));
+
366 goto err_out3;
+
367 }
+
368
+
369 /* Block until ctrl+c or fusermount -u */
+
370 if (opts.singlethread) {
+
371 ret = fuse_session_loop(se);
+
372 } else {
+
373 config = fuse_loop_cfg_create();
+
374 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
375 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
376 ret = fuse_session_loop_mt(se, config);
+
377 fuse_loop_cfg_destroy(config);
+
378 config = NULL;
+
379 }
+
380
+ +
382err_out3:
+ +
384err_out2:
+ +
386err_out1:
+
387 free(opts.mountpoint);
+
388 fuse_opt_free_args(&args);
+
389
+
390 return ret ? 1 : 0;
+
391}
+
392
+
393
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c.html new file mode 100644 index 0000000..05e62de --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c.html @@ -0,0 +1,483 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/notify_inval_inode.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_inval_inode.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

+

To see the effect, first start the file system with the --no-notify option:

+

$ notify_inval_inode –update-interval=1 –no-notify mnt/

+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

 $ notify_inval_inode --update-interval=1 mnt/
+ $ for i in 1 2 3 4 5; do
+ >     cat mnt/current_time
+ >     sleep 1
+ > done
+ The current time is 15:58:40
+ The current time is 15:58:41
+ The current time is 15:58:42
+ The current time is 15:58:43
+ The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
#include <stdatomic.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static size_t file_size;
+
static _Atomic bool is_stop = false;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_destroy(void *userarg)
+
{
+
(void)userarg;
+
+
is_stop = true;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
lookup_cnt++;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO)
+
lookup_cnt -= nlookup;
+
else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO)
+
fuse_reply_open(req, fi);
+
else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.destroy = tfs_destroy,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
+
while(!is_stop) {
+
update_fs();
+
if (!options.no_notify && lookup_cnt) {
+
/* Only send notification if the kernel is aware of the inode */
+
+
/* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
int ret =
+
fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
if ((ret != 0 && !is_stop) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
pthread_t updater;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0) {
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+ +
free(opts.mountpoint);
+
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_inval_inode.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c_source.html new file mode 100644 index 0000000..9280aa7 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2notify__inval__inode_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/notify_inval_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_inval_inode.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
63
+
64#include <fuse_lowlevel.h>
+
65#include <stdio.h>
+
66#include <stdlib.h>
+
67#include <string.h>
+
68#include <errno.h>
+
69#include <fcntl.h>
+
70#include <assert.h>
+
71#include <stddef.h>
+
72#include <unistd.h>
+
73#include <pthread.h>
+
74#include <stdbool.h>
+
75#include <stdatomic.h>
+
76
+
77/* We can't actually tell the kernel that there is no
+
78 timeout, so we just send a big value */
+
79#define NO_TIMEOUT 500000
+
80
+
81#define MAX_STR_LEN 128
+
82#define FILE_INO 2
+
83#define FILE_NAME "current_time"
+
84static char file_contents[MAX_STR_LEN];
+
85static int lookup_cnt = 0;
+
86static size_t file_size;
+
87static _Atomic bool is_stop = false;
+
88
+
89/* Command line parsing */
+
90struct options {
+
91 int no_notify;
+
92 int update_interval;
+
93};
+
94static struct options options = {
+
95 .no_notify = 0,
+
96 .update_interval = 1,
+
97};
+
98
+
99#define OPTION(t, p) \
+
100 { t, offsetof(struct options, p), 1 }
+
101static const struct fuse_opt option_spec[] = {
+
102 OPTION("--no-notify", no_notify),
+
103 OPTION("--update-interval=%d", update_interval),
+ +
105};
+
106
+
107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
108 stbuf->st_ino = ino;
+
109 if (ino == FUSE_ROOT_ID) {
+
110 stbuf->st_mode = S_IFDIR | 0755;
+
111 stbuf->st_nlink = 1;
+
112 }
+
113
+
114 else if (ino == FILE_INO) {
+
115 stbuf->st_mode = S_IFREG | 0444;
+
116 stbuf->st_nlink = 1;
+
117 stbuf->st_size = file_size;
+
118 }
+
119
+
120 else
+
121 return -1;
+
122
+
123 return 0;
+
124}
+
125
+
126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
127 (void)userdata;
+
128
+
129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
130 conn->no_interrupt = 1;
+
131}
+
132
+
133static void tfs_destroy(void *userarg)
+
134{
+
135 (void)userarg;
+
136
+
137 is_stop = true;
+
138}
+
139
+
140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
141 const char *name) {
+
142 struct fuse_entry_param e;
+
143 memset(&e, 0, sizeof(e));
+
144
+
145 if (parent != FUSE_ROOT_ID)
+
146 goto err_out;
+
147 else if (strcmp(name, FILE_NAME) == 0) {
+
148 e.ino = FILE_INO;
+
149 lookup_cnt++;
+
150 } else
+
151 goto err_out;
+
152
+
153 e.attr_timeout = NO_TIMEOUT;
+
154 e.entry_timeout = NO_TIMEOUT;
+
155 if (tfs_stat(e.ino, &e.attr) != 0)
+
156 goto err_out;
+
157 fuse_reply_entry(req, &e);
+
158 return;
+
159
+
160err_out:
+
161 fuse_reply_err(req, ENOENT);
+
162}
+
163
+
164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
165 uint64_t nlookup) {
+
166 (void) req;
+
167 if(ino == FILE_INO)
+
168 lookup_cnt -= nlookup;
+
169 else
+
170 assert(ino == FUSE_ROOT_ID);
+
171 fuse_reply_none(req);
+
172}
+
173
+
174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
175 struct fuse_file_info *fi) {
+
176 struct stat stbuf;
+
177
+
178 (void) fi;
+
179
+
180 memset(&stbuf, 0, sizeof(stbuf));
+
181 if (tfs_stat(ino, &stbuf) != 0)
+
182 fuse_reply_err(req, ENOENT);
+
183 else
+
184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
185}
+
186
+
187struct dirbuf {
+
188 char *p;
+
189 size_t size;
+
190};
+
191
+
192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
193 fuse_ino_t ino) {
+
194 struct stat stbuf;
+
195 size_t oldsize = b->size;
+
196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
197 b->p = (char *) realloc(b->p, b->size);
+
198 memset(&stbuf, 0, sizeof(stbuf));
+
199 stbuf.st_ino = ino;
+
200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
201 b->size);
+
202}
+
203
+
204#define min(x, y) ((x) < (y) ? (x) : (y))
+
205
+
206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
207 off_t off, size_t maxsize) {
+
208 if (off < bufsize)
+
209 return fuse_reply_buf(req, buf + off,
+
210 min(bufsize - off, maxsize));
+
211 else
+
212 return fuse_reply_buf(req, NULL, 0);
+
213}
+
214
+
215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
216 off_t off, struct fuse_file_info *fi) {
+
217 (void) fi;
+
218
+
219 if (ino != FUSE_ROOT_ID)
+
220 fuse_reply_err(req, ENOTDIR);
+
221 else {
+
222 struct dirbuf b;
+
223
+
224 memset(&b, 0, sizeof(b));
+
225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
226 reply_buf_limited(req, b.p, b.size, off, size);
+
227 free(b.p);
+
228 }
+
229}
+
230
+
231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
232 struct fuse_file_info *fi) {
+
233
+
234 /* Make cache persistent even if file is closed,
+
235 this makes it easier to see the effects */
+
236 fi->keep_cache = 1;
+
237
+
238 if (ino == FUSE_ROOT_ID)
+
239 fuse_reply_err(req, EISDIR);
+
240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
241 fuse_reply_err(req, EACCES);
+
242 else if (ino == FILE_INO)
+
243 fuse_reply_open(req, fi);
+
244 else {
+
245 // This should not happen
+
246 fprintf(stderr, "Got open for non-existing inode!\n");
+
247 fuse_reply_err(req, ENOENT);
+
248 }
+
249}
+
250
+
251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
252 off_t off, struct fuse_file_info *fi) {
+
253 (void) fi;
+
254
+
255 assert(ino == FILE_INO);
+
256 reply_buf_limited(req, file_contents, file_size, off, size);
+
257}
+
258
+
259static const struct fuse_lowlevel_ops tfs_oper = {
+
260 .init = tfs_init,
+
261 .destroy = tfs_destroy,
+
262 .lookup = tfs_lookup,
+
263 .getattr = tfs_getattr,
+
264 .readdir = tfs_readdir,
+
265 .open = tfs_open,
+
266 .read = tfs_read,
+
267 .forget = tfs_forget,
+
268};
+
269
+
270static void update_fs(void) {
+
271 struct tm *now;
+
272 time_t t;
+
273 t = time(NULL);
+
274 now = localtime(&t);
+
275 assert(now != NULL);
+
276
+
277 file_size = strftime(file_contents, MAX_STR_LEN,
+
278 "The current time is %H:%M:%S\n", now);
+
279 assert(file_size != 0);
+
280}
+
281
+
282static void* update_fs_loop(void *data) {
+
283 struct fuse_session *se = (struct fuse_session*) data;
+
284
+
285 while(!is_stop) {
+
286 update_fs();
+
287 if (!options.no_notify && lookup_cnt) {
+
288 /* Only send notification if the kernel is aware of the inode */
+
289
+
290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
291 * might come up during umount, when kernel side already releases
+
292 * all inodes, but does not send FUSE_DESTROY yet.
+
293 */
+
294 int ret =
+
295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
+
296 if ((ret != 0 && !is_stop) &&
+
297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
298 fprintf(stderr,
+
299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
300 strerror(-ret), -ret);
+
301 abort();
+
302 }
+
303 }
+
304 sleep(options.update_interval);
+
305 }
+
306 return NULL;
+
307}
+
308
+
309static void show_help(const char *progname)
+
310{
+
311 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
312 printf("File-system specific options:\n"
+
313 " --update-interval=<secs> Update-rate of file system contents\n"
+
314 " --no-notify Disable kernel notifications\n"
+
315 "\n");
+
316}
+
317
+
318int main(int argc, char *argv[]) {
+
319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
320 struct fuse_session *se;
+
321 struct fuse_cmdline_opts opts;
+
322 struct fuse_loop_config *config;
+
323 pthread_t updater;
+
324 int ret = -1;
+
325
+
326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
327 return 1;
+
328
+
329 if (fuse_parse_cmdline(&args, &opts) != 0) {
+
330 ret = 1;
+
331 goto err_out1;
+
332 }
+
333
+
334 if (opts.show_help) {
+
335 show_help(argv[0]);
+ + +
338 ret = 0;
+
339 goto err_out1;
+
340 } else if (opts.show_version) {
+
341 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
343 ret = 0;
+
344 goto err_out1;
+
345 }
+
346
+
347 /* Initial contents */
+
348 update_fs();
+
349
+
350 se = fuse_session_new(&args, &tfs_oper,
+
351 sizeof(tfs_oper), NULL);
+
352 if (se == NULL)
+
353 goto err_out1;
+
354
+
355 if (fuse_set_signal_handlers(se) != 0)
+
356 goto err_out2;
+
357
+
358 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
359 goto err_out3;
+
360
+
361 fuse_daemonize(opts.foreground);
+
362
+
363 /* Start thread to update file contents */
+
364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
365 if (ret != 0) {
+
366 fprintf(stderr, "pthread_create failed with %s\n",
+
367 strerror(ret));
+
368 goto err_out3;
+
369 }
+
370
+
371 /* Block until ctrl+c or fusermount -u */
+
372 if (opts.singlethread)
+
373 ret = fuse_session_loop(se);
+
374 else {
+
375 config = fuse_loop_cfg_create();
+
376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
378 ret = fuse_session_loop_mt(se, config);
+
379 fuse_loop_cfg_destroy(config);
+
380 config = NULL;
+
381 }
+
382
+ +
384err_out3:
+ +
386err_out2:
+ +
388err_out1:
+
389 fuse_opt_free_args(&args);
+
390 free(opts.mountpoint);
+
391
+
392 return ret ? 1 : 0;
+
393}
+
394
+
395
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c.html new file mode 100644 index 0000000..c1812f0 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c.html @@ -0,0 +1,560 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/notify_store_retrieve.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
notify_store_retrieve.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

+

While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

+

To see the effect, first start the file system with the --no-notify option:

$ notify_store_retrieve --update-interval=1 --no-notify mnt/
+

Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+The current time is 15:58:18
+

If you instead enable the notification functions, the changes become visible:

$ notify_store_retrieve --update-interval=1 mnt/
+$ for i in 1 2 3 4 5; do
+>     cat mnt/current_time
+>     sleep 1
+> done
+The current time is 15:58:40
+The current time is 15:58:41
+The current time is 15:58:42
+The current time is 15:58:43
+The current time is 15:58:44
+

+Compilation

+
gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
#include <stdbool.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define FILE_INO 2
+
#define FILE_NAME "current_time"
+
static char file_contents[MAX_STR_LEN];
+
static int lookup_cnt = 0;
+
static int open_cnt = 0;
+
static size_t file_size;
+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+
/* Keep track if we ever stored data (==1), and
+
received it back correctly (==2) */
+
static int retrieve_status = 0;
+
+
static bool is_umount = false;
+
+
/* updater thread tid */
+
static pthread_t updater;
+
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
stbuf->st_ino = ino;
+
if (ino == FUSE_ROOT_ID) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
}
+
+
else if (ino == FILE_INO) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = file_size;
+
}
+
+
else
+
return -1;
+
+
return 0;
+
}
+
+
static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
const char *name) {
+
struct fuse_entry_param e;
+
memset(&e, 0, sizeof(e));
+
+
if (parent != FUSE_ROOT_ID)
+
goto err_out;
+
else if (strcmp(name, FILE_NAME) == 0) {
+
e.ino = FILE_INO;
+
} else
+
goto err_out;
+
+
e.attr_timeout = NO_TIMEOUT;
+
e.entry_timeout = NO_TIMEOUT;
+
if (tfs_stat(e.ino, &e.attr) != 0)
+
goto err_out;
+
fuse_reply_entry(req, &e);
+
+
/*
+
* must only be set when the kernel knows about the entry,
+
* otherwise update_fs_loop() might see a positive count, but kernel
+
* would not have the entry yet
+
*/
+
if (e.ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt++;
+
pthread_mutex_unlock(&lock);
+
}
+
+
return;
+
+
err_out:
+
fuse_reply_err(req, ENOENT);
+
}
+
+
static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
uint64_t nlookup) {
+
(void) req;
+
if(ino == FILE_INO) {
+
pthread_mutex_lock(&lock);
+
lookup_cnt -= nlookup;
+
pthread_mutex_unlock(&lock);
+
} else
+
assert(ino == FUSE_ROOT_ID);
+ +
}
+
+
static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (tfs_stat(ino, &stbuf) != 0)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
fuse_ino_t ino) {
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize) {
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
if (ino != FUSE_ROOT_ID)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi) {
+
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
+
if (ino == FUSE_ROOT_ID)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else if (ino == FILE_INO) {
+
fuse_reply_open(req, fi);
+
pthread_mutex_lock(&lock);
+
open_cnt++;
+
pthread_mutex_unlock(&lock);
+
} else {
+
// This should not happen
+
fprintf(stderr, "Got open for non-existing inode!\n");
+
fuse_reply_err(req, ENOENT);
+
}
+
}
+
+
static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi) {
+
(void) fi;
+
+
assert(ino == FILE_INO);
+
reply_buf_limited(req, file_contents, file_size, off, size);
+
}
+
+
static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
off_t offset, struct fuse_bufvec *data) {
+
struct fuse_bufvec bufv;
+
char buf[MAX_STR_LEN];
+
char *expected;
+
ssize_t ret;
+
+
assert(ino == FILE_INO);
+
assert(offset == 0);
+
expected = (char*) cookie;
+
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = MAX_STR_LEN;
+
bufv.buf[0].mem = buf;
+
bufv.buf[0].flags = 0;
+
+
ret = fuse_buf_copy(&bufv, data, 0);
+
assert(ret > 0);
+
assert(strncmp(buf, expected, ret) == 0);
+
free(expected);
+
retrieve_status = 2;
+ +
}
+
+
static void tfs_destroy(void *userdata)
+
{
+
(void)userdata;
+
+
is_umount = true;
+
+
pthread_join(updater, NULL);
+
}
+
+
+
static const struct fuse_lowlevel_ops tfs_oper = {
+
.init = tfs_init,
+
.lookup = tfs_lookup,
+
.getattr = tfs_getattr,
+
.readdir = tfs_readdir,
+
.open = tfs_open,
+
.read = tfs_read,
+
.forget = tfs_forget,
+
.retrieve_reply = tfs_retrieve_reply,
+
.destroy = tfs_destroy,
+
};
+
+
static void update_fs(void) {
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
file_size = strftime(file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(file_size != 0);
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse_session *se = (struct fuse_session*) data;
+
struct fuse_bufvec bufv;
+
int ret;
+
+
while(!is_umount) {
+
update_fs();
+
pthread_mutex_lock(&lock);
+
if (!options.no_notify && open_cnt && lookup_cnt) {
+
/* Only send notification if the kernel
+
is aware of the inode */
+
bufv.count = 1;
+
bufv.idx = 0;
+
bufv.off = 0;
+
bufv.buf[0].size = file_size;
+
bufv.buf[0].mem = file_contents;
+
bufv.buf[0].flags = 0;
+
+
/*
+
* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
* might come up during umount, when kernel side already releases
+
* all inodes, but does not send FUSE_DESTROY yet.
+
*/
+
+
ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
if ((ret != 0 && !is_umount) &&
+
ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
fprintf(stderr,
+
"ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
strerror(-ret), -ret);
+
abort();
+
}
+
+
/* To make sure that everything worked correctly, ask the
+
kernel to send us back the stored data */
+
ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
0, (void*) strdup(file_contents));
+
assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
ret != -ENODEV);
+
if(retrieve_status == 0)
+
retrieve_status = 1;
+
}
+
pthread_mutex_unlock(&lock);
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
show_help(argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
/* Initial contents */
+
update_fs();
+
+
se = fuse_session_new(&args, &tfs_oper,
+
sizeof(tfs_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Start thread to update file contents */
+
ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n",
+
strerror(ret));
+
goto err_out3;
+
}
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+
assert(retrieve_status != 1);
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
+
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file notify_store_retrieve.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..05d386b --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2notify__store__retrieve_8c_source.html @@ -0,0 +1,522 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/notify_store_retrieve.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
notify_store_retrieve.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
62
+
63#include <fuse_lowlevel.h>
+
64#include <stdio.h>
+
65#include <stdlib.h>
+
66#include <string.h>
+
67#include <errno.h>
+
68#include <fcntl.h>
+
69#include <assert.h>
+
70#include <stddef.h>
+
71#include <unistd.h>
+
72#include <pthread.h>
+
73#include <stdbool.h>
+
74
+
75/* We can't actually tell the kernel that there is no
+
76 timeout, so we just send a big value */
+
77#define NO_TIMEOUT 500000
+
78
+
79#define MAX_STR_LEN 128
+
80#define FILE_INO 2
+
81#define FILE_NAME "current_time"
+
82static char file_contents[MAX_STR_LEN];
+
83static int lookup_cnt = 0;
+
84static int open_cnt = 0;
+
85static size_t file_size;
+
86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
87
+
88/* Keep track if we ever stored data (==1), and
+
89 received it back correctly (==2) */
+
90static int retrieve_status = 0;
+
91
+
92static bool is_umount = false;
+
93
+
94/* updater thread tid */
+
95static pthread_t updater;
+
96
+
97
+
98/* Command line parsing */
+
99struct options {
+
100 int no_notify;
+
101 int update_interval;
+
102};
+
103static struct options options = {
+
104 .no_notify = 0,
+
105 .update_interval = 1,
+
106};
+
107
+
108#define OPTION(t, p) \
+
109 { t, offsetof(struct options, p), 1 }
+
110static const struct fuse_opt option_spec[] = {
+
111 OPTION("--no-notify", no_notify),
+
112 OPTION("--update-interval=%d", update_interval),
+ +
114};
+
115
+
116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
117 stbuf->st_ino = ino;
+
118 if (ino == FUSE_ROOT_ID) {
+
119 stbuf->st_mode = S_IFDIR | 0755;
+
120 stbuf->st_nlink = 1;
+
121 }
+
122
+
123 else if (ino == FILE_INO) {
+
124 stbuf->st_mode = S_IFREG | 0444;
+
125 stbuf->st_nlink = 1;
+
126 stbuf->st_size = file_size;
+
127 }
+
128
+
129 else
+
130 return -1;
+
131
+
132 return 0;
+
133}
+
134
+
135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
+
136 (void)userdata;
+
137
+
138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
139 conn->no_interrupt = 1;
+
140}
+
141
+
142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
143 const char *name) {
+
144 struct fuse_entry_param e;
+
145 memset(&e, 0, sizeof(e));
+
146
+
147 if (parent != FUSE_ROOT_ID)
+
148 goto err_out;
+
149 else if (strcmp(name, FILE_NAME) == 0) {
+
150 e.ino = FILE_INO;
+
151 } else
+
152 goto err_out;
+
153
+
154 e.attr_timeout = NO_TIMEOUT;
+
155 e.entry_timeout = NO_TIMEOUT;
+
156 if (tfs_stat(e.ino, &e.attr) != 0)
+
157 goto err_out;
+
158 fuse_reply_entry(req, &e);
+
159
+
160 /*
+
161 * must only be set when the kernel knows about the entry,
+
162 * otherwise update_fs_loop() might see a positive count, but kernel
+
163 * would not have the entry yet
+
164 */
+
165 if (e.ino == FILE_INO) {
+
166 pthread_mutex_lock(&lock);
+
167 lookup_cnt++;
+
168 pthread_mutex_unlock(&lock);
+
169 }
+
170
+
171 return;
+
172
+
173err_out:
+
174 fuse_reply_err(req, ENOENT);
+
175}
+
176
+
177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+
178 uint64_t nlookup) {
+
179 (void) req;
+
180 if(ino == FILE_INO) {
+
181 pthread_mutex_lock(&lock);
+
182 lookup_cnt -= nlookup;
+
183 pthread_mutex_unlock(&lock);
+
184 } else
+
185 assert(ino == FUSE_ROOT_ID);
+
186 fuse_reply_none(req);
+
187}
+
188
+
189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
190 struct fuse_file_info *fi) {
+
191 struct stat stbuf;
+
192
+
193 (void) fi;
+
194
+
195 memset(&stbuf, 0, sizeof(stbuf));
+
196 if (tfs_stat(ino, &stbuf) != 0)
+
197 fuse_reply_err(req, ENOENT);
+
198 else
+
199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+
200}
+
201
+
202struct dirbuf {
+
203 char *p;
+
204 size_t size;
+
205};
+
206
+
207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
208 fuse_ino_t ino) {
+
209 struct stat stbuf;
+
210 size_t oldsize = b->size;
+
211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
212 b->p = (char *) realloc(b->p, b->size);
+
213 memset(&stbuf, 0, sizeof(stbuf));
+
214 stbuf.st_ino = ino;
+
215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
216 b->size);
+
217}
+
218
+
219#define min(x, y) ((x) < (y) ? (x) : (y))
+
220
+
221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
222 off_t off, size_t maxsize) {
+
223 if (off < bufsize)
+
224 return fuse_reply_buf(req, buf + off,
+
225 min(bufsize - off, maxsize));
+
226 else
+
227 return fuse_reply_buf(req, NULL, 0);
+
228}
+
229
+
230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
231 off_t off, struct fuse_file_info *fi) {
+
232 (void) fi;
+
233
+
234 if (ino != FUSE_ROOT_ID)
+
235 fuse_reply_err(req, ENOTDIR);
+
236 else {
+
237 struct dirbuf b;
+
238
+
239 memset(&b, 0, sizeof(b));
+
240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+
241 reply_buf_limited(req, b.p, b.size, off, size);
+
242 free(b.p);
+
243 }
+
244}
+
245
+
246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
247 struct fuse_file_info *fi) {
+
248
+
249 /* Make cache persistent even if file is closed,
+
250 this makes it easier to see the effects */
+
251 fi->keep_cache = 1;
+
252
+
253 if (ino == FUSE_ROOT_ID)
+
254 fuse_reply_err(req, EISDIR);
+
255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
256 fuse_reply_err(req, EACCES);
+
257 else if (ino == FILE_INO) {
+
258 fuse_reply_open(req, fi);
+
259 pthread_mutex_lock(&lock);
+
260 open_cnt++;
+
261 pthread_mutex_unlock(&lock);
+
262 } else {
+
263 // This should not happen
+
264 fprintf(stderr, "Got open for non-existing inode!\n");
+
265 fuse_reply_err(req, ENOENT);
+
266 }
+
267}
+
268
+
269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
270 off_t off, struct fuse_file_info *fi) {
+
271 (void) fi;
+
272
+
273 assert(ino == FILE_INO);
+
274 reply_buf_limited(req, file_contents, file_size, off, size);
+
275}
+
276
+
277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+
278 off_t offset, struct fuse_bufvec *data) {
+
279 struct fuse_bufvec bufv;
+
280 char buf[MAX_STR_LEN];
+
281 char *expected;
+
282 ssize_t ret;
+
283
+
284 assert(ino == FILE_INO);
+
285 assert(offset == 0);
+
286 expected = (char*) cookie;
+
287
+
288 bufv.count = 1;
+
289 bufv.idx = 0;
+
290 bufv.off = 0;
+
291 bufv.buf[0].size = MAX_STR_LEN;
+
292 bufv.buf[0].mem = buf;
+
293 bufv.buf[0].flags = 0;
+
294
+
295 ret = fuse_buf_copy(&bufv, data, 0);
+
296 assert(ret > 0);
+
297 assert(strncmp(buf, expected, ret) == 0);
+
298 free(expected);
+
299 retrieve_status = 2;
+
300 fuse_reply_none(req);
+
301}
+
302
+
303static void tfs_destroy(void *userdata)
+
304{
+
305 (void)userdata;
+
306
+
307 is_umount = true;
+
308
+
309 pthread_join(updater, NULL);
+
310}
+
311
+
312
+
313static const struct fuse_lowlevel_ops tfs_oper = {
+
314 .init = tfs_init,
+
315 .lookup = tfs_lookup,
+
316 .getattr = tfs_getattr,
+
317 .readdir = tfs_readdir,
+
318 .open = tfs_open,
+
319 .read = tfs_read,
+
320 .forget = tfs_forget,
+
321 .retrieve_reply = tfs_retrieve_reply,
+
322 .destroy = tfs_destroy,
+
323};
+
324
+
325static void update_fs(void) {
+
326 struct tm *now;
+
327 time_t t;
+
328 t = time(NULL);
+
329 now = localtime(&t);
+
330 assert(now != NULL);
+
331
+
332 file_size = strftime(file_contents, MAX_STR_LEN,
+
333 "The current time is %H:%M:%S\n", now);
+
334 assert(file_size != 0);
+
335}
+
336
+
337static void* update_fs_loop(void *data) {
+
338 struct fuse_session *se = (struct fuse_session*) data;
+
339 struct fuse_bufvec bufv;
+
340 int ret;
+
341
+
342 while(!is_umount) {
+
343 update_fs();
+
344 pthread_mutex_lock(&lock);
+
345 if (!options.no_notify && open_cnt && lookup_cnt) {
+
346 /* Only send notification if the kernel
+
347 is aware of the inode */
+
348 bufv.count = 1;
+
349 bufv.idx = 0;
+
350 bufv.off = 0;
+
351 bufv.buf[0].size = file_size;
+
352 bufv.buf[0].mem = file_contents;
+
353 bufv.buf[0].flags = 0;
+
354
+
355 /*
+
356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
+
357 * might come up during umount, when kernel side already releases
+
358 * all inodes, but does not send FUSE_DESTROY yet.
+
359 */
+
360
+
361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
+
362 if ((ret != 0 && !is_umount) &&
+
363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
+
364 fprintf(stderr,
+
365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
+
366 strerror(-ret), -ret);
+
367 abort();
+
368 }
+
369
+
370 /* To make sure that everything worked correctly, ask the
+
371 kernel to send us back the stored data */
+
372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
+
373 0, (void*) strdup(file_contents));
+
374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
+
375 ret != -ENODEV);
+
376 if(retrieve_status == 0)
+
377 retrieve_status = 1;
+
378 }
+
379 pthread_mutex_unlock(&lock);
+
380 sleep(options.update_interval);
+
381 }
+
382 return NULL;
+
383}
+
384
+
385static void show_help(const char *progname)
+
386{
+
387 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
388 printf("File-system specific options:\n"
+
389 " --update-interval=<secs> Update-rate of file system contents\n"
+
390 " --no-notify Disable kernel notifications\n"
+
391 "\n");
+
392}
+
393
+
394int main(int argc, char *argv[]) {
+
395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
396 struct fuse_session *se;
+
397 struct fuse_cmdline_opts opts;
+
398 struct fuse_loop_config *config;
+
399 int ret = -1;
+
400
+
401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
402 return 1;
+
403
+
404 if (fuse_parse_cmdline(&args, &opts) != 0)
+
405 return 1;
+
406 if (opts.show_help) {
+
407 show_help(argv[0]);
+ + +
410 ret = 0;
+
411 goto err_out1;
+
412 } else if (opts.show_version) {
+
413 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
415 ret = 0;
+
416 goto err_out1;
+
417 }
+
418
+
419 /* Initial contents */
+
420 update_fs();
+
421
+
422 se = fuse_session_new(&args, &tfs_oper,
+
423 sizeof(tfs_oper), NULL);
+
424 if (se == NULL)
+
425 goto err_out1;
+
426
+
427 if (fuse_set_signal_handlers(se) != 0)
+
428 goto err_out2;
+
429
+
430 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
431 goto err_out3;
+
432
+
433 fuse_daemonize(opts.foreground);
+
434
+
435 /* Start thread to update file contents */
+
436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+
437 if (ret != 0) {
+
438 fprintf(stderr, "pthread_create failed with %s\n",
+
439 strerror(ret));
+
440 goto err_out3;
+
441 }
+
442
+
443 /* Block until ctrl+c or fusermount -u */
+
444 if (opts.singlethread)
+
445 ret = fuse_session_loop(se);
+
446 else {
+
447 config = fuse_loop_cfg_create();
+
448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
450 ret = fuse_session_loop_mt(se, config);
+
451 fuse_loop_cfg_destroy(config);
+
452 config = NULL;
+
453 }
+
454
+
455 assert(retrieve_status != 1);
+ +
457err_out3:
+ +
459err_out2:
+ +
461err_out1:
+
462 free(opts.mountpoint);
+
463 fuse_opt_free_args(&args);
+
464
+
465 return ret ? 1 : 0;
+
466}
+
467
+
468
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
+
fuse_ino_t ino
+ +
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2null_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2null_8c.html new file mode 100644 index 0000000..2ad5ca2 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2null_8c.html @@ -0,0 +1,763 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/null.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
null.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

+

Compile with:

gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file null.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2null_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2null_8c_source.html new file mode 100644 index 0000000..49c08dc --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2null_8c_source.html @@ -0,0 +1,196 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/null.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
null.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
25#define FUSE_USE_VERSION 31
+
26
+
27#include <fuse.h>
+
28#include <fuse_lowlevel.h>
+
29#include <stdio.h>
+
30#include <stdlib.h>
+
31#include <string.h>
+
32#include <unistd.h>
+
33#include <time.h>
+
34#include <errno.h>
+
35
+
36static int null_getattr(const char *path, struct stat *stbuf,
+
37 struct fuse_file_info *fi)
+
38{
+
39 (void) fi;
+
40
+
41 if(strcmp(path, "/") != 0)
+
42 return -ENOENT;
+
43
+
44 stbuf->st_mode = S_IFREG | 0644;
+
45 stbuf->st_nlink = 1;
+
46 stbuf->st_uid = getuid();
+
47 stbuf->st_gid = getgid();
+
48 stbuf->st_size = (1ULL << 32); /* 4G */
+
49 stbuf->st_blocks = 0;
+
50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
51
+
52 return 0;
+
53}
+
54
+
55static int null_truncate(const char *path, off_t size,
+
56 struct fuse_file_info *fi)
+
57{
+
58 (void) size;
+
59 (void) fi;
+
60
+
61 if(strcmp(path, "/") != 0)
+
62 return -ENOENT;
+
63
+
64 return 0;
+
65}
+
66
+
67static int null_open(const char *path, struct fuse_file_info *fi)
+
68{
+
69 (void) fi;
+
70
+
71 if(strcmp(path, "/") != 0)
+
72 return -ENOENT;
+
73
+
74 return 0;
+
75}
+
76
+
77static int null_read(const char *path, char *buf, size_t size,
+
78 off_t offset, struct fuse_file_info *fi)
+
79{
+
80 (void) buf;
+
81 (void) offset;
+
82 (void) fi;
+
83
+
84 if(strcmp(path, "/") != 0)
+
85 return -ENOENT;
+
86
+
87 if (offset >= (1ULL << 32))
+
88 return 0;
+
89
+
90 memset(buf, 0, size);
+
91 return size;
+
92}
+
93
+
94static int null_write(const char *path, const char *buf, size_t size,
+
95 off_t offset, struct fuse_file_info *fi)
+
96{
+
97 (void) buf;
+
98 (void) offset;
+
99 (void) fi;
+
100
+
101 if(strcmp(path, "/") != 0)
+
102 return -ENOENT;
+
103
+
104 return size;
+
105}
+
106
+
107static const struct fuse_operations null_oper = {
+
108 .getattr = null_getattr,
+
109 .truncate = null_truncate,
+
110 .open = null_open,
+
111 .read = null_read,
+
112 .write = null_write,
+
113};
+
114
+
115int main(int argc, char *argv[])
+
116{
+
117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
118 struct fuse_cmdline_opts opts;
+
119 struct stat stbuf;
+
120
+
121 if (fuse_parse_cmdline(&args, &opts) != 0)
+
122 return 1;
+
123 fuse_opt_free_args(&args);
+
124
+
125 if (!opts.mountpoint) {
+
126 fprintf(stderr, "missing mountpoint parameter\n");
+
127 return 1;
+
128 }
+
129
+
130 if (stat(opts.mountpoint, &stbuf) == -1) {
+
131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
+
132 opts.mountpoint, strerror(errno));
+
133 free(opts.mountpoint);
+
134 return 1;
+
135 }
+
136 free(opts.mountpoint);
+
137 if (!S_ISREG(stbuf.st_mode)) {
+
138 fprintf(stderr, "mountpoint is not a regular file\n");
+
139 return 1;
+
140 }
+
141
+
142 return fuse_main(argc, argv, &null_oper, NULL);
+
143}
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c.html new file mode 100644 index 0000000..9e64685 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c.html @@ -0,0 +1,663 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/passthrough.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

+

Compile with

gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#ifdef linux
+
/* For pread()/pwrite()/utimensat() */
+
#define _XOPEN_SOURCE 700
+
#endif
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#ifdef __FreeBSD__
+
#include <sys/socket.h>
+
#include <sys/un.h>
+
#endif
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
+
#include "passthrough_helpers.h"
+
+
static int fill_dir_plus = 0;
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
if (!cfg->auto_cache) {
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
}
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
DIR *dp;
+
struct dirent *de;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
dp = opendir(path);
+
if (dp == NULL)
+
return -errno;
+
+
while ((de = readdir(dp)) != NULL) {
+
struct stat st;
+
if (fill_dir_plus) {
+
fstatat(dirfd(dp), de->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
} else {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = de->d_ino;
+
st.st_mode = de->d_type << 12;
+
}
+
if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
break;
+
}
+
+
closedir(dp);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi != NULL)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags, mode);
+
if (res == -1)
+
return -errno;
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
res = open(path, fi->flags);
+
if (res == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = res;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
if(fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pread(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = pwrite(fd, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
/* Just a stub. This method is optional and can safely be left
+
unimplemented */
+
+
(void) path;
+
(void) isdatasync;
+
(void) fi;
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int fd;
+
int res;
+
+
(void) fi;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
if(fi == NULL)
+
fd = open(path, O_WRONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = -posix_fallocate(fd, offset, length);
+
+
if(fi == NULL)
+
close(fd);
+
return res;
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t offset_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t offset_out, size_t len, int flags)
+
{
+
int fd_in, fd_out;
+
ssize_t res;
+
+
if(fi_in == NULL)
+
fd_in = open(path_in, O_RDONLY);
+
else
+
fd_in = fi_in->fh;
+
+
if (fd_in == -1)
+
return -errno;
+
+
if(fi_out == NULL)
+
fd_out = open(path_out, O_WRONLY);
+
else
+
fd_out = fi_out->fh;
+
+
if (fd_out == -1) {
+
close(fd_in);
+
return -errno;
+
}
+
+
res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
flags);
+
if (res == -1)
+
res = -errno;
+
+
if (fi_out == NULL)
+
close(fd_out);
+
if (fi_in == NULL)
+
close(fd_in);
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
int fd;
+
off_t res;
+
+
if (fi == NULL)
+
fd = open(path, O_RDONLY);
+
else
+
fd = fi->fh;
+
+
if (fd == -1)
+
return -errno;
+
+
res = lseek(fd, off, whence);
+
if (res == -1)
+
res = -errno;
+
+
if (fi == NULL)
+
close(fd);
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.readdir = xmp_readdir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.open = xmp_open,
+
.create = xmp_create,
+
.read = xmp_read,
+
.write = xmp_write,
+
.statfs = xmp_statfs,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
enum { MAX_ARGS = 10 };
+
int i,new_argc;
+
char *new_argv[MAX_ARGS];
+
+
umask(0);
+
/* Process the "--plus" option apart */
+
for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
if (!strcmp(argv[i], "--plus")) {
+
fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
} else {
+
new_argv[new_argc++] = argv[i];
+
}
+
}
+
return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c_source.html new file mode 100644 index 0000000..ab98adb --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2passthrough_8c_source.html @@ -0,0 +1,649 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/passthrough.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#ifdef linux
+
31/* For pread()/pwrite()/utimensat() */
+
32#define _XOPEN_SOURCE 700
+
33#endif
+
34
+
35#include <fuse.h>
+
36#include <stdio.h>
+
37#include <string.h>
+
38#include <unistd.h>
+
39#include <fcntl.h>
+
40#include <sys/stat.h>
+
41#include <dirent.h>
+
42#include <errno.h>
+
43#ifdef __FreeBSD__
+
44#include <sys/socket.h>
+
45#include <sys/un.h>
+
46#endif
+
47#include <sys/time.h>
+
48#ifdef HAVE_SETXATTR
+
49#include <sys/xattr.h>
+
50#endif
+
51
+
52#include "passthrough_helpers.h"
+
53
+
54static int fill_dir_plus = 0;
+
55
+
56static void *xmp_init(struct fuse_conn_info *conn,
+
57 struct fuse_config *cfg)
+
58{
+
59 (void) conn;
+
60 cfg->use_ino = 1;
+
61
+
62 /* parallel_direct_writes feature depends on direct_io features.
+
63 To make parallel_direct_writes valid, need either set cfg->direct_io
+
64 in current function (recommended in high level API) or set fi->direct_io
+
65 in xmp_create() or xmp_open(). */
+
66 // cfg->direct_io = 1;
+ +
68
+
69 /* Pick up changes from lower filesystem right away. This is
+
70 also necessary for better hardlink support. When the kernel
+
71 calls the unlink() handler, it does not know the inode of
+
72 the to-be-removed entry and can therefore not invalidate
+
73 the cache of the associated inode - resulting in an
+
74 incorrect st_nlink value being reported for any remaining
+
75 hardlinks to this inode. */
+
76 if (!cfg->auto_cache) {
+
77 cfg->entry_timeout = 0;
+
78 cfg->attr_timeout = 0;
+
79 cfg->negative_timeout = 0;
+
80 }
+
81
+
82 return NULL;
+
83}
+
84
+
85static int xmp_getattr(const char *path, struct stat *stbuf,
+
86 struct fuse_file_info *fi)
+
87{
+
88 (void) fi;
+
89 int res;
+
90
+
91 res = lstat(path, stbuf);
+
92 if (res == -1)
+
93 return -errno;
+
94
+
95 return 0;
+
96}
+
97
+
98static int xmp_access(const char *path, int mask)
+
99{
+
100 int res;
+
101
+
102 res = access(path, mask);
+
103 if (res == -1)
+
104 return -errno;
+
105
+
106 return 0;
+
107}
+
108
+
109static int xmp_readlink(const char *path, char *buf, size_t size)
+
110{
+
111 int res;
+
112
+
113 res = readlink(path, buf, size - 1);
+
114 if (res == -1)
+
115 return -errno;
+
116
+
117 buf[res] = '\0';
+
118 return 0;
+
119}
+
120
+
121
+
122static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
123 off_t offset, struct fuse_file_info *fi,
+
124 enum fuse_readdir_flags flags)
+
125{
+
126 DIR *dp;
+
127 struct dirent *de;
+
128
+
129 (void) offset;
+
130 (void) fi;
+
131 (void) flags;
+
132
+
133 dp = opendir(path);
+
134 if (dp == NULL)
+
135 return -errno;
+
136
+
137 while ((de = readdir(dp)) != NULL) {
+
138 struct stat st;
+
139 if (fill_dir_plus) {
+
140 fstatat(dirfd(dp), de->d_name, &st,
+
141 AT_SYMLINK_NOFOLLOW);
+
142 } else {
+
143 memset(&st, 0, sizeof(st));
+
144 st.st_ino = de->d_ino;
+
145 st.st_mode = de->d_type << 12;
+
146 }
+
147 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
+
148 break;
+
149 }
+
150
+
151 closedir(dp);
+
152 return 0;
+
153}
+
154
+
155static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
156{
+
157 int res;
+
158
+
159 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
+
160 if (res == -1)
+
161 return -errno;
+
162
+
163 return 0;
+
164}
+
165
+
166static int xmp_mkdir(const char *path, mode_t mode)
+
167{
+
168 int res;
+
169
+
170 res = mkdir(path, mode);
+
171 if (res == -1)
+
172 return -errno;
+
173
+
174 return 0;
+
175}
+
176
+
177static int xmp_unlink(const char *path)
+
178{
+
179 int res;
+
180
+
181 res = unlink(path);
+
182 if (res == -1)
+
183 return -errno;
+
184
+
185 return 0;
+
186}
+
187
+
188static int xmp_rmdir(const char *path)
+
189{
+
190 int res;
+
191
+
192 res = rmdir(path);
+
193 if (res == -1)
+
194 return -errno;
+
195
+
196 return 0;
+
197}
+
198
+
199static int xmp_symlink(const char *from, const char *to)
+
200{
+
201 int res;
+
202
+
203 res = symlink(from, to);
+
204 if (res == -1)
+
205 return -errno;
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
211{
+
212 int res;
+
213
+
214 if (flags)
+
215 return -EINVAL;
+
216
+
217 res = rename(from, to);
+
218 if (res == -1)
+
219 return -errno;
+
220
+
221 return 0;
+
222}
+
223
+
224static int xmp_link(const char *from, const char *to)
+
225{
+
226 int res;
+
227
+
228 res = link(from, to);
+
229 if (res == -1)
+
230 return -errno;
+
231
+
232 return 0;
+
233}
+
234
+
235static int xmp_chmod(const char *path, mode_t mode,
+
236 struct fuse_file_info *fi)
+
237{
+
238 (void) fi;
+
239 int res;
+
240
+
241 res = chmod(path, mode);
+
242 if (res == -1)
+
243 return -errno;
+
244
+
245 return 0;
+
246}
+
247
+
248static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
249 struct fuse_file_info *fi)
+
250{
+
251 (void) fi;
+
252 int res;
+
253
+
254 res = lchown(path, uid, gid);
+
255 if (res == -1)
+
256 return -errno;
+
257
+
258 return 0;
+
259}
+
260
+
261static int xmp_truncate(const char *path, off_t size,
+
262 struct fuse_file_info *fi)
+
263{
+
264 int res;
+
265
+
266 if (fi != NULL)
+
267 res = ftruncate(fi->fh, size);
+
268 else
+
269 res = truncate(path, size);
+
270 if (res == -1)
+
271 return -errno;
+
272
+
273 return 0;
+
274}
+
275
+
276#ifdef HAVE_UTIMENSAT
+
277static int xmp_utimens(const char *path, const struct timespec ts[2],
+
278 struct fuse_file_info *fi)
+
279{
+
280 (void) fi;
+
281 int res;
+
282
+
283 /* don't use utime/utimes since they follow symlinks */
+
284 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
285 if (res == -1)
+
286 return -errno;
+
287
+
288 return 0;
+
289}
+
290#endif
+
291
+
292static int xmp_create(const char *path, mode_t mode,
+
293 struct fuse_file_info *fi)
+
294{
+
295 int res;
+
296
+
297 res = open(path, fi->flags, mode);
+
298 if (res == -1)
+
299 return -errno;
+
300
+
301 fi->fh = res;
+
302 return 0;
+
303}
+
304
+
305static int xmp_open(const char *path, struct fuse_file_info *fi)
+
306{
+
307 int res;
+
308
+
309 res = open(path, fi->flags);
+
310 if (res == -1)
+
311 return -errno;
+
312
+
313 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
314 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
315 for writes to the same file). */
+
316 if (fi->flags & O_DIRECT) {
+
317 fi->direct_io = 1;
+ +
319 }
+
320
+
321 fi->fh = res;
+
322 return 0;
+
323}
+
324
+
325static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
326 struct fuse_file_info *fi)
+
327{
+
328 int fd;
+
329 int res;
+
330
+
331 if(fi == NULL)
+
332 fd = open(path, O_RDONLY);
+
333 else
+
334 fd = fi->fh;
+
335
+
336 if (fd == -1)
+
337 return -errno;
+
338
+
339 res = pread(fd, buf, size, offset);
+
340 if (res == -1)
+
341 res = -errno;
+
342
+
343 if(fi == NULL)
+
344 close(fd);
+
345 return res;
+
346}
+
347
+
348static int xmp_write(const char *path, const char *buf, size_t size,
+
349 off_t offset, struct fuse_file_info *fi)
+
350{
+
351 int fd;
+
352 int res;
+
353
+
354 (void) fi;
+
355 if(fi == NULL)
+
356 fd = open(path, O_WRONLY);
+
357 else
+
358 fd = fi->fh;
+
359
+
360 if (fd == -1)
+
361 return -errno;
+
362
+
363 res = pwrite(fd, buf, size, offset);
+
364 if (res == -1)
+
365 res = -errno;
+
366
+
367 if(fi == NULL)
+
368 close(fd);
+
369 return res;
+
370}
+
371
+
372static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
373{
+
374 int res;
+
375
+
376 res = statvfs(path, stbuf);
+
377 if (res == -1)
+
378 return -errno;
+
379
+
380 return 0;
+
381}
+
382
+
383static int xmp_release(const char *path, struct fuse_file_info *fi)
+
384{
+
385 (void) path;
+
386 close(fi->fh);
+
387 return 0;
+
388}
+
389
+
390static int xmp_fsync(const char *path, int isdatasync,
+
391 struct fuse_file_info *fi)
+
392{
+
393 /* Just a stub. This method is optional and can safely be left
+
394 unimplemented */
+
395
+
396 (void) path;
+
397 (void) isdatasync;
+
398 (void) fi;
+
399 return 0;
+
400}
+
401
+
402#ifdef HAVE_POSIX_FALLOCATE
+
403static int xmp_fallocate(const char *path, int mode,
+
404 off_t offset, off_t length, struct fuse_file_info *fi)
+
405{
+
406 int fd;
+
407 int res;
+
408
+
409 (void) fi;
+
410
+
411 if (mode)
+
412 return -EOPNOTSUPP;
+
413
+
414 if(fi == NULL)
+
415 fd = open(path, O_WRONLY);
+
416 else
+
417 fd = fi->fh;
+
418
+
419 if (fd == -1)
+
420 return -errno;
+
421
+
422 res = -posix_fallocate(fd, offset, length);
+
423
+
424 if(fi == NULL)
+
425 close(fd);
+
426 return res;
+
427}
+
428#endif
+
429
+
430#ifdef HAVE_SETXATTR
+
431/* xattr operations are optional and can safely be left unimplemented */
+
432static int xmp_setxattr(const char *path, const char *name, const char *value,
+
433 size_t size, int flags)
+
434{
+
435 int res = lsetxattr(path, name, value, size, flags);
+
436 if (res == -1)
+
437 return -errno;
+
438 return 0;
+
439}
+
440
+
441static int xmp_getxattr(const char *path, const char *name, char *value,
+
442 size_t size)
+
443{
+
444 int res = lgetxattr(path, name, value, size);
+
445 if (res == -1)
+
446 return -errno;
+
447 return res;
+
448}
+
449
+
450static int xmp_listxattr(const char *path, char *list, size_t size)
+
451{
+
452 int res = llistxattr(path, list, size);
+
453 if (res == -1)
+
454 return -errno;
+
455 return res;
+
456}
+
457
+
458static int xmp_removexattr(const char *path, const char *name)
+
459{
+
460 int res = lremovexattr(path, name);
+
461 if (res == -1)
+
462 return -errno;
+
463 return 0;
+
464}
+
465#endif /* HAVE_SETXATTR */
+
466
+
467#ifdef HAVE_COPY_FILE_RANGE
+
468static ssize_t xmp_copy_file_range(const char *path_in,
+
469 struct fuse_file_info *fi_in,
+
470 off_t offset_in, const char *path_out,
+
471 struct fuse_file_info *fi_out,
+
472 off_t offset_out, size_t len, int flags)
+
473{
+
474 int fd_in, fd_out;
+
475 ssize_t res;
+
476
+
477 if(fi_in == NULL)
+
478 fd_in = open(path_in, O_RDONLY);
+
479 else
+
480 fd_in = fi_in->fh;
+
481
+
482 if (fd_in == -1)
+
483 return -errno;
+
484
+
485 if(fi_out == NULL)
+
486 fd_out = open(path_out, O_WRONLY);
+
487 else
+
488 fd_out = fi_out->fh;
+
489
+
490 if (fd_out == -1) {
+
491 close(fd_in);
+
492 return -errno;
+
493 }
+
494
+
495 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
+
496 flags);
+
497 if (res == -1)
+
498 res = -errno;
+
499
+
500 if (fi_out == NULL)
+
501 close(fd_out);
+
502 if (fi_in == NULL)
+
503 close(fd_in);
+
504
+
505 return res;
+
506}
+
507#endif
+
508
+
509static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
510{
+
511 int fd;
+
512 off_t res;
+
513
+
514 if (fi == NULL)
+
515 fd = open(path, O_RDONLY);
+
516 else
+
517 fd = fi->fh;
+
518
+
519 if (fd == -1)
+
520 return -errno;
+
521
+
522 res = lseek(fd, off, whence);
+
523 if (res == -1)
+
524 res = -errno;
+
525
+
526 if (fi == NULL)
+
527 close(fd);
+
528 return res;
+
529}
+
530
+
531static const struct fuse_operations xmp_oper = {
+
532 .init = xmp_init,
+
533 .getattr = xmp_getattr,
+
534 .access = xmp_access,
+
535 .readlink = xmp_readlink,
+
536 .readdir = xmp_readdir,
+
537 .mknod = xmp_mknod,
+
538 .mkdir = xmp_mkdir,
+
539 .symlink = xmp_symlink,
+
540 .unlink = xmp_unlink,
+
541 .rmdir = xmp_rmdir,
+
542 .rename = xmp_rename,
+
543 .link = xmp_link,
+
544 .chmod = xmp_chmod,
+
545 .chown = xmp_chown,
+
546 .truncate = xmp_truncate,
+
547#ifdef HAVE_UTIMENSAT
+
548 .utimens = xmp_utimens,
+
549#endif
+
550 .open = xmp_open,
+
551 .create = xmp_create,
+
552 .read = xmp_read,
+
553 .write = xmp_write,
+
554 .statfs = xmp_statfs,
+
555 .release = xmp_release,
+
556 .fsync = xmp_fsync,
+
557#ifdef HAVE_POSIX_FALLOCATE
+
558 .fallocate = xmp_fallocate,
+
559#endif
+
560#ifdef HAVE_SETXATTR
+
561 .setxattr = xmp_setxattr,
+
562 .getxattr = xmp_getxattr,
+
563 .listxattr = xmp_listxattr,
+
564 .removexattr = xmp_removexattr,
+
565#endif
+
566#ifdef HAVE_COPY_FILE_RANGE
+
567 .copy_file_range = xmp_copy_file_range,
+
568#endif
+
569 .lseek = xmp_lseek,
+
570};
+
571
+
572int main(int argc, char *argv[])
+
573{
+
574 enum { MAX_ARGS = 10 };
+
575 int i,new_argc;
+
576 char *new_argv[MAX_ARGS];
+
577
+
578 umask(0);
+
579 /* Process the "--plus" option apart */
+
580 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
+
581 if (!strcmp(argv[i], "--plus")) {
+
582 fill_dir_plus = FUSE_FILL_DIR_PLUS;
+
583 } else {
+
584 new_argv[new_argc++] = argv[i];
+
585 }
+
586 }
+
587 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
+
588}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ +
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t auto_cache
Definition fuse.h:253
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c.html new file mode 100644 index 0000000..2d789c3 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c.html @@ -0,0 +1,766 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/passthrough_fh.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_fh.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/file.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

+

Compile with:

gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#define _GNU_SOURCE
+
+
#include <fuse.h>
+
+
#ifdef HAVE_LIBULOCKMGR
+
#include <ulockmgr.h>
+
#endif
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <dirent.h>
+
#include <errno.h>
+
#include <sys/time.h>
+
#ifdef HAVE_SETXATTR
+
#include <sys/xattr.h>
+
#endif
+
#include <sys/file.h> /* flock(2) */
+
+
static void *xmp_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->use_ino = 1;
+
cfg->nullpath_ok = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need either set cfg->direct_io
+
in current function (recommended in high level API) or set fi->direct_io
+
in xmp_create() or xmp_open(). */
+
// cfg->direct_io = 1;
+ +
+
/* Pick up changes from lower filesystem right away. This is
+
also necessary for better hardlink support. When the kernel
+
calls the unlink() handler, it does not know the inode of
+
the to-be-removed entry and can therefore not invalidate
+
the cache of the associated inode - resulting in an
+
incorrect st_nlink value being reported for any remaining
+
hardlinks to this inode. */
+
cfg->entry_timeout = 0;
+
cfg->attr_timeout = 0;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
+
if(fi)
+
res = fstat(fi->fh, stbuf);
+
else
+
res = lstat(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_access(const char *path, int mask)
+
{
+
int res;
+
+
res = access(path, mask);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_readlink(const char *path, char *buf, size_t size)
+
{
+
int res;
+
+
res = readlink(path, buf, size - 1);
+
if (res == -1)
+
return -errno;
+
+
buf[res] = '\0';
+
return 0;
+
}
+
+
struct xmp_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
if (d == NULL)
+
return -ENOMEM;
+
+
d->dp = opendir(path);
+
if (d->dp == NULL) {
+
res = -errno;
+
free(d);
+
return res;
+
}
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (unsigned long) d;
+
return 0;
+
}
+
+
static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
{
+
return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
+
(void) path;
+
if (offset != d->offset) {
+
#ifndef __FreeBSD__
+
seekdir(d->dp, offset);
+
#else
+
/* Subtract the one that we add when calling
+
telldir() below */
+
seekdir(d->dp, offset-1);
+
#endif
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
struct stat st;
+
off_t nextoff;
+ +
+
if (!d->entry) {
+
d->entry = readdir(d->dp);
+
if (!d->entry)
+
break;
+
}
+
#ifdef HAVE_FSTATAT
+
if (flags & FUSE_READDIR_PLUS) {
+
int res;
+
+
res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
AT_SYMLINK_NOFOLLOW);
+
if (res != -1)
+
fill_flags |= FUSE_FILL_DIR_PLUS;
+
}
+
#endif
+
if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
memset(&st, 0, sizeof(st));
+
st.st_ino = d->entry->d_ino;
+
st.st_mode = d->entry->d_type << 12;
+
}
+
nextoff = telldir(d->dp);
+
#ifdef __FreeBSD__
+
/* Under FreeBSD, telldir() may return 0 the first time
+
it is called. But for libfuse, an offset of zero
+
means that offsets are not supported, so we shift
+
everything by one. */
+
nextoff++;
+
#endif
+
if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
break;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
return 0;
+
}
+
+
static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
{
+
struct xmp_dirp *d = get_dirp(fi);
+
(void) path;
+
closedir(d->dp);
+
free(d);
+
return 0;
+
}
+
+
static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
{
+
int res;
+
+
if (S_ISFIFO(mode))
+
res = mkfifo(path, mode);
+
else
+
res = mknod(path, mode, rdev);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_mkdir(const char *path, mode_t mode)
+
{
+
int res;
+
+
res = mkdir(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_unlink(const char *path)
+
{
+
int res;
+
+
res = unlink(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rmdir(const char *path)
+
{
+
int res;
+
+
res = rmdir(path);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_symlink(const char *from, const char *to)
+
{
+
int res;
+
+
res = symlink(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
{
+
int res;
+
+
/* When we have renameat2() in libc, then we can implement flags */
+
if (flags)
+
return -EINVAL;
+
+
res = rename(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_link(const char *from, const char *to)
+
{
+
int res;
+
+
res = link(from, to);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chmod(const char *path, mode_t mode,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = fchmod(fi->fh, mode);
+
else
+
res = chmod(path, mode);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if (fi)
+
res = fchown(fi->fh, uid, gid);
+
else
+
res = lchown(path, uid, gid);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
if(fi)
+
res = ftruncate(fi->fh, size);
+
else
+
res = truncate(path, size);
+
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_UTIMENSAT
+
static int xmp_utimens(const char *path, const struct timespec ts[2],
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
/* don't use utime/utimes since they follow symlinks */
+
if (fi)
+
res = futimens(fi->fh, ts);
+
else
+
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
#endif
+
+
static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags, mode);
+
if (fd == -1)
+
return -errno;
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi)
+
{
+
int fd;
+
+
fd = open(path, fi->flags);
+
if (fd == -1)
+
return -errno;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file). */
+
if (fi->flags & O_DIRECT) {
+
fi->direct_io = 1;
+ +
}
+
+
fi->fh = fd;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pread(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
size_t size, off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec *src;
+
+
(void) path;
+
+
src = malloc(sizeof(struct fuse_bufvec));
+
if (src == NULL)
+
return -ENOMEM;
+
+
*src = FUSE_BUFVEC_INIT(size);
+
+ +
src->buf[0].fd = fi->fh;
+
src->buf[0].pos = offset;
+
+
*bufp = src;
+
+
return 0;
+
}
+
+
static int xmp_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
res = pwrite(fi->fh, buf, size, offset);
+
if (res == -1)
+
res = -errno;
+
+
return res;
+
}
+
+
static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+
(void) path;
+
+ +
dst.buf[0].fd = fi->fh;
+
dst.buf[0].pos = offset;
+
+ +
}
+
+
static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
{
+
int res;
+
+
res = statvfs(path, stbuf);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
{
+
int res;
+
+
(void) path;
+
/* This is called from every close on an open file, so call the
+
close on the underlying filesystem. But since flush may be
+
called multiple times for an open file, this must not really
+
close the file. This is important if used on a network
+
filesystem like NFS which flush the data/metadata on close() */
+
res = close(dup(fi->fh));
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
static int xmp_release(const char *path, struct fuse_file_info *fi)
+
{
+
(void) path;
+
close(fi->fh);
+
+
return 0;
+
}
+
+
static int xmp_fsync(const char *path, int isdatasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) path;
+
+
#ifndef HAVE_FDATASYNC
+
(void) isdatasync;
+
#else
+
if (isdatasync)
+
res = fdatasync(fi->fh);
+
else
+
#endif
+
res = fsync(fi->fh);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_POSIX_FALLOCATE
+
static int xmp_fallocate(const char *path, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
(void) path;
+
+
if (mode)
+
return -EOPNOTSUPP;
+
+
return -posix_fallocate(fi->fh, offset, length);
+
}
+
#endif
+
+
#ifdef HAVE_SETXATTR
+
/* xattr operations are optional and can safely be left unimplemented */
+
static int xmp_setxattr(const char *path, const char *name, const char *value,
+
size_t size, int flags)
+
{
+
int res = lsetxattr(path, name, value, size, flags);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
+
static int xmp_getxattr(const char *path, const char *name, char *value,
+
size_t size)
+
{
+
int res = lgetxattr(path, name, value, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_listxattr(const char *path, char *list, size_t size)
+
{
+
int res = llistxattr(path, list, size);
+
if (res == -1)
+
return -errno;
+
return res;
+
}
+
+
static int xmp_removexattr(const char *path, const char *name)
+
{
+
int res = lremovexattr(path, name);
+
if (res == -1)
+
return -errno;
+
return 0;
+
}
+
#endif /* HAVE_SETXATTR */
+
+
#ifdef HAVE_LIBULOCKMGR
+
static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
struct flock *lock)
+
{
+
(void) path;
+
+
return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
sizeof(fi->lock_owner));
+
}
+
#endif
+
+
static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
{
+
int res;
+
(void) path;
+
+
res = flock(fi->fh, op);
+
if (res == -1)
+
return -errno;
+
+
return 0;
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static ssize_t xmp_copy_file_range(const char *path_in,
+
struct fuse_file_info *fi_in,
+
off_t off_in, const char *path_out,
+
struct fuse_file_info *fi_out,
+
off_t off_out, size_t len, int flags)
+
{
+
ssize_t res;
+
(void) path_in;
+
(void) path_out;
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
#endif
+
+
static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
{
+
off_t res;
+
(void) path;
+
+
res = lseek(fi->fh, off, whence);
+
if (res == -1)
+
return -errno;
+
+
return res;
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.access = xmp_access,
+
.readlink = xmp_readlink,
+
.opendir = xmp_opendir,
+
.readdir = xmp_readdir,
+
.releasedir = xmp_releasedir,
+
.mknod = xmp_mknod,
+
.mkdir = xmp_mkdir,
+
.symlink = xmp_symlink,
+
.unlink = xmp_unlink,
+
.rmdir = xmp_rmdir,
+
.rename = xmp_rename,
+
.link = xmp_link,
+
.chmod = xmp_chmod,
+
.chown = xmp_chown,
+
.truncate = xmp_truncate,
+
#ifdef HAVE_UTIMENSAT
+
.utimens = xmp_utimens,
+
#endif
+
.create = xmp_create,
+
.open = xmp_open,
+
.read = xmp_read,
+
.read_buf = xmp_read_buf,
+
.write = xmp_write,
+
.write_buf = xmp_write_buf,
+
.statfs = xmp_statfs,
+
.flush = xmp_flush,
+
.release = xmp_release,
+
.fsync = xmp_fsync,
+
#ifdef HAVE_POSIX_FALLOCATE
+
.fallocate = xmp_fallocate,
+
#endif
+
#ifdef HAVE_SETXATTR
+
.setxattr = xmp_setxattr,
+
.getxattr = xmp_getxattr,
+
.listxattr = xmp_listxattr,
+
.removexattr = xmp_removexattr,
+
#endif
+
#ifdef HAVE_LIBULOCKMGR
+
.lock = xmp_lock,
+
#endif
+
.flock = xmp_flock,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = xmp_copy_file_range,
+
#endif
+
.lseek = xmp_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
umask(0);
+
return fuse_main(argc, argv, &xmp_oper, NULL);
+
}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+

Definition in file passthrough_fh.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c_source.html new file mode 100644 index 0000000..41bf111 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__fh_8c_source.html @@ -0,0 +1,751 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/passthrough_fh.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_fh.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
26#define FUSE_USE_VERSION 31
+
27
+
28#define _GNU_SOURCE
+
29
+
30#include <fuse.h>
+
31
+
32#ifdef HAVE_LIBULOCKMGR
+
33#include <ulockmgr.h>
+
34#endif
+
35
+
36#include <stdio.h>
+
37#include <stdlib.h>
+
38#include <string.h>
+
39#include <unistd.h>
+
40#include <fcntl.h>
+
41#include <sys/stat.h>
+
42#include <dirent.h>
+
43#include <errno.h>
+
44#include <sys/time.h>
+
45#ifdef HAVE_SETXATTR
+
46#include <sys/xattr.h>
+
47#endif
+
48#include <sys/file.h> /* flock(2) */
+
49
+
50static void *xmp_init(struct fuse_conn_info *conn,
+
51 struct fuse_config *cfg)
+
52{
+
53 (void) conn;
+
54 cfg->use_ino = 1;
+
55 cfg->nullpath_ok = 1;
+
56
+
57 /* parallel_direct_writes feature depends on direct_io features.
+
58 To make parallel_direct_writes valid, need either set cfg->direct_io
+
59 in current function (recommended in high level API) or set fi->direct_io
+
60 in xmp_create() or xmp_open(). */
+
61 // cfg->direct_io = 1;
+ +
63
+
64 /* Pick up changes from lower filesystem right away. This is
+
65 also necessary for better hardlink support. When the kernel
+
66 calls the unlink() handler, it does not know the inode of
+
67 the to-be-removed entry and can therefore not invalidate
+
68 the cache of the associated inode - resulting in an
+
69 incorrect st_nlink value being reported for any remaining
+
70 hardlinks to this inode. */
+
71 cfg->entry_timeout = 0;
+
72 cfg->attr_timeout = 0;
+
73 cfg->negative_timeout = 0;
+
74
+
75 return NULL;
+
76}
+
77
+
78static int xmp_getattr(const char *path, struct stat *stbuf,
+
79 struct fuse_file_info *fi)
+
80{
+
81 int res;
+
82
+
83 (void) path;
+
84
+
85 if(fi)
+
86 res = fstat(fi->fh, stbuf);
+
87 else
+
88 res = lstat(path, stbuf);
+
89 if (res == -1)
+
90 return -errno;
+
91
+
92 return 0;
+
93}
+
94
+
95static int xmp_access(const char *path, int mask)
+
96{
+
97 int res;
+
98
+
99 res = access(path, mask);
+
100 if (res == -1)
+
101 return -errno;
+
102
+
103 return 0;
+
104}
+
105
+
106static int xmp_readlink(const char *path, char *buf, size_t size)
+
107{
+
108 int res;
+
109
+
110 res = readlink(path, buf, size - 1);
+
111 if (res == -1)
+
112 return -errno;
+
113
+
114 buf[res] = '\0';
+
115 return 0;
+
116}
+
117
+
118struct xmp_dirp {
+
119 DIR *dp;
+
120 struct dirent *entry;
+
121 off_t offset;
+
122};
+
123
+
124static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+
125{
+
126 int res;
+
127 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+
128 if (d == NULL)
+
129 return -ENOMEM;
+
130
+
131 d->dp = opendir(path);
+
132 if (d->dp == NULL) {
+
133 res = -errno;
+
134 free(d);
+
135 return res;
+
136 }
+
137 d->offset = 0;
+
138 d->entry = NULL;
+
139
+
140 fi->fh = (unsigned long) d;
+
141 return 0;
+
142}
+
143
+
144static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+
145{
+
146 return (struct xmp_dirp *) (uintptr_t) fi->fh;
+
147}
+
148
+
149static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
150 off_t offset, struct fuse_file_info *fi,
+
151 enum fuse_readdir_flags flags)
+
152{
+
153 struct xmp_dirp *d = get_dirp(fi);
+
154
+
155 (void) path;
+
156 if (offset != d->offset) {
+
157#ifndef __FreeBSD__
+
158 seekdir(d->dp, offset);
+
159#else
+
160 /* Subtract the one that we add when calling
+
161 telldir() below */
+
162 seekdir(d->dp, offset-1);
+
163#endif
+
164 d->entry = NULL;
+
165 d->offset = offset;
+
166 }
+
167 while (1) {
+
168 struct stat st;
+
169 off_t nextoff;
+ +
171
+
172 if (!d->entry) {
+
173 d->entry = readdir(d->dp);
+
174 if (!d->entry)
+
175 break;
+
176 }
+
177#ifdef HAVE_FSTATAT
+
178 if (flags & FUSE_READDIR_PLUS) {
+
179 int res;
+
180
+
181 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+
182 AT_SYMLINK_NOFOLLOW);
+
183 if (res != -1)
+
184 fill_flags |= FUSE_FILL_DIR_PLUS;
+
185 }
+
186#endif
+
187 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+
188 memset(&st, 0, sizeof(st));
+
189 st.st_ino = d->entry->d_ino;
+
190 st.st_mode = d->entry->d_type << 12;
+
191 }
+
192 nextoff = telldir(d->dp);
+
193#ifdef __FreeBSD__
+
194 /* Under FreeBSD, telldir() may return 0 the first time
+
195 it is called. But for libfuse, an offset of zero
+
196 means that offsets are not supported, so we shift
+
197 everything by one. */
+
198 nextoff++;
+
199#endif
+
200 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+
201 break;
+
202
+
203 d->entry = NULL;
+
204 d->offset = nextoff;
+
205 }
+
206
+
207 return 0;
+
208}
+
209
+
210static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+
211{
+
212 struct xmp_dirp *d = get_dirp(fi);
+
213 (void) path;
+
214 closedir(d->dp);
+
215 free(d);
+
216 return 0;
+
217}
+
218
+
219static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+
220{
+
221 int res;
+
222
+
223 if (S_ISFIFO(mode))
+
224 res = mkfifo(path, mode);
+
225 else
+
226 res = mknod(path, mode, rdev);
+
227 if (res == -1)
+
228 return -errno;
+
229
+
230 return 0;
+
231}
+
232
+
233static int xmp_mkdir(const char *path, mode_t mode)
+
234{
+
235 int res;
+
236
+
237 res = mkdir(path, mode);
+
238 if (res == -1)
+
239 return -errno;
+
240
+
241 return 0;
+
242}
+
243
+
244static int xmp_unlink(const char *path)
+
245{
+
246 int res;
+
247
+
248 res = unlink(path);
+
249 if (res == -1)
+
250 return -errno;
+
251
+
252 return 0;
+
253}
+
254
+
255static int xmp_rmdir(const char *path)
+
256{
+
257 int res;
+
258
+
259 res = rmdir(path);
+
260 if (res == -1)
+
261 return -errno;
+
262
+
263 return 0;
+
264}
+
265
+
266static int xmp_symlink(const char *from, const char *to)
+
267{
+
268 int res;
+
269
+
270 res = symlink(from, to);
+
271 if (res == -1)
+
272 return -errno;
+
273
+
274 return 0;
+
275}
+
276
+
277static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
278{
+
279 int res;
+
280
+
281 /* When we have renameat2() in libc, then we can implement flags */
+
282 if (flags)
+
283 return -EINVAL;
+
284
+
285 res = rename(from, to);
+
286 if (res == -1)
+
287 return -errno;
+
288
+
289 return 0;
+
290}
+
291
+
292static int xmp_link(const char *from, const char *to)
+
293{
+
294 int res;
+
295
+
296 res = link(from, to);
+
297 if (res == -1)
+
298 return -errno;
+
299
+
300 return 0;
+
301}
+
302
+
303static int xmp_chmod(const char *path, mode_t mode,
+
304 struct fuse_file_info *fi)
+
305{
+
306 int res;
+
307
+
308 if(fi)
+
309 res = fchmod(fi->fh, mode);
+
310 else
+
311 res = chmod(path, mode);
+
312 if (res == -1)
+
313 return -errno;
+
314
+
315 return 0;
+
316}
+
317
+
318static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 int res;
+
322
+
323 if (fi)
+
324 res = fchown(fi->fh, uid, gid);
+
325 else
+
326 res = lchown(path, uid, gid);
+
327 if (res == -1)
+
328 return -errno;
+
329
+
330 return 0;
+
331}
+
332
+
333static int xmp_truncate(const char *path, off_t size,
+
334 struct fuse_file_info *fi)
+
335{
+
336 int res;
+
337
+
338 if(fi)
+
339 res = ftruncate(fi->fh, size);
+
340 else
+
341 res = truncate(path, size);
+
342
+
343 if (res == -1)
+
344 return -errno;
+
345
+
346 return 0;
+
347}
+
348
+
349#ifdef HAVE_UTIMENSAT
+
350static int xmp_utimens(const char *path, const struct timespec ts[2],
+
351 struct fuse_file_info *fi)
+
352{
+
353 int res;
+
354
+
355 /* don't use utime/utimes since they follow symlinks */
+
356 if (fi)
+
357 res = futimens(fi->fh, ts);
+
358 else
+
359 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+
360 if (res == -1)
+
361 return -errno;
+
362
+
363 return 0;
+
364}
+
365#endif
+
366
+
367static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
368{
+
369 int fd;
+
370
+
371 fd = open(path, fi->flags, mode);
+
372 if (fd == -1)
+
373 return -errno;
+
374
+
375 fi->fh = fd;
+
376 return 0;
+
377}
+
378
+
379static int xmp_open(const char *path, struct fuse_file_info *fi)
+
380{
+
381 int fd;
+
382
+
383 fd = open(path, fi->flags);
+
384 if (fd == -1)
+
385 return -errno;
+
386
+
387 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
388 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
389 for writes to the same file). */
+
390 if (fi->flags & O_DIRECT) {
+
391 fi->direct_io = 1;
+ +
393 }
+
394
+
395 fi->fh = fd;
+
396 return 0;
+
397}
+
398
+
399static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
400 struct fuse_file_info *fi)
+
401{
+
402 int res;
+
403
+
404 (void) path;
+
405 res = pread(fi->fh, buf, size, offset);
+
406 if (res == -1)
+
407 res = -errno;
+
408
+
409 return res;
+
410}
+
411
+
412static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+
413 size_t size, off_t offset, struct fuse_file_info *fi)
+
414{
+
415 struct fuse_bufvec *src;
+
416
+
417 (void) path;
+
418
+
419 src = malloc(sizeof(struct fuse_bufvec));
+
420 if (src == NULL)
+
421 return -ENOMEM;
+
422
+
423 *src = FUSE_BUFVEC_INIT(size);
+
424
+ +
426 src->buf[0].fd = fi->fh;
+
427 src->buf[0].pos = offset;
+
428
+
429 *bufp = src;
+
430
+
431 return 0;
+
432}
+
433
+
434static int xmp_write(const char *path, const char *buf, size_t size,
+
435 off_t offset, struct fuse_file_info *fi)
+
436{
+
437 int res;
+
438
+
439 (void) path;
+
440 res = pwrite(fi->fh, buf, size, offset);
+
441 if (res == -1)
+
442 res = -errno;
+
443
+
444 return res;
+
445}
+
446
+
447static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+
448 off_t offset, struct fuse_file_info *fi)
+
449{
+
450 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
451
+
452 (void) path;
+
453
+ +
455 dst.buf[0].fd = fi->fh;
+
456 dst.buf[0].pos = offset;
+
457
+ +
459}
+
460
+
461static int xmp_statfs(const char *path, struct statvfs *stbuf)
+
462{
+
463 int res;
+
464
+
465 res = statvfs(path, stbuf);
+
466 if (res == -1)
+
467 return -errno;
+
468
+
469 return 0;
+
470}
+
471
+
472static int xmp_flush(const char *path, struct fuse_file_info *fi)
+
473{
+
474 int res;
+
475
+
476 (void) path;
+
477 /* This is called from every close on an open file, so call the
+
478 close on the underlying filesystem. But since flush may be
+
479 called multiple times for an open file, this must not really
+
480 close the file. This is important if used on a network
+
481 filesystem like NFS which flush the data/metadata on close() */
+
482 res = close(dup(fi->fh));
+
483 if (res == -1)
+
484 return -errno;
+
485
+
486 return 0;
+
487}
+
488
+
489static int xmp_release(const char *path, struct fuse_file_info *fi)
+
490{
+
491 (void) path;
+
492 close(fi->fh);
+
493
+
494 return 0;
+
495}
+
496
+
497static int xmp_fsync(const char *path, int isdatasync,
+
498 struct fuse_file_info *fi)
+
499{
+
500 int res;
+
501 (void) path;
+
502
+
503#ifndef HAVE_FDATASYNC
+
504 (void) isdatasync;
+
505#else
+
506 if (isdatasync)
+
507 res = fdatasync(fi->fh);
+
508 else
+
509#endif
+
510 res = fsync(fi->fh);
+
511 if (res == -1)
+
512 return -errno;
+
513
+
514 return 0;
+
515}
+
516
+
517#ifdef HAVE_POSIX_FALLOCATE
+
518static int xmp_fallocate(const char *path, int mode,
+
519 off_t offset, off_t length, struct fuse_file_info *fi)
+
520{
+
521 (void) path;
+
522
+
523 if (mode)
+
524 return -EOPNOTSUPP;
+
525
+
526 return -posix_fallocate(fi->fh, offset, length);
+
527}
+
528#endif
+
529
+
530#ifdef HAVE_SETXATTR
+
531/* xattr operations are optional and can safely be left unimplemented */
+
532static int xmp_setxattr(const char *path, const char *name, const char *value,
+
533 size_t size, int flags)
+
534{
+
535 int res = lsetxattr(path, name, value, size, flags);
+
536 if (res == -1)
+
537 return -errno;
+
538 return 0;
+
539}
+
540
+
541static int xmp_getxattr(const char *path, const char *name, char *value,
+
542 size_t size)
+
543{
+
544 int res = lgetxattr(path, name, value, size);
+
545 if (res == -1)
+
546 return -errno;
+
547 return res;
+
548}
+
549
+
550static int xmp_listxattr(const char *path, char *list, size_t size)
+
551{
+
552 int res = llistxattr(path, list, size);
+
553 if (res == -1)
+
554 return -errno;
+
555 return res;
+
556}
+
557
+
558static int xmp_removexattr(const char *path, const char *name)
+
559{
+
560 int res = lremovexattr(path, name);
+
561 if (res == -1)
+
562 return -errno;
+
563 return 0;
+
564}
+
565#endif /* HAVE_SETXATTR */
+
566
+
567#ifdef HAVE_LIBULOCKMGR
+
568static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
569 struct flock *lock)
+
570{
+
571 (void) path;
+
572
+
573 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+
574 sizeof(fi->lock_owner));
+
575}
+
576#endif
+
577
+
578static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+
579{
+
580 int res;
+
581 (void) path;
+
582
+
583 res = flock(fi->fh, op);
+
584 if (res == -1)
+
585 return -errno;
+
586
+
587 return 0;
+
588}
+
589
+
590#ifdef HAVE_COPY_FILE_RANGE
+
591static ssize_t xmp_copy_file_range(const char *path_in,
+
592 struct fuse_file_info *fi_in,
+
593 off_t off_in, const char *path_out,
+
594 struct fuse_file_info *fi_out,
+
595 off_t off_out, size_t len, int flags)
+
596{
+
597 ssize_t res;
+
598 (void) path_in;
+
599 (void) path_out;
+
600
+
601 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
602 flags);
+
603 if (res == -1)
+
604 return -errno;
+
605
+
606 return res;
+
607}
+
608#endif
+
609
+
610static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
+
611{
+
612 off_t res;
+
613 (void) path;
+
614
+
615 res = lseek(fi->fh, off, whence);
+
616 if (res == -1)
+
617 return -errno;
+
618
+
619 return res;
+
620}
+
621
+
622static const struct fuse_operations xmp_oper = {
+
623 .init = xmp_init,
+
624 .getattr = xmp_getattr,
+
625 .access = xmp_access,
+
626 .readlink = xmp_readlink,
+
627 .opendir = xmp_opendir,
+
628 .readdir = xmp_readdir,
+
629 .releasedir = xmp_releasedir,
+
630 .mknod = xmp_mknod,
+
631 .mkdir = xmp_mkdir,
+
632 .symlink = xmp_symlink,
+
633 .unlink = xmp_unlink,
+
634 .rmdir = xmp_rmdir,
+
635 .rename = xmp_rename,
+
636 .link = xmp_link,
+
637 .chmod = xmp_chmod,
+
638 .chown = xmp_chown,
+
639 .truncate = xmp_truncate,
+
640#ifdef HAVE_UTIMENSAT
+
641 .utimens = xmp_utimens,
+
642#endif
+
643 .create = xmp_create,
+
644 .open = xmp_open,
+
645 .read = xmp_read,
+
646 .read_buf = xmp_read_buf,
+
647 .write = xmp_write,
+
648 .write_buf = xmp_write_buf,
+
649 .statfs = xmp_statfs,
+
650 .flush = xmp_flush,
+
651 .release = xmp_release,
+
652 .fsync = xmp_fsync,
+
653#ifdef HAVE_POSIX_FALLOCATE
+
654 .fallocate = xmp_fallocate,
+
655#endif
+
656#ifdef HAVE_SETXATTR
+
657 .setxattr = xmp_setxattr,
+
658 .getxattr = xmp_getxattr,
+
659 .listxattr = xmp_listxattr,
+
660 .removexattr = xmp_removexattr,
+
661#endif
+
662#ifdef HAVE_LIBULOCKMGR
+
663 .lock = xmp_lock,
+
664#endif
+
665 .flock = xmp_flock,
+
666#ifdef HAVE_COPY_FILE_RANGE
+
667 .copy_file_range = xmp_copy_file_range,
+
668#endif
+
669 .lseek = xmp_lseek,
+
670};
+
671
+
672int main(int argc, char *argv[])
+
673{
+
674 umask(0);
+
675 return fuse_main(argc, argv, &xmp_oper, NULL);
+
676}
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
enum fuse_buf_flags flags
+ +
off_t pos
+ + +
struct fuse_buf buf[1]
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint64_t lock_owner
+ +
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2passthrough__helpers_8h_source.html b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__helpers_8h_source.html new file mode 100644 index 0000000..61e59fb --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__helpers_8h_source.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/passthrough_helpers.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_helpers.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 *
+
4 * Redistribution and use in source and binary forms, with or without
+
5 * modification, are permitted provided that the following conditions
+
6 * are met:
+
7 * 1. Redistributions of source code must retain the above copyright
+
8 * notice, this list of conditions and the following disclaimer.
+
9 * 2. Redistributions in binary form must reproduce the above copyright
+
10 * notice, this list of conditions and the following disclaimer in the
+
11 * documentation and/or other materials provided with the distribution.
+
12 *
+
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
23 * SUCH DAMAGE
+
24 */
+
25
+
26/*
+
27 * Creates files on the underlying file system in response to a FUSE_MKNOD
+
28 * operation
+
29 */
+
30static int mknod_wrapper(int dirfd, const char *path, const char *link,
+
31 int mode, dev_t rdev)
+
32{
+
33 int res;
+
34
+
35 if (S_ISREG(mode)) {
+
36 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+
37 if (res >= 0)
+
38 res = close(res);
+
39 } else if (S_ISDIR(mode)) {
+
40 res = mkdirat(dirfd, path, mode);
+
41 } else if (S_ISLNK(mode) && link != NULL) {
+
42 res = symlinkat(link, dirfd, path);
+
43 } else if (S_ISFIFO(mode)) {
+
44 res = mkfifoat(dirfd, path, mode);
+
45#ifdef __FreeBSD__
+
46 } else if (S_ISSOCK(mode)) {
+
47 struct sockaddr_un su;
+
48 int fd;
+
49
+
50 if (strlen(path) >= sizeof(su.sun_path)) {
+
51 errno = ENAMETOOLONG;
+
52 return -1;
+
53 }
+
54 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
55 if (fd >= 0) {
+
56 /*
+
57 * We must bind the socket to the underlying file
+
58 * system to create the socket file, even though
+
59 * we'll never listen on this socket.
+
60 */
+
61 su.sun_family = AF_UNIX;
+
62 strncpy(su.sun_path, path, sizeof(su.sun_path));
+
63 res = bindat(dirfd, fd, (struct sockaddr*)&su,
+
64 sizeof(su));
+
65 if (res == 0)
+
66 close(fd);
+
67 } else {
+
68 res = -1;
+
69 }
+
70#endif
+
71 } else {
+
72 res = mknodat(dirfd, path, mode, rdev);
+
73 }
+
74
+
75 return res;
+
76}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c.html new file mode 100644 index 0000000..548a2a8 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c.html @@ -0,0 +1,1529 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/passthrough_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
passthrough_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/file.h>
+#include <sys/xattr.h>
+#include "passthrough_helpers.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three, but many operations are not implemented. In particular, it is not possible to remove files (or directories) because the code necessary to defer actual removal until the file is not opened anymore would make the example much more complicated.

+

When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

+

Compile with:

gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define _GNU_SOURCE
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <unistd.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stddef.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <limits.h>
+
#include <dirent.h>
+
#include <assert.h>
+
#include <errno.h>
+
#include <inttypes.h>
+
#include <pthread.h>
+
#include <sys/file.h>
+
#include <sys/xattr.h>
+
+
#include "passthrough_helpers.h"
+
+
/* We are re-using pointers to our `struct lo_inode` and `struct
+
lo_dirp` elements as inodes. This means that we must be able to
+
store uintptr_t values in a fuse_ino_t variable. The following
+
incantation checks this condition at compile time. */
+
#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
"fuse_ino_t too small to hold uintptr_t values!");
+
#else
+
struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
{ unsigned _uintptr_to_must_hold_fuse_ino_t:
+
((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
#endif
+
+
struct lo_inode {
+
struct lo_inode *next; /* protected by lo->mutex */
+
struct lo_inode *prev; /* protected by lo->mutex */
+
int fd;
+
ino_t ino;
+
dev_t dev;
+
uint64_t refcount; /* protected by lo->mutex */
+
};
+
+
enum {
+
CACHE_NEVER,
+
CACHE_NORMAL,
+
CACHE_ALWAYS,
+
};
+
+
struct lo_data {
+
pthread_mutex_t mutex;
+
int debug;
+
int writeback;
+
int flock;
+
int xattr;
+
char *source;
+
double timeout;
+
int cache;
+
int timeout_set;
+
struct lo_inode root; /* protected by lo->mutex */
+
};
+
+
static const struct fuse_opt lo_opts[] = {
+
{ "writeback",
+
offsetof(struct lo_data, writeback), 1 },
+
{ "no_writeback",
+
offsetof(struct lo_data, writeback), 0 },
+
{ "source=%s",
+
offsetof(struct lo_data, source), 0 },
+
{ "flock",
+
offsetof(struct lo_data, flock), 1 },
+
{ "no_flock",
+
offsetof(struct lo_data, flock), 0 },
+
{ "xattr",
+
offsetof(struct lo_data, xattr), 1 },
+
{ "no_xattr",
+
offsetof(struct lo_data, xattr), 0 },
+
{ "timeout=%lf",
+
offsetof(struct lo_data, timeout), 0 },
+
{ "timeout=",
+
offsetof(struct lo_data, timeout_set), 1 },
+
{ "cache=never",
+
offsetof(struct lo_data, cache), CACHE_NEVER },
+
{ "cache=auto",
+
offsetof(struct lo_data, cache), CACHE_NORMAL },
+
{ "cache=always",
+
offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
+ +
};
+
+
static void passthrough_ll_help(void)
+
{
+
printf(
+
" -o writeback Enable writeback\n"
+
" -o no_writeback Disable write back\n"
+
" -o source=/home/dir Source directory to be mounted\n"
+
" -o flock Enable flock\n"
+
" -o no_flock Disable flock\n"
+
" -o xattr Enable xattr\n"
+
" -o no_xattr Disable xattr\n"
+
" -o timeout=1.0 Caching timeout\n"
+
" -o timeout=0/1 Timeout is set\n"
+
" -o cache=never Disable cache\n"
+
" -o cache=auto Auto enable cache\n"
+
" -o cache=always Cache always\n");
+
}
+
+
static struct lo_data *lo_data(fuse_req_t req)
+
{
+
return (struct lo_data *) fuse_req_userdata(req);
+
}
+
+
static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
{
+
if (ino == FUSE_ROOT_ID)
+
return &lo_data(req)->root;
+
else
+
return (struct lo_inode *) (uintptr_t) ino;
+
}
+
+
static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
{
+
return lo_inode(req, ino)->fd;
+
}
+
+
static bool lo_debug(fuse_req_t req)
+
{
+
return lo_data(req)->debug != 0;
+
}
+
+
static void lo_init(void *userdata,
+
struct fuse_conn_info *conn)
+
{
+
struct lo_data *lo = (struct lo_data *)userdata;
+
bool has_flag;
+
+
if (lo->writeback) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating writeback\n");
+
}
+
if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
if (lo->debug && has_flag)
+
fuse_log(FUSE_LOG_DEBUG,
+
"lo_init: activating flock locks\n");
+
}
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void lo_destroy(void *userdata)
+
{
+
struct lo_data *lo = (struct lo_data*) userdata;
+
+
while (lo->root.next != &lo->root) {
+
struct lo_inode* next = lo->root.next;
+
lo->root.next = next->next;
+
close(next->fd);
+
free(next);
+
}
+
}
+
+
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
struct stat buf;
+
struct lo_data *lo = lo_data(req);
+
int fd = fi ? fi->fh : lo_fd(req, ino);
+
+
(void) fi;
+
+
res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fuse_reply_attr(req, &buf, lo->timeout);
+
}
+
+
static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
int valid, struct fuse_file_info *fi)
+
{
+
int saverr;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
int ifd = inode->fd;
+
int res;
+
+
if (valid & FUSE_SET_ATTR_MODE) {
+
if (fi) {
+
res = fchmod(fi->fh, attr->st_mode);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = chmod(procname, attr->st_mode);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
attr->st_uid : (uid_t) -1;
+
gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
attr->st_gid : (gid_t) -1;
+
+
res = fchownat(ifd, "", uid, gid,
+
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & FUSE_SET_ATTR_SIZE) {
+
if (fi) {
+
res = ftruncate(fi->fh, attr->st_size);
+
} else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = truncate(procname, attr->st_size);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
struct timespec tv[2];
+
+
tv[0].tv_sec = 0;
+
tv[1].tv_sec = 0;
+
tv[0].tv_nsec = UTIME_OMIT;
+
tv[1].tv_nsec = UTIME_OMIT;
+
+
if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
tv[0].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_ATIME)
+
tv[0] = attr->st_atim;
+
+
if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
tv[1].tv_nsec = UTIME_NOW;
+
else if (valid & FUSE_SET_ATTR_MTIME)
+
tv[1] = attr->st_mtim;
+
+
if (fi)
+
res = futimens(fi->fh, tv);
+
else {
+
sprintf(procname, "/proc/self/fd/%i", ifd);
+
res = utimensat(AT_FDCWD, procname, tv, 0);
+
}
+
if (res == -1)
+
goto out_err;
+
}
+
+
return lo_getattr(req, ino, fi);
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
{
+
struct lo_inode *p;
+
struct lo_inode *ret = NULL;
+
+
pthread_mutex_lock(&lo->mutex);
+
for (p = lo->root.next; p != &lo->root; p = p->next) {
+
if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
assert(p->refcount > 0);
+
ret = p;
+
ret->refcount++;
+
break;
+
}
+
}
+
pthread_mutex_unlock(&lo->mutex);
+
return ret;
+
}
+
+
+
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
{
+
struct lo_inode *inode = NULL;
+
struct lo_inode *prev, *next;
+
+
inode = calloc(1, sizeof(struct lo_inode));
+
if (!inode)
+
return NULL;
+
+
inode->refcount = 1;
+
inode->fd = fd;
+
inode->ino = e->attr.st_ino;
+
inode->dev = e->attr.st_dev;
+
+
pthread_mutex_lock(&lo->mutex);
+
prev = &lo->root;
+
next = prev->next;
+
next->prev = inode;
+
inode->next = next;
+
inode->prev = prev;
+
prev->next = inode;
+
pthread_mutex_unlock(&lo->mutex);
+
return inode;
+
}
+
+
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
return errno;
+
+
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
(unsigned long long) parent, fd, (unsigned long long) e->ino);
+
+
return 0;
+
+
}
+
+
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
struct fuse_entry_param *e)
+
{
+
int newfd;
+
int res;
+
int saverr;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode;
+
+
memset(e, 0, sizeof(*e));
+
e->attr_timeout = lo->timeout;
+
e->entry_timeout = lo->timeout;
+
+
newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
if (newfd == -1)
+
goto out_err;
+
+
res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
inode = lo_find(lo_data(req), &e->attr);
+
if (inode) {
+
close(newfd);
+
newfd = -1;
+
} else {
+
inode = create_new_inode(newfd, e, lo);
+
if (!inode)
+
goto out_err;
+
}
+
e->ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+
return 0;
+
+
out_err:
+
saverr = errno;
+
if (newfd != -1)
+
close(newfd);
+
return saverr;
+
}
+
+
static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_entry(req, &e);
+
}
+
+
static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev,
+
const char *link)
+
{
+
int res;
+
int saverr;
+
struct lo_inode *dir = lo_inode(req, parent);
+
struct fuse_entry_param e;
+
+
res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
+
saverr = errno;
+
if (res == -1)
+
goto out;
+
+
saverr = lo_do_lookup(req, parent, name, &e);
+
if (saverr)
+
goto out;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name, (unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
const char *name, mode_t mode, dev_t rdev)
+
{
+
lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
}
+
+
static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
}
+
+
static void lo_symlink(fuse_req_t req, const char *link,
+
fuse_ino_t parent, const char *name)
+
{
+
lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
}
+
+
static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
const char *name)
+
{
+
int res;
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
struct fuse_entry_param e;
+
char procname[64];
+
int saverr;
+
+
memset(&e, 0, sizeof(struct fuse_entry_param));
+
e.attr_timeout = lo->timeout;
+
e.entry_timeout = lo->timeout;
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
AT_SYMLINK_FOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
if (res == -1)
+
goto out_err;
+
+
pthread_mutex_lock(&lo->mutex);
+
inode->refcount++;
+
pthread_mutex_unlock(&lo->mutex);
+
e.ino = (uintptr_t) inode;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
(unsigned long long) parent, name,
+
(unsigned long long) e.ino);
+
+
fuse_reply_entry(req, &e);
+
return;
+
+
out_err:
+
saverr = errno;
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
fuse_ino_t newparent, const char *newname,
+
unsigned int flags)
+
{
+
int res;
+
+
if (flags) {
+
fuse_reply_err(req, EINVAL);
+
return;
+
}
+
+
res = renameat(lo_fd(req, parent), name,
+
lo_fd(req, newparent), newname);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
int res;
+
+
res = unlinkat(lo_fd(req, parent), name, 0);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
{
+
if (!inode)
+
return;
+
+
pthread_mutex_lock(&lo->mutex);
+
assert(inode->refcount >= n);
+
inode->refcount -= n;
+
if (!inode->refcount) {
+
struct lo_inode *prev, *next;
+
+
prev = inode->prev;
+
next = inode->next;
+
next->prev = prev;
+
prev->next = next;
+
+
pthread_mutex_unlock(&lo->mutex);
+
close(inode->fd);
+
free(inode);
+
+
} else {
+
pthread_mutex_unlock(&lo->mutex);
+
}
+
}
+
+
static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
struct lo_data *lo = lo_data(req);
+
struct lo_inode *inode = lo_inode(req, ino);
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
(unsigned long long) ino,
+
(unsigned long long) inode->refcount,
+
(unsigned long long) nlookup);
+
}
+
+
unref_inode(lo, inode, nlookup);
+
}
+
+
static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
{
+
lo_forget_one(req, ino, nlookup);
+ +
}
+
+
static void lo_forget_multi(fuse_req_t req, size_t count,
+
struct fuse_forget_data *forgets)
+
{
+
int i;
+
+
for (i = 0; i < count; i++)
+
lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+ +
}
+
+
static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
{
+
char buf[PATH_MAX + 1];
+
int res;
+
+
res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
if (res == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
if (res == sizeof(buf))
+
return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+
buf[res] = '\0';
+
+ +
}
+
+
struct lo_dirp {
+
DIR *dp;
+
struct dirent *entry;
+
off_t offset;
+
};
+
+
static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
{
+
return (struct lo_dirp *) (uintptr_t) fi->fh;
+
}
+
+
static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int error = ENOMEM;
+
struct lo_data *lo = lo_data(req);
+
struct lo_dirp *d;
+
int fd = -1;
+
+
d = calloc(1, sizeof(struct lo_dirp));
+
if (d == NULL)
+
goto out_err;
+
+
fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
if (fd == -1)
+
goto out_errno;
+
+
d->dp = fdopendir(fd);
+
if (d->dp == NULL)
+
goto out_errno;
+
+
d->offset = 0;
+
d->entry = NULL;
+
+
fi->fh = (uintptr_t) d;
+
if (lo->cache != CACHE_NEVER)
+
fi->cache_readdir = 1;
+
if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
fuse_reply_open(req, fi);
+
return;
+
+
out_errno:
+
error = errno;
+
out_err:
+
if (d) {
+
if (fd != -1)
+
close(fd);
+
free(d);
+
}
+
fuse_reply_err(req, error);
+
}
+
+
static int is_dot_or_dotdot(const char *name)
+
{
+
return name[0] == '.' && (name[1] == '\0' ||
+
(name[1] == '.' && name[2] == '\0'));
+
}
+
+
static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi, int plus)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
char *buf;
+
char *p;
+
size_t rem = size;
+
int err;
+
+
(void) ino;
+
+
buf = calloc(1, size);
+
if (!buf) {
+
err = ENOMEM;
+
goto error;
+
}
+
p = buf;
+
+
if (offset != d->offset) {
+
seekdir(d->dp, offset);
+
d->entry = NULL;
+
d->offset = offset;
+
}
+
while (1) {
+
size_t entsize;
+
off_t nextoff;
+
const char *name;
+
+
if (!d->entry) {
+
errno = 0;
+
d->entry = readdir(d->dp);
+
if (!d->entry) {
+
if (errno) { // Error
+
err = errno;
+
goto error;
+
} else { // End of stream
+
break;
+
}
+
}
+
}
+
nextoff = d->entry->d_off;
+
name = d->entry->d_name;
+
fuse_ino_t entry_ino = 0;
+
if (plus) {
+
struct fuse_entry_param e;
+
if (is_dot_or_dotdot(name)) {
+
e = (struct fuse_entry_param) {
+
.attr.st_ino = d->entry->d_ino,
+
.attr.st_mode = d->entry->d_type << 12,
+
};
+
} else {
+
err = lo_do_lookup(req, ino, name, &e);
+
if (err)
+
goto error;
+
entry_ino = e.ino;
+
}
+
+
entsize = fuse_add_direntry_plus(req, p, rem, name,
+
&e, nextoff);
+
} else {
+
struct stat st = {
+
.st_ino = d->entry->d_ino,
+
.st_mode = d->entry->d_type << 12,
+
};
+
entsize = fuse_add_direntry(req, p, rem, name,
+
&st, nextoff);
+
}
+
if (entsize > rem) {
+
if (entry_ino != 0)
+
lo_forget_one(req, entry_ino, 1);
+
break;
+
}
+
+
p += entsize;
+
rem -= entsize;
+
+
d->entry = NULL;
+
d->offset = nextoff;
+
}
+
+
err = 0;
+
error:
+
// If there's an error, we can only signal it if we haven't stored
+
// any entries yet - otherwise we'd end up with wrong lookup
+
// counts for the entries that are already in the buffer. So we
+
// return what we've collected until that point.
+
if (err && rem == size)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_buf(req, buf, size - rem);
+
free(buf);
+
}
+
+
static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 0);
+
}
+
+
static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
lo_do_readdir(req, ino, size, offset, fi, 1);
+
}
+
+
static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
struct lo_dirp *d = lo_dirp(fi);
+
(void) ino;
+
closedir(d->dp);
+
free(d);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
parent);
+
+
fd = openat(lo_fd(req, parent), ".",
+
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = fill_entry_param_new_inode(req, parent, fd, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
mode_t mode, struct fuse_file_info *fi)
+
{
+
int fd;
+
struct lo_data *lo = lo_data(req);
+
struct fuse_entry_param e;
+
int err;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
parent, name);
+
+
fd = openat(lo_fd(req, parent), name,
+
(fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
err = lo_do_lookup(req, parent, name, &e);
+
if (err)
+
fuse_reply_err(req, err);
+
else
+
fuse_reply_create(req, &e, fi);
+
}
+
+
static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
int fd = dirfd(lo_dirp(fi)->dp);
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fd);
+
else
+
res = fsync(fd);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int fd;
+
char buf[64];
+
struct lo_data *lo = lo_data(req);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
ino, fi->flags);
+
+
/* With writeback cache, kernel may send read requests even
+
when userspace opened write-only */
+
if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
fi->flags &= ~O_ACCMODE;
+
fi->flags |= O_RDWR;
+
}
+
+
/* With writeback cache, O_APPEND is handled by the kernel.
+
This breaks atomicity (since the file may change in the
+
underlying filesystem, so that the kernel's idea of the
+
end of the file isn't accurate anymore). In this example,
+
we just accept that. A more rigorous filesystem may want
+
to return an error here */
+
if (lo->writeback && (fi->flags & O_APPEND))
+
fi->flags &= ~O_APPEND;
+
+
sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
if (fd == -1)
+
return (void) fuse_reply_err(req, errno);
+
+
fi->fh = fd;
+
if (lo->cache == CACHE_NEVER)
+
fi->direct_io = 1;
+
else if (lo->cache == CACHE_ALWAYS)
+
fi->keep_cache = 1;
+
+
/* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
for writes to the same file in the kernel). */
+
if (fi->flags & O_DIRECT)
+
fi->direct_io = 1;
+
+
/* parallel_direct_writes feature depends on direct_io features.
+
To make parallel_direct_writes valid, need set fi->direct_io
+
in current function. */
+ +
+
fuse_reply_open(req, fi);
+
}
+
+
static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
(void) ino;
+
+
close(fi->fh);
+
fuse_reply_err(req, 0);
+
}
+
+
static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
res = close(dup(fi->fh));
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
struct fuse_file_info *fi)
+
{
+
int res;
+
(void) ino;
+
if (datasync)
+
res = fdatasync(fi->fh);
+
else
+
res = fsync(fi->fh);
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
"off=%lu)\n", ino, size, (unsigned long) offset);
+
+ +
buf.buf[0].fd = fi->fh;
+
buf.buf[0].pos = offset;
+
+ +
}
+
+
static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_bufvec *in_buf, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void) ino;
+
ssize_t res;
+
struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
+ +
out_buf.buf[0].fd = fi->fh;
+
out_buf.buf[0].pos = off;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
ino, out_buf.buf[0].size, (unsigned long) off);
+
+
res = fuse_buf_copy(&out_buf, in_buf, 0);
+
if(res < 0)
+
fuse_reply_err(req, -res);
+
else
+
fuse_reply_write(req, (size_t) res);
+
}
+
+
static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
{
+
int res;
+
struct statvfs stbuf;
+
+
res = fstatvfs(lo_fd(req, ino), &stbuf);
+
if (res == -1)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_statfs(req, &stbuf);
+
}
+
+
static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
off_t offset, off_t length, struct fuse_file_info *fi)
+
{
+
int err = EOPNOTSUPP;
+
(void) ino;
+
+
#ifdef HAVE_FALLOCATE
+
err = fallocate(fi->fh, mode, offset, length);
+
if (err < 0)
+
err = errno;
+
+
#elif defined(HAVE_POSIX_FALLOCATE)
+
if (mode) {
+
fuse_reply_err(req, EOPNOTSUPP);
+
return;
+
}
+
+
err = posix_fallocate(fi->fh, offset, length);
+
#endif
+
+
fuse_reply_err(req, err);
+
}
+
+
static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
int op)
+
{
+
int res;
+
(void) ino;
+
+
res = flock(fi->fh, op);
+
+
fuse_reply_err(req, res == -1 ? errno : 0);
+
}
+
+
static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
ino, name, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = getxattr(procname, name, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = getxattr(procname, name, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
{
+
char *value = NULL;
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
ino, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
if (size) {
+
value = malloc(size);
+
if (!value)
+
goto out_err;
+
+
ret = listxattr(procname, value, size);
+
if (ret == -1)
+
goto out_err;
+
saverr = 0;
+
if (ret == 0)
+
goto out;
+
+
fuse_reply_buf(req, value, ret);
+
} else {
+
ret = listxattr(procname, NULL, 0);
+
if (ret == -1)
+
goto out_err;
+
+
fuse_reply_xattr(req, ret);
+
}
+
out_free:
+
free(value);
+
return;
+
+
out_err:
+
saverr = errno;
+
out:
+
fuse_reply_err(req, saverr);
+
goto out_free;
+
}
+
+
static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
ino, name, value, size);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = setxattr(procname, name, value, size, flags);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
char procname[64];
+
struct lo_inode *inode = lo_inode(req, ino);
+
ssize_t ret;
+
int saverr;
+
+
saverr = ENOSYS;
+
if (!lo_data(req)->xattr)
+
goto out;
+
+
if (lo_debug(req)) {
+
fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
ino, name);
+
}
+
+
sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
+
ret = removexattr(procname, name);
+
saverr = ret == -1 ? errno : 0;
+
+
out:
+
fuse_reply_err(req, saverr);
+
}
+
+
#ifdef HAVE_COPY_FILE_RANGE
+
static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
struct fuse_file_info *fi_in,
+
fuse_ino_t ino_out, off_t off_out,
+
struct fuse_file_info *fi_out, size_t len,
+
int flags)
+
{
+
ssize_t res;
+
+
if (lo_debug(req))
+
fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
"off=%lu, size=%zd, flags=0x%x)\n",
+
ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
len, flags);
+
+
res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
flags);
+
if (res < 0)
+
fuse_reply_err(req, errno);
+
else
+
fuse_reply_write(req, res);
+
}
+
#endif
+
+
static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
struct fuse_file_info *fi)
+
{
+
off_t res;
+
+
(void)ino;
+
res = lseek(fi->fh, off, whence);
+
if (res != -1)
+
fuse_reply_lseek(req, res);
+
else
+
fuse_reply_err(req, errno);
+
}
+
+
static const struct fuse_lowlevel_ops lo_oper = {
+
.init = lo_init,
+
.destroy = lo_destroy,
+
.lookup = lo_lookup,
+
.mkdir = lo_mkdir,
+
.mknod = lo_mknod,
+
.symlink = lo_symlink,
+
.link = lo_link,
+
.unlink = lo_unlink,
+
.rmdir = lo_rmdir,
+
.rename = lo_rename,
+
.forget = lo_forget,
+
.forget_multi = lo_forget_multi,
+
.getattr = lo_getattr,
+
.setattr = lo_setattr,
+
.readlink = lo_readlink,
+
.opendir = lo_opendir,
+
.readdir = lo_readdir,
+
.readdirplus = lo_readdirplus,
+
.releasedir = lo_releasedir,
+
.fsyncdir = lo_fsyncdir,
+
.create = lo_create,
+
.tmpfile = lo_tmpfile,
+
.open = lo_open,
+
.release = lo_release,
+
.flush = lo_flush,
+
.fsync = lo_fsync,
+
.read = lo_read,
+
.write_buf = lo_write_buf,
+
.statfs = lo_statfs,
+
.fallocate = lo_fallocate,
+
.flock = lo_flock,
+
.getxattr = lo_getxattr,
+
.listxattr = lo_listxattr,
+
.setxattr = lo_setxattr,
+
.removexattr = lo_removexattr,
+
#ifdef HAVE_COPY_FILE_RANGE
+
.copy_file_range = lo_copy_file_range,
+
#endif
+
.lseek = lo_lseek,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
struct lo_data lo = { .debug = 0,
+
.writeback = 0 };
+
int ret = -1;
+
+
/* Don't mask creation mode, kernel already did that */
+
umask(0);
+
+
pthread_mutex_init(&lo.mutex, NULL);
+
lo.root.next = lo.root.prev = &lo.root;
+
lo.root.fd = -1;
+
lo.cache = CACHE_NORMAL;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
passthrough_ll_help();
+
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
return 1;
+
+
lo.debug = opts.debug;
+
lo.root.refcount = 2;
+
if (lo.source) {
+
struct stat stat;
+
int res;
+
+
res = lstat(lo.source, &stat);
+
if (res == -1) {
+
fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
lo.source);
+
exit(1);
+
}
+
if (!S_ISDIR(stat.st_mode)) {
+
fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
exit(1);
+
}
+
+
} else {
+
lo.source = strdup("/");
+
if(!lo.source) {
+
fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
exit(1);
+
}
+
}
+
if (!lo.timeout_set) {
+
switch (lo.cache) {
+
case CACHE_NEVER:
+
lo.timeout = 0.0;
+
break;
+
+
case CACHE_NORMAL:
+
lo.timeout = 1.0;
+
break;
+
+
case CACHE_ALWAYS:
+
lo.timeout = 86400.0;
+
break;
+
}
+
} else if (lo.timeout < 0) {
+
fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
lo.timeout);
+
exit(1);
+
}
+
+
lo.root.fd = open(lo.source, O_PATH);
+
if (lo.root.fd == -1) {
+
fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
lo.source);
+
exit(1);
+
}
+
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
if (lo.root.fd >= 0)
+
close(lo.root.fd);
+
+
free(lo.source);
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+

Definition in file passthrough_ll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c_source.html new file mode 100644 index 0000000..bf56fb0 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2passthrough__ll_8c_source.html @@ -0,0 +1,1508 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/passthrough_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
passthrough_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
37#define _GNU_SOURCE
+
38#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
39
+
40#include <fuse_lowlevel.h>
+
41#include <unistd.h>
+
42#include <stdlib.h>
+
43#include <stdio.h>
+
44#include <stddef.h>
+
45#include <stdbool.h>
+
46#include <string.h>
+
47#include <limits.h>
+
48#include <dirent.h>
+
49#include <assert.h>
+
50#include <errno.h>
+
51#include <inttypes.h>
+
52#include <pthread.h>
+
53#include <sys/file.h>
+
54#include <sys/xattr.h>
+
55
+
56#include "passthrough_helpers.h"
+
57
+
58/* We are re-using pointers to our `struct lo_inode` and `struct
+
59 lo_dirp` elements as inodes. This means that we must be able to
+
60 store uintptr_t values in a fuse_ino_t variable. The following
+
61 incantation checks this condition at compile time. */
+
62#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+
63_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
+
64 "fuse_ino_t too small to hold uintptr_t values!");
+
65#else
+
66struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
+
67 { unsigned _uintptr_to_must_hold_fuse_ino_t:
+
68 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
+
69#endif
+
70
+
71struct lo_inode {
+
72 struct lo_inode *next; /* protected by lo->mutex */
+
73 struct lo_inode *prev; /* protected by lo->mutex */
+
74 int fd;
+
75 ino_t ino;
+
76 dev_t dev;
+
77 uint64_t refcount; /* protected by lo->mutex */
+
78};
+
79
+
80enum {
+
81 CACHE_NEVER,
+
82 CACHE_NORMAL,
+
83 CACHE_ALWAYS,
+
84};
+
85
+
86struct lo_data {
+
87 pthread_mutex_t mutex;
+
88 int debug;
+
89 int writeback;
+
90 int flock;
+
91 int xattr;
+
92 char *source;
+
93 double timeout;
+
94 int cache;
+
95 int timeout_set;
+
96 struct lo_inode root; /* protected by lo->mutex */
+
97};
+
98
+
99static const struct fuse_opt lo_opts[] = {
+
100 { "writeback",
+
101 offsetof(struct lo_data, writeback), 1 },
+
102 { "no_writeback",
+
103 offsetof(struct lo_data, writeback), 0 },
+
104 { "source=%s",
+
105 offsetof(struct lo_data, source), 0 },
+
106 { "flock",
+
107 offsetof(struct lo_data, flock), 1 },
+
108 { "no_flock",
+
109 offsetof(struct lo_data, flock), 0 },
+
110 { "xattr",
+
111 offsetof(struct lo_data, xattr), 1 },
+
112 { "no_xattr",
+
113 offsetof(struct lo_data, xattr), 0 },
+
114 { "timeout=%lf",
+
115 offsetof(struct lo_data, timeout), 0 },
+
116 { "timeout=",
+
117 offsetof(struct lo_data, timeout_set), 1 },
+
118 { "cache=never",
+
119 offsetof(struct lo_data, cache), CACHE_NEVER },
+
120 { "cache=auto",
+
121 offsetof(struct lo_data, cache), CACHE_NORMAL },
+
122 { "cache=always",
+
123 offsetof(struct lo_data, cache), CACHE_ALWAYS },
+
124
+ +
126};
+
127
+
128static void passthrough_ll_help(void)
+
129{
+
130 printf(
+
131" -o writeback Enable writeback\n"
+
132" -o no_writeback Disable write back\n"
+
133" -o source=/home/dir Source directory to be mounted\n"
+
134" -o flock Enable flock\n"
+
135" -o no_flock Disable flock\n"
+
136" -o xattr Enable xattr\n"
+
137" -o no_xattr Disable xattr\n"
+
138" -o timeout=1.0 Caching timeout\n"
+
139" -o timeout=0/1 Timeout is set\n"
+
140" -o cache=never Disable cache\n"
+
141" -o cache=auto Auto enable cache\n"
+
142" -o cache=always Cache always\n");
+
143}
+
144
+
145static struct lo_data *lo_data(fuse_req_t req)
+
146{
+
147 return (struct lo_data *) fuse_req_userdata(req);
+
148}
+
149
+
150static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+
151{
+
152 if (ino == FUSE_ROOT_ID)
+
153 return &lo_data(req)->root;
+
154 else
+
155 return (struct lo_inode *) (uintptr_t) ino;
+
156}
+
157
+
158static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+
159{
+
160 return lo_inode(req, ino)->fd;
+
161}
+
162
+
163static bool lo_debug(fuse_req_t req)
+
164{
+
165 return lo_data(req)->debug != 0;
+
166}
+
167
+
168static void lo_init(void *userdata,
+
169 struct fuse_conn_info *conn)
+
170{
+
171 struct lo_data *lo = (struct lo_data *)userdata;
+
172 bool has_flag;
+
173
+
174 if (lo->writeback) {
+
175 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
176 if (lo->debug && has_flag)
+
177 fuse_log(FUSE_LOG_DEBUG,
+
178 "lo_init: activating writeback\n");
+
179 }
+
180 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
+
181 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
182 if (lo->debug && has_flag)
+
183 fuse_log(FUSE_LOG_DEBUG,
+
184 "lo_init: activating flock locks\n");
+
185 }
+
186
+
187 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
188 conn->no_interrupt = 1;
+
189}
+
190
+
191static void lo_destroy(void *userdata)
+
192{
+
193 struct lo_data *lo = (struct lo_data*) userdata;
+
194
+
195 while (lo->root.next != &lo->root) {
+
196 struct lo_inode* next = lo->root.next;
+
197 lo->root.next = next->next;
+
198 close(next->fd);
+
199 free(next);
+
200 }
+
201}
+
202
+
203static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+
204 struct fuse_file_info *fi)
+
205{
+
206 int res;
+
207 struct stat buf;
+
208 struct lo_data *lo = lo_data(req);
+
209 int fd = fi ? fi->fh : lo_fd(req, ino);
+
210
+
211 (void) fi;
+
212
+
213 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
214 if (res == -1)
+
215 return (void) fuse_reply_err(req, errno);
+
216
+
217 fuse_reply_attr(req, &buf, lo->timeout);
+
218}
+
219
+
220static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
221 int valid, struct fuse_file_info *fi)
+
222{
+
223 int saverr;
+
224 char procname[64];
+
225 struct lo_inode *inode = lo_inode(req, ino);
+
226 int ifd = inode->fd;
+
227 int res;
+
228
+
229 if (valid & FUSE_SET_ATTR_MODE) {
+
230 if (fi) {
+
231 res = fchmod(fi->fh, attr->st_mode);
+
232 } else {
+
233 sprintf(procname, "/proc/self/fd/%i", ifd);
+
234 res = chmod(procname, attr->st_mode);
+
235 }
+
236 if (res == -1)
+
237 goto out_err;
+
238 }
+
239 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
+
240 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
241 attr->st_uid : (uid_t) -1;
+
242 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
243 attr->st_gid : (gid_t) -1;
+
244
+
245 res = fchownat(ifd, "", uid, gid,
+
246 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
247 if (res == -1)
+
248 goto out_err;
+
249 }
+
250 if (valid & FUSE_SET_ATTR_SIZE) {
+
251 if (fi) {
+
252 res = ftruncate(fi->fh, attr->st_size);
+
253 } else {
+
254 sprintf(procname, "/proc/self/fd/%i", ifd);
+
255 res = truncate(procname, attr->st_size);
+
256 }
+
257 if (res == -1)
+
258 goto out_err;
+
259 }
+
260 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
261 struct timespec tv[2];
+
262
+
263 tv[0].tv_sec = 0;
+
264 tv[1].tv_sec = 0;
+
265 tv[0].tv_nsec = UTIME_OMIT;
+
266 tv[1].tv_nsec = UTIME_OMIT;
+
267
+
268 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
269 tv[0].tv_nsec = UTIME_NOW;
+
270 else if (valid & FUSE_SET_ATTR_ATIME)
+
271 tv[0] = attr->st_atim;
+
272
+
273 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
274 tv[1].tv_nsec = UTIME_NOW;
+
275 else if (valid & FUSE_SET_ATTR_MTIME)
+
276 tv[1] = attr->st_mtim;
+
277
+
278 if (fi)
+
279 res = futimens(fi->fh, tv);
+
280 else {
+
281 sprintf(procname, "/proc/self/fd/%i", ifd);
+
282 res = utimensat(AT_FDCWD, procname, tv, 0);
+
283 }
+
284 if (res == -1)
+
285 goto out_err;
+
286 }
+
287
+
288 return lo_getattr(req, ino, fi);
+
289
+
290out_err:
+
291 saverr = errno;
+
292 fuse_reply_err(req, saverr);
+
293}
+
294
+
295static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+
296{
+
297 struct lo_inode *p;
+
298 struct lo_inode *ret = NULL;
+
299
+
300 pthread_mutex_lock(&lo->mutex);
+
301 for (p = lo->root.next; p != &lo->root; p = p->next) {
+
302 if (p->ino == st->st_ino && p->dev == st->st_dev) {
+
303 assert(p->refcount > 0);
+
304 ret = p;
+
305 ret->refcount++;
+
306 break;
+
307 }
+
308 }
+
309 pthread_mutex_unlock(&lo->mutex);
+
310 return ret;
+
311}
+
312
+
313
+
314static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
+
315{
+
316 struct lo_inode *inode = NULL;
+
317 struct lo_inode *prev, *next;
+
318
+
319 inode = calloc(1, sizeof(struct lo_inode));
+
320 if (!inode)
+
321 return NULL;
+
322
+
323 inode->refcount = 1;
+
324 inode->fd = fd;
+
325 inode->ino = e->attr.st_ino;
+
326 inode->dev = e->attr.st_dev;
+
327
+
328 pthread_mutex_lock(&lo->mutex);
+
329 prev = &lo->root;
+
330 next = prev->next;
+
331 next->prev = inode;
+
332 inode->next = next;
+
333 inode->prev = prev;
+
334 prev->next = inode;
+
335 pthread_mutex_unlock(&lo->mutex);
+
336 return inode;
+
337}
+
338
+
339static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
+
340{
+
341 int res;
+
342 struct lo_data *lo = lo_data(req);
+
343
+
344 memset(e, 0, sizeof(*e));
+
345 e->attr_timeout = lo->timeout;
+
346 e->entry_timeout = lo->timeout;
+
347
+
348 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
349 if (res == -1)
+
350 return errno;
+
351
+
352 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
+
353
+
354 if (lo_debug(req))
+
355 fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
+
356 (unsigned long long) parent, fd, (unsigned long long) e->ino);
+
357
+
358 return 0;
+
359
+
360}
+
361
+
362static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+
363 struct fuse_entry_param *e)
+
364{
+
365 int newfd;
+
366 int res;
+
367 int saverr;
+
368 struct lo_data *lo = lo_data(req);
+
369 struct lo_inode *inode;
+
370
+
371 memset(e, 0, sizeof(*e));
+
372 e->attr_timeout = lo->timeout;
+
373 e->entry_timeout = lo->timeout;
+
374
+
375 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+
376 if (newfd == -1)
+
377 goto out_err;
+
378
+
379 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
380 if (res == -1)
+
381 goto out_err;
+
382
+
383 inode = lo_find(lo_data(req), &e->attr);
+
384 if (inode) {
+
385 close(newfd);
+
386 newfd = -1;
+
387 } else {
+
388 inode = create_new_inode(newfd, e, lo);
+
389 if (!inode)
+
390 goto out_err;
+
391 }
+
392 e->ino = (uintptr_t) inode;
+
393
+
394 if (lo_debug(req))
+
395 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
396 (unsigned long long) parent, name, (unsigned long long) e->ino);
+
397
+
398 return 0;
+
399
+
400out_err:
+
401 saverr = errno;
+
402 if (newfd != -1)
+
403 close(newfd);
+
404 return saverr;
+
405}
+
406
+
407static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
408{
+
409 struct fuse_entry_param e;
+
410 int err;
+
411
+
412 if (lo_debug(req))
+
413 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
+
414 parent, name);
+
415
+
416 err = lo_do_lookup(req, parent, name, &e);
+
417 if (err)
+
418 fuse_reply_err(req, err);
+
419 else
+
420 fuse_reply_entry(req, &e);
+
421}
+
422
+
423static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
+
424 const char *name, mode_t mode, dev_t rdev,
+
425 const char *link)
+
426{
+
427 int res;
+
428 int saverr;
+
429 struct lo_inode *dir = lo_inode(req, parent);
+
430 struct fuse_entry_param e;
+
431
+
432 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
433
+
434 saverr = errno;
+
435 if (res == -1)
+
436 goto out;
+
437
+
438 saverr = lo_do_lookup(req, parent, name, &e);
+
439 if (saverr)
+
440 goto out;
+
441
+
442 if (lo_debug(req))
+
443 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
444 (unsigned long long) parent, name, (unsigned long long) e.ino);
+
445
+
446 fuse_reply_entry(req, &e);
+
447 return;
+
448
+
449out:
+
450 fuse_reply_err(req, saverr);
+
451}
+
452
+
453static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
+
454 const char *name, mode_t mode, dev_t rdev)
+
455{
+
456 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
+
457}
+
458
+
459static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
460 mode_t mode)
+
461{
+
462 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
+
463}
+
464
+
465static void lo_symlink(fuse_req_t req, const char *link,
+
466 fuse_ino_t parent, const char *name)
+
467{
+
468 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
+
469}
+
470
+
471static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
+
472 const char *name)
+
473{
+
474 int res;
+
475 struct lo_data *lo = lo_data(req);
+
476 struct lo_inode *inode = lo_inode(req, ino);
+
477 struct fuse_entry_param e;
+
478 char procname[64];
+
479 int saverr;
+
480
+
481 memset(&e, 0, sizeof(struct fuse_entry_param));
+
482 e.attr_timeout = lo->timeout;
+
483 e.entry_timeout = lo->timeout;
+
484
+
485 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
486 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
+
487 AT_SYMLINK_FOLLOW);
+
488 if (res == -1)
+
489 goto out_err;
+
490
+
491 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+
492 if (res == -1)
+
493 goto out_err;
+
494
+
495 pthread_mutex_lock(&lo->mutex);
+
496 inode->refcount++;
+
497 pthread_mutex_unlock(&lo->mutex);
+
498 e.ino = (uintptr_t) inode;
+
499
+
500 if (lo_debug(req))
+
501 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
+
502 (unsigned long long) parent, name,
+
503 (unsigned long long) e.ino);
+
504
+
505 fuse_reply_entry(req, &e);
+
506 return;
+
507
+
508out_err:
+
509 saverr = errno;
+
510 fuse_reply_err(req, saverr);
+
511}
+
512
+
513static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
514{
+
515 int res;
+
516
+
517 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
+
518
+
519 fuse_reply_err(req, res == -1 ? errno : 0);
+
520}
+
521
+
522static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
+
523 fuse_ino_t newparent, const char *newname,
+
524 unsigned int flags)
+
525{
+
526 int res;
+
527
+
528 if (flags) {
+
529 fuse_reply_err(req, EINVAL);
+
530 return;
+
531 }
+
532
+
533 res = renameat(lo_fd(req, parent), name,
+
534 lo_fd(req, newparent), newname);
+
535
+
536 fuse_reply_err(req, res == -1 ? errno : 0);
+
537}
+
538
+
539static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
+
540{
+
541 int res;
+
542
+
543 res = unlinkat(lo_fd(req, parent), name, 0);
+
544
+
545 fuse_reply_err(req, res == -1 ? errno : 0);
+
546}
+
547
+
548static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
+
549{
+
550 if (!inode)
+
551 return;
+
552
+
553 pthread_mutex_lock(&lo->mutex);
+
554 assert(inode->refcount >= n);
+
555 inode->refcount -= n;
+
556 if (!inode->refcount) {
+
557 struct lo_inode *prev, *next;
+
558
+
559 prev = inode->prev;
+
560 next = inode->next;
+
561 next->prev = prev;
+
562 prev->next = next;
+
563
+
564 pthread_mutex_unlock(&lo->mutex);
+
565 close(inode->fd);
+
566 free(inode);
+
567
+
568 } else {
+
569 pthread_mutex_unlock(&lo->mutex);
+
570 }
+
571}
+
572
+
573static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
574{
+
575 struct lo_data *lo = lo_data(req);
+
576 struct lo_inode *inode = lo_inode(req, ino);
+
577
+
578 if (lo_debug(req)) {
+
579 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
+
580 (unsigned long long) ino,
+
581 (unsigned long long) inode->refcount,
+
582 (unsigned long long) nlookup);
+
583 }
+
584
+
585 unref_inode(lo, inode, nlookup);
+
586}
+
587
+
588static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
589{
+
590 lo_forget_one(req, ino, nlookup);
+
591 fuse_reply_none(req);
+
592}
+
593
+
594static void lo_forget_multi(fuse_req_t req, size_t count,
+
595 struct fuse_forget_data *forgets)
+
596{
+
597 int i;
+
598
+
599 for (i = 0; i < count; i++)
+
600 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
+
601 fuse_reply_none(req);
+
602}
+
603
+
604static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+
605{
+
606 char buf[PATH_MAX + 1];
+
607 int res;
+
608
+
609 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+
610 if (res == -1)
+
611 return (void) fuse_reply_err(req, errno);
+
612
+
613 if (res == sizeof(buf))
+
614 return (void) fuse_reply_err(req, ENAMETOOLONG);
+
615
+
616 buf[res] = '\0';
+
617
+
618 fuse_reply_readlink(req, buf);
+
619}
+
620
+
621struct lo_dirp {
+
622 DIR *dp;
+
623 struct dirent *entry;
+
624 off_t offset;
+
625};
+
626
+
627static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+
628{
+
629 return (struct lo_dirp *) (uintptr_t) fi->fh;
+
630}
+
631
+
632static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
633{
+
634 int error = ENOMEM;
+
635 struct lo_data *lo = lo_data(req);
+
636 struct lo_dirp *d;
+
637 int fd = -1;
+
638
+
639 d = calloc(1, sizeof(struct lo_dirp));
+
640 if (d == NULL)
+
641 goto out_err;
+
642
+
643 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+
644 if (fd == -1)
+
645 goto out_errno;
+
646
+
647 d->dp = fdopendir(fd);
+
648 if (d->dp == NULL)
+
649 goto out_errno;
+
650
+
651 d->offset = 0;
+
652 d->entry = NULL;
+
653
+
654 fi->fh = (uintptr_t) d;
+
655 if (lo->cache != CACHE_NEVER)
+
656 fi->cache_readdir = 1;
+
657 if (lo->cache == CACHE_ALWAYS)
+
658 fi->keep_cache = 1;
+
659 fuse_reply_open(req, fi);
+
660 return;
+
661
+
662out_errno:
+
663 error = errno;
+
664out_err:
+
665 if (d) {
+
666 if (fd != -1)
+
667 close(fd);
+
668 free(d);
+
669 }
+
670 fuse_reply_err(req, error);
+
671}
+
672
+
673static int is_dot_or_dotdot(const char *name)
+
674{
+
675 return name[0] == '.' && (name[1] == '\0' ||
+
676 (name[1] == '.' && name[2] == '\0'));
+
677}
+
678
+
679static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
680 off_t offset, struct fuse_file_info *fi, int plus)
+
681{
+
682 struct lo_dirp *d = lo_dirp(fi);
+
683 char *buf;
+
684 char *p;
+
685 size_t rem = size;
+
686 int err;
+
687
+
688 (void) ino;
+
689
+
690 buf = calloc(1, size);
+
691 if (!buf) {
+
692 err = ENOMEM;
+
693 goto error;
+
694 }
+
695 p = buf;
+
696
+
697 if (offset != d->offset) {
+
698 seekdir(d->dp, offset);
+
699 d->entry = NULL;
+
700 d->offset = offset;
+
701 }
+
702 while (1) {
+
703 size_t entsize;
+
704 off_t nextoff;
+
705 const char *name;
+
706
+
707 if (!d->entry) {
+
708 errno = 0;
+
709 d->entry = readdir(d->dp);
+
710 if (!d->entry) {
+
711 if (errno) { // Error
+
712 err = errno;
+
713 goto error;
+
714 } else { // End of stream
+
715 break;
+
716 }
+
717 }
+
718 }
+
719 nextoff = d->entry->d_off;
+
720 name = d->entry->d_name;
+
721 fuse_ino_t entry_ino = 0;
+
722 if (plus) {
+
723 struct fuse_entry_param e;
+
724 if (is_dot_or_dotdot(name)) {
+
725 e = (struct fuse_entry_param) {
+
726 .attr.st_ino = d->entry->d_ino,
+
727 .attr.st_mode = d->entry->d_type << 12,
+
728 };
+
729 } else {
+
730 err = lo_do_lookup(req, ino, name, &e);
+
731 if (err)
+
732 goto error;
+
733 entry_ino = e.ino;
+
734 }
+
735
+
736 entsize = fuse_add_direntry_plus(req, p, rem, name,
+
737 &e, nextoff);
+
738 } else {
+
739 struct stat st = {
+
740 .st_ino = d->entry->d_ino,
+
741 .st_mode = d->entry->d_type << 12,
+
742 };
+
743 entsize = fuse_add_direntry(req, p, rem, name,
+
744 &st, nextoff);
+
745 }
+
746 if (entsize > rem) {
+
747 if (entry_ino != 0)
+
748 lo_forget_one(req, entry_ino, 1);
+
749 break;
+
750 }
+
751
+
752 p += entsize;
+
753 rem -= entsize;
+
754
+
755 d->entry = NULL;
+
756 d->offset = nextoff;
+
757 }
+
758
+
759 err = 0;
+
760error:
+
761 // If there's an error, we can only signal it if we haven't stored
+
762 // any entries yet - otherwise we'd end up with wrong lookup
+
763 // counts for the entries that are already in the buffer. So we
+
764 // return what we've collected until that point.
+
765 if (err && rem == size)
+
766 fuse_reply_err(req, err);
+
767 else
+
768 fuse_reply_buf(req, buf, size - rem);
+
769 free(buf);
+
770}
+
771
+
772static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
773 off_t offset, struct fuse_file_info *fi)
+
774{
+
775 lo_do_readdir(req, ino, size, offset, fi, 0);
+
776}
+
777
+
778static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
779 off_t offset, struct fuse_file_info *fi)
+
780{
+
781 lo_do_readdir(req, ino, size, offset, fi, 1);
+
782}
+
783
+
784static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
785{
+
786 struct lo_dirp *d = lo_dirp(fi);
+
787 (void) ino;
+
788 closedir(d->dp);
+
789 free(d);
+
790 fuse_reply_err(req, 0);
+
791}
+
792
+
793static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
+
794 mode_t mode, struct fuse_file_info *fi)
+
795{
+
796 int fd;
+
797 struct lo_data *lo = lo_data(req);
+
798 struct fuse_entry_param e;
+
799 int err;
+
800
+
801 if (lo_debug(req))
+
802 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
+
803 parent);
+
804
+
805 fd = openat(lo_fd(req, parent), ".",
+
806 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
+
807 if (fd == -1)
+
808 return (void) fuse_reply_err(req, errno);
+
809
+
810 fi->fh = fd;
+
811 if (lo->cache == CACHE_NEVER)
+
812 fi->direct_io = 1;
+
813 else if (lo->cache == CACHE_ALWAYS)
+
814 fi->keep_cache = 1;
+
815
+
816 /* parallel_direct_writes feature depends on direct_io features.
+
817 To make parallel_direct_writes valid, need set fi->direct_io
+
818 in current function. */
+
819 fi->parallel_direct_writes = 1;
+
820
+
821 err = fill_entry_param_new_inode(req, parent, fd, &e);
+
822 if (err)
+
823 fuse_reply_err(req, err);
+
824 else
+
825 fuse_reply_create(req, &e, fi);
+
826}
+
827
+
828static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+
829 mode_t mode, struct fuse_file_info *fi)
+
830{
+
831 int fd;
+
832 struct lo_data *lo = lo_data(req);
+
833 struct fuse_entry_param e;
+
834 int err;
+
835
+
836 if (lo_debug(req))
+
837 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
+
838 parent, name);
+
839
+
840 fd = openat(lo_fd(req, parent), name,
+
841 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
+
842 if (fd == -1)
+
843 return (void) fuse_reply_err(req, errno);
+
844
+
845 fi->fh = fd;
+
846 if (lo->cache == CACHE_NEVER)
+
847 fi->direct_io = 1;
+
848 else if (lo->cache == CACHE_ALWAYS)
+
849 fi->keep_cache = 1;
+
850
+
851 /* parallel_direct_writes feature depends on direct_io features.
+
852 To make parallel_direct_writes valid, need set fi->direct_io
+
853 in current function. */
+ +
855
+
856 err = lo_do_lookup(req, parent, name, &e);
+
857 if (err)
+
858 fuse_reply_err(req, err);
+
859 else
+
860 fuse_reply_create(req, &e, fi);
+
861}
+
862
+
863static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
864 struct fuse_file_info *fi)
+
865{
+
866 int res;
+
867 int fd = dirfd(lo_dirp(fi)->dp);
+
868 (void) ino;
+
869 if (datasync)
+
870 res = fdatasync(fd);
+
871 else
+
872 res = fsync(fd);
+
873 fuse_reply_err(req, res == -1 ? errno : 0);
+
874}
+
875
+
876static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
877{
+
878 int fd;
+
879 char buf[64];
+
880 struct lo_data *lo = lo_data(req);
+
881
+
882 if (lo_debug(req))
+
883 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
+
884 ino, fi->flags);
+
885
+
886 /* With writeback cache, kernel may send read requests even
+
887 when userspace opened write-only */
+
888 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
+
889 fi->flags &= ~O_ACCMODE;
+
890 fi->flags |= O_RDWR;
+
891 }
+
892
+
893 /* With writeback cache, O_APPEND is handled by the kernel.
+
894 This breaks atomicity (since the file may change in the
+
895 underlying filesystem, so that the kernel's idea of the
+
896 end of the file isn't accurate anymore). In this example,
+
897 we just accept that. A more rigorous filesystem may want
+
898 to return an error here */
+
899 if (lo->writeback && (fi->flags & O_APPEND))
+
900 fi->flags &= ~O_APPEND;
+
901
+
902 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+
903 fd = open(buf, fi->flags & ~O_NOFOLLOW);
+
904 if (fd == -1)
+
905 return (void) fuse_reply_err(req, errno);
+
906
+
907 fi->fh = fd;
+
908 if (lo->cache == CACHE_NEVER)
+
909 fi->direct_io = 1;
+
910 else if (lo->cache == CACHE_ALWAYS)
+
911 fi->keep_cache = 1;
+
912
+
913 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
+
914 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
+
915 for writes to the same file in the kernel). */
+
916 if (fi->flags & O_DIRECT)
+
917 fi->direct_io = 1;
+
918
+
919 /* parallel_direct_writes feature depends on direct_io features.
+
920 To make parallel_direct_writes valid, need set fi->direct_io
+
921 in current function. */
+ +
923
+
924 fuse_reply_open(req, fi);
+
925}
+
926
+
927static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
928{
+
929 (void) ino;
+
930
+
931 close(fi->fh);
+
932 fuse_reply_err(req, 0);
+
933}
+
934
+
935static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
936{
+
937 int res;
+
938 (void) ino;
+
939 res = close(dup(fi->fh));
+
940 fuse_reply_err(req, res == -1 ? errno : 0);
+
941}
+
942
+
943static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
944 struct fuse_file_info *fi)
+
945{
+
946 int res;
+
947 (void) ino;
+
948 if (datasync)
+
949 res = fdatasync(fi->fh);
+
950 else
+
951 res = fsync(fi->fh);
+
952 fuse_reply_err(req, res == -1 ? errno : 0);
+
953}
+
954
+
955static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
956 off_t offset, struct fuse_file_info *fi)
+
957{
+
958 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
959
+
960 if (lo_debug(req))
+
961 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
+
962 "off=%lu)\n", ino, size, (unsigned long) offset);
+
963
+ +
965 buf.buf[0].fd = fi->fh;
+
966 buf.buf[0].pos = offset;
+
967
+ +
969}
+
970
+
971static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
+
972 struct fuse_bufvec *in_buf, off_t off,
+
973 struct fuse_file_info *fi)
+
974{
+
975 (void) ino;
+
976 ssize_t res;
+
977 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
+
978
+ +
980 out_buf.buf[0].fd = fi->fh;
+
981 out_buf.buf[0].pos = off;
+
982
+
983 if (lo_debug(req))
+
984 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
+
985 ino, out_buf.buf[0].size, (unsigned long) off);
+
986
+
987 res = fuse_buf_copy(&out_buf, in_buf, 0);
+
988 if(res < 0)
+
989 fuse_reply_err(req, -res);
+
990 else
+
991 fuse_reply_write(req, (size_t) res);
+
992}
+
993
+
994static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
+
995{
+
996 int res;
+
997 struct statvfs stbuf;
+
998
+
999 res = fstatvfs(lo_fd(req, ino), &stbuf);
+
1000 if (res == -1)
+
1001 fuse_reply_err(req, errno);
+
1002 else
+
1003 fuse_reply_statfs(req, &stbuf);
+
1004}
+
1005
+
1006static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
1007 off_t offset, off_t length, struct fuse_file_info *fi)
+
1008{
+
1009 int err = EOPNOTSUPP;
+
1010 (void) ino;
+
1011
+
1012#ifdef HAVE_FALLOCATE
+
1013 err = fallocate(fi->fh, mode, offset, length);
+
1014 if (err < 0)
+
1015 err = errno;
+
1016
+
1017#elif defined(HAVE_POSIX_FALLOCATE)
+
1018 if (mode) {
+
1019 fuse_reply_err(req, EOPNOTSUPP);
+
1020 return;
+
1021 }
+
1022
+
1023 err = posix_fallocate(fi->fh, offset, length);
+
1024#endif
+
1025
+
1026 fuse_reply_err(req, err);
+
1027}
+
1028
+
1029static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1030 int op)
+
1031{
+
1032 int res;
+
1033 (void) ino;
+
1034
+
1035 res = flock(fi->fh, op);
+
1036
+
1037 fuse_reply_err(req, res == -1 ? errno : 0);
+
1038}
+
1039
+
1040static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1041 size_t size)
+
1042{
+
1043 char *value = NULL;
+
1044 char procname[64];
+
1045 struct lo_inode *inode = lo_inode(req, ino);
+
1046 ssize_t ret;
+
1047 int saverr;
+
1048
+
1049 saverr = ENOSYS;
+
1050 if (!lo_data(req)->xattr)
+
1051 goto out;
+
1052
+
1053 if (lo_debug(req)) {
+
1054 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
+
1055 ino, name, size);
+
1056 }
+
1057
+
1058 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1059
+
1060 if (size) {
+
1061 value = malloc(size);
+
1062 if (!value)
+
1063 goto out_err;
+
1064
+
1065 ret = getxattr(procname, name, value, size);
+
1066 if (ret == -1)
+
1067 goto out_err;
+
1068 saverr = 0;
+
1069 if (ret == 0)
+
1070 goto out;
+
1071
+
1072 fuse_reply_buf(req, value, ret);
+
1073 } else {
+
1074 ret = getxattr(procname, name, NULL, 0);
+
1075 if (ret == -1)
+
1076 goto out_err;
+
1077
+
1078 fuse_reply_xattr(req, ret);
+
1079 }
+
1080out_free:
+
1081 free(value);
+
1082 return;
+
1083
+
1084out_err:
+
1085 saverr = errno;
+
1086out:
+
1087 fuse_reply_err(req, saverr);
+
1088 goto out_free;
+
1089}
+
1090
+
1091static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
1092{
+
1093 char *value = NULL;
+
1094 char procname[64];
+
1095 struct lo_inode *inode = lo_inode(req, ino);
+
1096 ssize_t ret;
+
1097 int saverr;
+
1098
+
1099 saverr = ENOSYS;
+
1100 if (!lo_data(req)->xattr)
+
1101 goto out;
+
1102
+
1103 if (lo_debug(req)) {
+
1104 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
+
1105 ino, size);
+
1106 }
+
1107
+
1108 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1109
+
1110 if (size) {
+
1111 value = malloc(size);
+
1112 if (!value)
+
1113 goto out_err;
+
1114
+
1115 ret = listxattr(procname, value, size);
+
1116 if (ret == -1)
+
1117 goto out_err;
+
1118 saverr = 0;
+
1119 if (ret == 0)
+
1120 goto out;
+
1121
+
1122 fuse_reply_buf(req, value, ret);
+
1123 } else {
+
1124 ret = listxattr(procname, NULL, 0);
+
1125 if (ret == -1)
+
1126 goto out_err;
+
1127
+
1128 fuse_reply_xattr(req, ret);
+
1129 }
+
1130out_free:
+
1131 free(value);
+
1132 return;
+
1133
+
1134out_err:
+
1135 saverr = errno;
+
1136out:
+
1137 fuse_reply_err(req, saverr);
+
1138 goto out_free;
+
1139}
+
1140
+
1141static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
1142 const char *value, size_t size, int flags)
+
1143{
+
1144 char procname[64];
+
1145 struct lo_inode *inode = lo_inode(req, ino);
+
1146 ssize_t ret;
+
1147 int saverr;
+
1148
+
1149 saverr = ENOSYS;
+
1150 if (!lo_data(req)->xattr)
+
1151 goto out;
+
1152
+
1153 if (lo_debug(req)) {
+
1154 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
+
1155 ino, name, value, size);
+
1156 }
+
1157
+
1158 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1159
+
1160 ret = setxattr(procname, name, value, size, flags);
+
1161 saverr = ret == -1 ? errno : 0;
+
1162
+
1163out:
+
1164 fuse_reply_err(req, saverr);
+
1165}
+
1166
+
1167static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
1168{
+
1169 char procname[64];
+
1170 struct lo_inode *inode = lo_inode(req, ino);
+
1171 ssize_t ret;
+
1172 int saverr;
+
1173
+
1174 saverr = ENOSYS;
+
1175 if (!lo_data(req)->xattr)
+
1176 goto out;
+
1177
+
1178 if (lo_debug(req)) {
+
1179 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
+
1180 ino, name);
+
1181 }
+
1182
+
1183 sprintf(procname, "/proc/self/fd/%i", inode->fd);
+
1184
+
1185 ret = removexattr(procname, name);
+
1186 saverr = ret == -1 ? errno : 0;
+
1187
+
1188out:
+
1189 fuse_reply_err(req, saverr);
+
1190}
+
1191
+
1192#ifdef HAVE_COPY_FILE_RANGE
+
1193static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
+
1194 struct fuse_file_info *fi_in,
+
1195 fuse_ino_t ino_out, off_t off_out,
+
1196 struct fuse_file_info *fi_out, size_t len,
+
1197 int flags)
+
1198{
+
1199 ssize_t res;
+
1200
+
1201 if (lo_debug(req))
+
1202 fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
+
1203 "off=%lu, ino=%" PRIu64 "/fd=%lu, "
+
1204 "off=%lu, size=%zd, flags=0x%x)\n",
+
1205 ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
+
1206 len, flags);
+
1207
+
1208 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
+
1209 flags);
+
1210 if (res < 0)
+
1211 fuse_reply_err(req, errno);
+
1212 else
+
1213 fuse_reply_write(req, res);
+
1214}
+
1215#endif
+
1216
+
1217static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1218 struct fuse_file_info *fi)
+
1219{
+
1220 off_t res;
+
1221
+
1222 (void)ino;
+
1223 res = lseek(fi->fh, off, whence);
+
1224 if (res != -1)
+
1225 fuse_reply_lseek(req, res);
+
1226 else
+
1227 fuse_reply_err(req, errno);
+
1228}
+
1229
+
1230static const struct fuse_lowlevel_ops lo_oper = {
+
1231 .init = lo_init,
+
1232 .destroy = lo_destroy,
+
1233 .lookup = lo_lookup,
+
1234 .mkdir = lo_mkdir,
+
1235 .mknod = lo_mknod,
+
1236 .symlink = lo_symlink,
+
1237 .link = lo_link,
+
1238 .unlink = lo_unlink,
+
1239 .rmdir = lo_rmdir,
+
1240 .rename = lo_rename,
+
1241 .forget = lo_forget,
+
1242 .forget_multi = lo_forget_multi,
+
1243 .getattr = lo_getattr,
+
1244 .setattr = lo_setattr,
+
1245 .readlink = lo_readlink,
+
1246 .opendir = lo_opendir,
+
1247 .readdir = lo_readdir,
+
1248 .readdirplus = lo_readdirplus,
+
1249 .releasedir = lo_releasedir,
+
1250 .fsyncdir = lo_fsyncdir,
+
1251 .create = lo_create,
+
1252 .tmpfile = lo_tmpfile,
+
1253 .open = lo_open,
+
1254 .release = lo_release,
+
1255 .flush = lo_flush,
+
1256 .fsync = lo_fsync,
+
1257 .read = lo_read,
+
1258 .write_buf = lo_write_buf,
+
1259 .statfs = lo_statfs,
+
1260 .fallocate = lo_fallocate,
+
1261 .flock = lo_flock,
+
1262 .getxattr = lo_getxattr,
+
1263 .listxattr = lo_listxattr,
+
1264 .setxattr = lo_setxattr,
+
1265 .removexattr = lo_removexattr,
+
1266#ifdef HAVE_COPY_FILE_RANGE
+
1267 .copy_file_range = lo_copy_file_range,
+
1268#endif
+
1269 .lseek = lo_lseek,
+
1270};
+
1271
+
1272int main(int argc, char *argv[])
+
1273{
+
1274 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
1275 struct fuse_session *se;
+
1276 struct fuse_cmdline_opts opts;
+
1277 struct fuse_loop_config *config;
+
1278 struct lo_data lo = { .debug = 0,
+
1279 .writeback = 0 };
+
1280 int ret = -1;
+
1281
+
1282 /* Don't mask creation mode, kernel already did that */
+
1283 umask(0);
+
1284
+
1285 pthread_mutex_init(&lo.mutex, NULL);
+
1286 lo.root.next = lo.root.prev = &lo.root;
+
1287 lo.root.fd = -1;
+
1288 lo.cache = CACHE_NORMAL;
+
1289
+
1290 if (fuse_parse_cmdline(&args, &opts) != 0)
+
1291 return 1;
+
1292 if (opts.show_help) {
+
1293 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
1296 passthrough_ll_help();
+
1297 ret = 0;
+
1298 goto err_out1;
+
1299 } else if (opts.show_version) {
+
1300 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
1302 ret = 0;
+
1303 goto err_out1;
+
1304 }
+
1305
+
1306 if(opts.mountpoint == NULL) {
+
1307 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
1308 printf(" %s --help\n", argv[0]);
+
1309 ret = 1;
+
1310 goto err_out1;
+
1311 }
+
1312
+
1313 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
+
1314 return 1;
+
1315
+
1316 lo.debug = opts.debug;
+
1317 lo.root.refcount = 2;
+
1318 if (lo.source) {
+
1319 struct stat stat;
+
1320 int res;
+
1321
+
1322 res = lstat(lo.source, &stat);
+
1323 if (res == -1) {
+
1324 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
+
1325 lo.source);
+
1326 exit(1);
+
1327 }
+
1328 if (!S_ISDIR(stat.st_mode)) {
+
1329 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
+
1330 exit(1);
+
1331 }
+
1332
+
1333 } else {
+
1334 lo.source = strdup("/");
+
1335 if(!lo.source) {
+
1336 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
1337 exit(1);
+
1338 }
+
1339 }
+
1340 if (!lo.timeout_set) {
+
1341 switch (lo.cache) {
+
1342 case CACHE_NEVER:
+
1343 lo.timeout = 0.0;
+
1344 break;
+
1345
+
1346 case CACHE_NORMAL:
+
1347 lo.timeout = 1.0;
+
1348 break;
+
1349
+
1350 case CACHE_ALWAYS:
+
1351 lo.timeout = 86400.0;
+
1352 break;
+
1353 }
+
1354 } else if (lo.timeout < 0) {
+
1355 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
+
1356 lo.timeout);
+
1357 exit(1);
+
1358 }
+
1359
+
1360 lo.root.fd = open(lo.source, O_PATH);
+
1361 if (lo.root.fd == -1) {
+
1362 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
+
1363 lo.source);
+
1364 exit(1);
+
1365 }
+
1366
+
1367 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+
1368 if (se == NULL)
+
1369 goto err_out1;
+
1370
+
1371 if (fuse_set_signal_handlers(se) != 0)
+
1372 goto err_out2;
+
1373
+
1374 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
1375 goto err_out3;
+
1376
+
1377 fuse_daemonize(opts.foreground);
+
1378
+
1379 /* Block until ctrl+c or fusermount -u */
+
1380 if (opts.singlethread)
+
1381 ret = fuse_session_loop(se);
+
1382 else {
+
1383 config = fuse_loop_cfg_create();
+
1384 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
1385 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
1386 ret = fuse_session_loop_mt(se, config);
+
1387 fuse_loop_cfg_destroy(config);
+
1388 config = NULL;
+
1389 }
+
1390
+ +
1392err_out3:
+ +
1394err_out2:
+ +
1396err_out1:
+
1397 free(opts.mountpoint);
+
1398 fuse_opt_free_args(&args);
+
1399
+
1400 if (lo.root.fd >= 0)
+
1401 close(lo.root.fd);
+
1402
+
1403 free(lo.source);
+
1404 return ret ? 1 : 0;
+
1405}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+
enum fuse_buf_flags flags
+ +
off_t pos
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
uint32_t no_interrupt
+
uint32_t capable
+
+
double entry_timeout
+
fuse_ino_t ino
+
double attr_timeout
+
struct stat attr
+ + +
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2poll_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2poll_8c.html new file mode 100644 index 0000000..a29d26b --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2poll_8c.html @@ -0,0 +1,379 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/poll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll.c File Reference
+
+
+
#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdbool.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

+

Compile with:

gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+

+Source code

+
/*
+
FUSE fsel: FUSE select example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <string.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
#include <time.h>
+
#include <pthread.h>
+
#include <poll.h>
+
#include <stdbool.h>
+
+
/*
+
* fsel_open_mask is used to limit the number of opens to 1 per file.
+
* This is to use file index (0-F) as fh as poll support requires
+
* unique fh per open file. Lifting this would require proper open
+
* file management.
+
*/
+
static unsigned fsel_open_mask;
+
static const char fsel_hex_map[] = "0123456789ABCDEF";
+
static struct fuse *fsel_fuse; /* needed for poll notification */
+
+
#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
#define FSEL_FILES 16
+
+
static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
static _Atomic bool fsel_stop = false;
+
static pthread_t fsel_producer_thread;
+
+
+
static int fsel_path_index(const char *path)
+
{
+
char ch = path[1];
+
+
if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
return -1;
+
return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
}
+
+
static void fsel_destroy(void *private_data)
+
{
+
(void)private_data;
+
+
fsel_stop = true;
+
+
pthread_join(fsel_producer_thread, NULL);
+
}
+
+
static int fsel_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int idx;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0555;
+
stbuf->st_nlink = 2;
+
return 0;
+
}
+
+
idx = fsel_path_index(path);
+
if (idx < 0)
+
return -ENOENT;
+
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fsel_cnt[idx];
+
return 0;
+
}
+
+
static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
char name[2] = { };
+
int i;
+
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
name[0] = fsel_hex_map[i];
+
filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
}
+
+
return 0;
+
}
+
+
static int fsel_open(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fsel_path_index(path);
+
+
if (idx < 0)
+
return -ENOENT;
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
if (fsel_open_mask & (1 << idx))
+
return -EBUSY;
+
fsel_open_mask |= (1 << idx);
+
+
/*
+
* fsel files are nonseekable somewhat pipe-like files which
+
* gets filled up periodically by producer thread and consumed
+
* on read. Tell FUSE as such.
+
*/
+
fi->fh = idx;
+
fi->direct_io = 1;
+
fi->nonseekable = 1;
+
+
return 0;
+
}
+
+
static int fsel_release(const char *path, struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
+
fsel_open_mask &= ~(1 << idx);
+
return 0;
+
}
+
+
static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
int idx = fi->fh;
+
+
(void) path;
+
(void) offset;
+
+
pthread_mutex_lock(&fsel_mutex);
+
if (fsel_cnt[idx] < size)
+
size = fsel_cnt[idx];
+
printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
fsel_cnt[idx] -= size;
+
pthread_mutex_unlock(&fsel_mutex);
+
+
memset(buf, fsel_hex_map[idx], size);
+
return size;
+
}
+
+
static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
struct fuse_pollhandle *ph, unsigned *reventsp)
+
{
+
static unsigned polled_zero;
+
int idx = fi->fh;
+
+
(void) path;
+
+
/*
+
* Poll notification requires pointer to struct fuse which
+
* can't be obtained when using fuse_main(). As notification
+
* happens only after poll is called, fill it here from
+
* fuse_context.
+
*/
+
if (!fsel_fuse) {
+
struct fuse_context *cxt = fuse_get_context();
+
if (cxt)
+
fsel_fuse = cxt->fuse;
+
}
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
if (ph != NULL) {
+
struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+
if (oldph)
+ +
+
fsel_poll_notify_mask |= (1 << idx);
+
fsel_poll_handle[idx] = ph;
+
}
+
+
if (fsel_cnt[idx]) {
+
*reventsp |= POLLIN;
+
printf("POLL %X cnt=%u polled_zero=%u\n",
+
idx, fsel_cnt[idx], polled_zero);
+
polled_zero = 0;
+
} else
+
polled_zero++;
+
+
pthread_mutex_unlock(&fsel_mutex);
+
return 0;
+
}
+
+
static const struct fuse_operations fsel_oper = {
+
.destroy = fsel_destroy,
+
.getattr = fsel_getattr,
+
.readdir = fsel_readdir,
+
.open = fsel_open,
+
.release = fsel_release,
+
.read = fsel_read,
+
.poll = fsel_poll,
+
};
+
+
static void *fsel_producer(void *data)
+
{
+
const struct timespec interval = { 0, 250000000 };
+
unsigned idx = 0, nr = 1;
+
+
(void) data;
+
+
while (!fsel_stop) {
+
int i, t;
+
+
pthread_mutex_lock(&fsel_mutex);
+
+
/*
+
* This is the main producer loop which is executed
+
* ever 500ms. On each iteration, it fills one byte
+
* to 1, 2 or 4 files and sends poll notification if
+
* requested.
+
*/
+
for (i = 0, t = idx; i < nr;
+
i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
if (fsel_cnt[t] == FSEL_CNT_MAX)
+
continue;
+
+
fsel_cnt[t]++;
+
if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
struct fuse_pollhandle *ph;
+
+
printf("NOTIFY %X\n", t);
+
ph = fsel_poll_handle[t];
+
fuse_notify_poll(ph);
+ +
fsel_poll_notify_mask &= ~(1 << t);
+
fsel_poll_handle[t] = NULL;
+
}
+
}
+
+
idx = (idx + 1) % FSEL_FILES;
+
if (idx == 0)
+
nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
+
pthread_mutex_unlock(&fsel_mutex);
+
+
nanosleep(&interval, NULL);
+
}
+
+
return NULL;
+
}
+
+
int main(int argc, char *argv[])
+
{
+
pthread_attr_t attr;
+
int ret;
+
+
errno = pthread_mutex_init(&fsel_mutex, NULL);
+
if (errno) {
+
perror("pthread_mutex_init");
+
return 1;
+
}
+
+
errno = pthread_attr_init(&attr);
+
if (errno) {
+
perror("pthread_attr_init");
+
return 1;
+
}
+
+
errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
if (errno) {
+
perror("pthread_create");
+
return 1;
+
}
+
+
ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+
return ret;
+
}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+

Definition in file poll.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2poll_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2poll_8c_source.html new file mode 100644 index 0000000..aadf986 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/poll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fsel: FUSE select example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
24#define FUSE_USE_VERSION 31
+
25
+
26#include <fuse.h>
+
27#include <unistd.h>
+
28#include <ctype.h>
+
29#include <string.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33#include <time.h>
+
34#include <pthread.h>
+
35#include <poll.h>
+
36#include <stdbool.h>
+
37
+
38/*
+
39 * fsel_open_mask is used to limit the number of opens to 1 per file.
+
40 * This is to use file index (0-F) as fh as poll support requires
+
41 * unique fh per open file. Lifting this would require proper open
+
42 * file management.
+
43 */
+
44static unsigned fsel_open_mask;
+
45static const char fsel_hex_map[] = "0123456789ABCDEF";
+
46static struct fuse *fsel_fuse; /* needed for poll notification */
+
47
+
48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
+
49#define FSEL_FILES 16
+
50
+
51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
+
52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
+
53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+
54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
+
55static _Atomic bool fsel_stop = false;
+
56static pthread_t fsel_producer_thread;
+
57
+
58
+
59static int fsel_path_index(const char *path)
+
60{
+
61 char ch = path[1];
+
62
+
63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+
64 return -1;
+
65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+
66}
+
67
+
68static void fsel_destroy(void *private_data)
+
69{
+
70 (void)private_data;
+
71
+
72 fsel_stop = true;
+
73
+
74 pthread_join(fsel_producer_thread, NULL);
+
75}
+
76
+
77static int fsel_getattr(const char *path, struct stat *stbuf,
+
78 struct fuse_file_info *fi)
+
79{
+
80 (void) fi;
+
81 int idx;
+
82
+
83 memset(stbuf, 0, sizeof(struct stat));
+
84
+
85 if (strcmp(path, "/") == 0) {
+
86 stbuf->st_mode = S_IFDIR | 0555;
+
87 stbuf->st_nlink = 2;
+
88 return 0;
+
89 }
+
90
+
91 idx = fsel_path_index(path);
+
92 if (idx < 0)
+
93 return -ENOENT;
+
94
+
95 stbuf->st_mode = S_IFREG | 0444;
+
96 stbuf->st_nlink = 1;
+
97 stbuf->st_size = fsel_cnt[idx];
+
98 return 0;
+
99}
+
100
+
101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
102 off_t offset, struct fuse_file_info *fi,
+
103 enum fuse_readdir_flags flags)
+
104{
+
105 char name[2] = { };
+
106 int i;
+
107
+
108 (void) offset;
+
109 (void) fi;
+
110 (void) flags;
+
111
+
112 if (strcmp(path, "/") != 0)
+
113 return -ENOENT;
+
114
+
115 for (i = 0; i < FSEL_FILES; i++) {
+
116 name[0] = fsel_hex_map[i];
+
117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
118 }
+
119
+
120 return 0;
+
121}
+
122
+
123static int fsel_open(const char *path, struct fuse_file_info *fi)
+
124{
+
125 int idx = fsel_path_index(path);
+
126
+
127 if (idx < 0)
+
128 return -ENOENT;
+
129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
130 return -EACCES;
+
131 if (fsel_open_mask & (1 << idx))
+
132 return -EBUSY;
+
133 fsel_open_mask |= (1 << idx);
+
134
+
135 /*
+
136 * fsel files are nonseekable somewhat pipe-like files which
+
137 * gets filled up periodically by producer thread and consumed
+
138 * on read. Tell FUSE as such.
+
139 */
+
140 fi->fh = idx;
+
141 fi->direct_io = 1;
+
142 fi->nonseekable = 1;
+
143
+
144 return 0;
+
145}
+
146
+
147static int fsel_release(const char *path, struct fuse_file_info *fi)
+
148{
+
149 int idx = fi->fh;
+
150
+
151 (void) path;
+
152
+
153 fsel_open_mask &= ~(1 << idx);
+
154 return 0;
+
155}
+
156
+
157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+
158 struct fuse_file_info *fi)
+
159{
+
160 int idx = fi->fh;
+
161
+
162 (void) path;
+
163 (void) offset;
+
164
+
165 pthread_mutex_lock(&fsel_mutex);
+
166 if (fsel_cnt[idx] < size)
+
167 size = fsel_cnt[idx];
+
168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+
169 fsel_cnt[idx] -= size;
+
170 pthread_mutex_unlock(&fsel_mutex);
+
171
+
172 memset(buf, fsel_hex_map[idx], size);
+
173 return size;
+
174}
+
175
+
176static int fsel_poll(const char *path, struct fuse_file_info *fi,
+
177 struct fuse_pollhandle *ph, unsigned *reventsp)
+
178{
+
179 static unsigned polled_zero;
+
180 int idx = fi->fh;
+
181
+
182 (void) path;
+
183
+
184 /*
+
185 * Poll notification requires pointer to struct fuse which
+
186 * can't be obtained when using fuse_main(). As notification
+
187 * happens only after poll is called, fill it here from
+
188 * fuse_context.
+
189 */
+
190 if (!fsel_fuse) {
+
191 struct fuse_context *cxt = fuse_get_context();
+
192 if (cxt)
+
193 fsel_fuse = cxt->fuse;
+
194 }
+
195
+
196 pthread_mutex_lock(&fsel_mutex);
+
197
+
198 if (ph != NULL) {
+
199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
200
+
201 if (oldph)
+ +
203
+
204 fsel_poll_notify_mask |= (1 << idx);
+
205 fsel_poll_handle[idx] = ph;
+
206 }
+
207
+
208 if (fsel_cnt[idx]) {
+
209 *reventsp |= POLLIN;
+
210 printf("POLL %X cnt=%u polled_zero=%u\n",
+
211 idx, fsel_cnt[idx], polled_zero);
+
212 polled_zero = 0;
+
213 } else
+
214 polled_zero++;
+
215
+
216 pthread_mutex_unlock(&fsel_mutex);
+
217 return 0;
+
218}
+
219
+
220static const struct fuse_operations fsel_oper = {
+
221 .destroy = fsel_destroy,
+
222 .getattr = fsel_getattr,
+
223 .readdir = fsel_readdir,
+
224 .open = fsel_open,
+
225 .release = fsel_release,
+
226 .read = fsel_read,
+
227 .poll = fsel_poll,
+
228};
+
229
+
230static void *fsel_producer(void *data)
+
231{
+
232 const struct timespec interval = { 0, 250000000 };
+
233 unsigned idx = 0, nr = 1;
+
234
+
235 (void) data;
+
236
+
237 while (!fsel_stop) {
+
238 int i, t;
+
239
+
240 pthread_mutex_lock(&fsel_mutex);
+
241
+
242 /*
+
243 * This is the main producer loop which is executed
+
244 * ever 500ms. On each iteration, it fills one byte
+
245 * to 1, 2 or 4 files and sends poll notification if
+
246 * requested.
+
247 */
+
248 for (i = 0, t = idx; i < nr;
+
249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+
250 if (fsel_cnt[t] == FSEL_CNT_MAX)
+
251 continue;
+
252
+
253 fsel_cnt[t]++;
+
254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+
255 struct fuse_pollhandle *ph;
+
256
+
257 printf("NOTIFY %X\n", t);
+
258 ph = fsel_poll_handle[t];
+
259 fuse_notify_poll(ph);
+ +
261 fsel_poll_notify_mask &= ~(1 << t);
+
262 fsel_poll_handle[t] = NULL;
+
263 }
+
264 }
+
265
+
266 idx = (idx + 1) % FSEL_FILES;
+
267 if (idx == 0)
+
268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
+
269
+
270 pthread_mutex_unlock(&fsel_mutex);
+
271
+
272 nanosleep(&interval, NULL);
+
273 }
+
274
+
275 return NULL;
+
276}
+
277
+
278int main(int argc, char *argv[])
+
279{
+
280 pthread_attr_t attr;
+
281 int ret;
+
282
+
283 errno = pthread_mutex_init(&fsel_mutex, NULL);
+
284 if (errno) {
+
285 perror("pthread_mutex_init");
+
286 return 1;
+
287 }
+
288
+
289 errno = pthread_attr_init(&attr);
+
290 if (errno) {
+
291 perror("pthread_attr_init");
+
292 return 1;
+
293 }
+
294
+
295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
+
296 if (errno) {
+
297 perror("pthread_create");
+
298 return 1;
+
299 }
+
300
+
301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
302
+
303 return ret;
+
304}
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+ +
struct fuse * fuse
Definition fuse.h:862
+ + +
uint32_t nonseekable
Definition fuse_common.h:86
+
uint32_t direct_io
Definition fuse_common.h:71
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c.html new file mode 100644 index 0000000..a603351 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/poll_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
poll_client.c File Reference
+
+
+
#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the poll.c example file systsem.

+

Compile with:

 gcc -Wall poll_client.c -o poll_client
+

+Source code

+
/*
+
FUSE fselclient: FUSE select example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/select.h>
+
#include <sys/time.h>
+
#include <sys/types.h>
+
#include <sys/stat.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <ctype.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <errno.h>
+
+
#define FSEL_FILES 16
+
+
int main(void)
+
{
+
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
int fds[FSEL_FILES];
+
int i, nfds, tries;
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
char name[] = { hex_map[i], '\0' };
+
fds[i] = open(name, O_RDONLY);
+
if (fds[i] < 0) {
+
perror("open");
+
return 1;
+
}
+
}
+
nfds = fds[FSEL_FILES - 1] + 1;
+
+
for(tries=0; tries < 16; tries++) {
+
static char buf[4096];
+
fd_set rfds;
+
int rc;
+
+
FD_ZERO(&rfds);
+
for (i = 0; i < FSEL_FILES; i++)
+
FD_SET(fds[i], &rfds);
+
+
rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+
if (rc < 0) {
+
perror("select");
+
return 1;
+
}
+
+
for (i = 0; i < FSEL_FILES; i++) {
+
if (!FD_ISSET(fds[i], &rfds)) {
+
printf("_: ");
+
continue;
+
}
+
printf("%X:", i);
+
rc = read(fds[i], buf, sizeof(buf));
+
if (rc < 0) {
+
perror("read");
+
return 1;
+
}
+
printf("%02d ", rc);
+
}
+
printf("\n");
+
}
+
return 0;
+
}
+
+

Definition in file poll_client.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c_source.html new file mode 100644 index 0000000..39cd0d0 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/poll_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
poll_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fselclient: FUSE select example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#include <sys/select.h>
+
24#include <sys/time.h>
+
25#include <sys/types.h>
+
26#include <sys/stat.h>
+
27#include <fcntl.h>
+
28#include <unistd.h>
+
29#include <ctype.h>
+
30#include <stdio.h>
+
31#include <stdlib.h>
+
32#include <errno.h>
+
33
+
34#define FSEL_FILES 16
+
35
+
36int main(void)
+
37{
+
38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+
39 int fds[FSEL_FILES];
+
40 int i, nfds, tries;
+
41
+
42 for (i = 0; i < FSEL_FILES; i++) {
+
43 char name[] = { hex_map[i], '\0' };
+
44 fds[i] = open(name, O_RDONLY);
+
45 if (fds[i] < 0) {
+
46 perror("open");
+
47 return 1;
+
48 }
+
49 }
+
50 nfds = fds[FSEL_FILES - 1] + 1;
+
51
+
52 for(tries=0; tries < 16; tries++) {
+
53 static char buf[4096];
+
54 fd_set rfds;
+
55 int rc;
+
56
+
57 FD_ZERO(&rfds);
+
58 for (i = 0; i < FSEL_FILES; i++)
+
59 FD_SET(fds[i], &rfds);
+
60
+
61 rc = select(nfds, &rfds, NULL, NULL, NULL);
+
62
+
63 if (rc < 0) {
+
64 perror("select");
+
65 return 1;
+
66 }
+
67
+
68 for (i = 0; i < FSEL_FILES; i++) {
+
69 if (!FD_ISSET(fds[i], &rfds)) {
+
70 printf("_: ");
+
71 continue;
+
72 }
+
73 printf("%X:", i);
+
74 rc = read(fds[i], buf, sizeof(buf));
+
75 if (rc < 0) {
+
76 perror("read");
+
77 return 1;
+
78 }
+
79 printf("%02d ", rc);
+
80 }
+
81 printf("\n");
+
82 }
+
83 return 0;
+
84}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2printcap_8c.html b/doc/html/fuse-3_817_81_8dir_2example_2printcap_8c.html new file mode 100644 index 0000000..d2d3eb4 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2printcap_8c.html @@ -0,0 +1,239 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/printcap.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
printcap.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

+

Compile with:

gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <unistd.h>
+
#include <string.h>
+
#include <stdlib.h>
+
+
struct fuse_session *se;
+
+
// Define a structure to hold capability information
+
struct cap_info {
+
uint64_t flag;
+
const char *name;
+
};
+
+
// Define an array of all capabilities
+
static const struct cap_info capabilities[] = {
+
{FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
{FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
{FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
{FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
{FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
{FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
{FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
{FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
{FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
{FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
{FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
{FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
{FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
{FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
{FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
{FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
{FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
{FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
{FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
{FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
{FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
{FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
{FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
{FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
{FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
{FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
{FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
{FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
// Add any new capabilities here
+
{0, NULL} // Sentinel to mark the end of the array
+
};
+
+
static void print_capabilities(struct fuse_conn_info *conn)
+
{
+
printf("Capabilities:\n");
+
for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
if (fuse_get_feature_flag(conn, cap->flag)) {
+
printf("\t%s\n", cap->name);
+
}
+
}
+
}
+
+
static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void) userdata;
+
+
printf("Protocol version: %d.%d\n", conn->proto_major,
+
conn->proto_minor);
+
print_capabilities(conn);
+ +
}
+
+
+
static const struct fuse_lowlevel_ops pc_oper = {
+
.init = pc_init,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
char *mountpoint;
+
int ret = -1;
+
+
mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
if(mkdtemp(mountpoint) == NULL) {
+
perror("mkdtemp");
+
return 1;
+
}
+
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
+
se = fuse_session_new(&args, &pc_oper,
+
sizeof(pc_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, mountpoint) != 0)
+
goto err_out3;
+
+
ret = fuse_session_loop(se);
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
rmdir(mountpoint);
+
free(mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file printcap.c.

+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2example_2printcap_8c_source.html b/doc/html/fuse-3_817_81_8dir_2example_2printcap_8c_source.html new file mode 100644 index 0000000..54c59d9 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2example_2printcap_8c_source.html @@ -0,0 +1,230 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/example/printcap.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
printcap.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse_lowlevel.h>
+
25#include <stdio.h>
+
26#include <unistd.h>
+
27#include <string.h>
+
28#include <stdlib.h>
+
29
+
30struct fuse_session *se;
+
31
+
32// Define a structure to hold capability information
+
33struct cap_info {
+
34 uint64_t flag;
+
35 const char *name;
+
36};
+
37
+
38// Define an array of all capabilities
+
39static const struct cap_info capabilities[] = {
+
40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
+
41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
+
42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
+
43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
+
44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
+
45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
+
46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
+
47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
+
48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
+
49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
+
50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
+
51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
+
52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
+
53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
+
54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
+
55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
+
56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
+
57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
+
58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
+
59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
+
60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
+
61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
+
62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
+
63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
+
64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
+
65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
+
66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
+
67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
+
68 // Add any new capabilities here
+
69 {0, NULL} // Sentinel to mark the end of the array
+
70};
+
71
+
72static void print_capabilities(struct fuse_conn_info *conn)
+
73{
+
74 printf("Capabilities:\n");
+
75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
+
76 if (fuse_get_feature_flag(conn, cap->flag)) {
+
77 printf("\t%s\n", cap->name);
+
78 }
+
79 }
+
80}
+
81
+
82static void pc_init(void *userdata, struct fuse_conn_info *conn)
+
83{
+
84 (void) userdata;
+
85
+
86 printf("Protocol version: %d.%d\n", conn->proto_major,
+
87 conn->proto_minor);
+
88 print_capabilities(conn);
+ +
90}
+
91
+
92
+
93static const struct fuse_lowlevel_ops pc_oper = {
+
94 .init = pc_init,
+
95};
+
96
+
97int main(int argc, char **argv)
+
98{
+
99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
100 char *mountpoint;
+
101 int ret = -1;
+
102
+
103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
+
104 if(mkdtemp(mountpoint) == NULL) {
+
105 perror("mkdtemp");
+
106 return 1;
+
107 }
+
108
+
109 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
111
+
112 se = fuse_session_new(&args, &pc_oper,
+
113 sizeof(pc_oper), NULL);
+
114 if (se == NULL)
+
115 goto err_out1;
+
116
+
117 if (fuse_set_signal_handlers(se) != 0)
+
118 goto err_out2;
+
119
+
120 if (fuse_session_mount(se, mountpoint) != 0)
+
121 goto err_out3;
+
122
+
123 ret = fuse_session_loop(se);
+
124
+ +
126err_out3:
+ +
128err_out2:
+ +
130err_out1:
+
131 rmdir(mountpoint);
+
132 free(mountpoint);
+
133 fuse_opt_free_args(&args);
+
134
+
135 return ret ? 1 : 0;
+
136}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_session_destroy(struct fuse_session *se)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t proto_major
+
uint32_t proto_minor
+ +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2cuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..9f8cb8a --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse_8h.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse_8h.html new file mode 100644 index 0000000..f0148f2 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse_8h.html @@ -0,0 +1,864 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1398 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 87 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1369 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: all file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+
+ +

Definition at line 58 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 42 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4458 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5169 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4664 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4879 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4669 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4545 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4679 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4688 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4698 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4769 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4602 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+

Main function of FUSE.

+

This is for the lazy. This is all that has to be called from the main() function.

+

This function does the following:

    +
  • parses command line options, and handles –help and –version
  • +
  • installs signal handlers for INT, HUP, TERM and PIPE
  • +
  • registers an exit handler to unmount the filesystem on program exit
  • +
  • creates a fuse handle
  • +
  • registers the operations
  • +
  • calls either the single-threaded or the multi-threaded event loop
  • +
+

Most file systems will have to parse some file-system specific arguments before calling this function. It is recommended to do this with fuse_opt_parse() and a processing function that passes through any unknown options (this can also be achieved by just passing NULL as the processing function). That way, the remaining options can be passed directly to fuse_main().

+

fuse_main() accepts all options that can be passed to fuse_parse_cmdline(), fuse_new(), or fuse_session_new().

+

Option parsing skips argv[0], which is assumed to contain the program name. This element must always be present and is used to construct a basic usage: message for the –help output. argv[0] may also be set to the empty string. In this case the usage message is suppressed. This can be used by file systems to print their own usage line first. See hello.c for an example of how to do this.

+

Note: this is currently implemented as a macro.

+

The following error codes may be returned from fuse_main(): 1: Invalid option arguments 2: No mount point specified 3: FUSE setup failed 4: Mounting failed 5: Failed to daemonize (detach from session) 6: Failed to set up signal handlers 7: An error occurred during the life of the file system

+
Parameters
+ + + + + +
argcthe argument counter passed to the main() function
argvthe argument vector passed to the main() function
opthe file system operation
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
0 on success, nonzero on failure
+

Example usage, see hello.c

+ +

Definition at line 307 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5220 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 475 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4929 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4937 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5225 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse_8h_source.html new file mode 100644 index 0000000..ed62178 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse_8h_source.html @@ -0,0 +1,650 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
37struct fuse;
+
38
+
+ + +
52 FUSE_READDIR_PLUS = (1 << 0)
+
53};
+
+
54
+
+ + +
69 FUSE_FILL_DIR_PLUS = (1 << 1)
+
70};
+
+
71
+
87typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
88 const struct stat *stbuf, off_t off,
+
89 enum fuse_fill_dir_flags flags);
+
+ +
106 int32_t set_gid;
+
107 uint32_t gid;
+
108
+
113 int32_t set_uid;
+
114 uint32_t uid;
+
115
+
120 int32_t set_mode;
+
121 uint32_t umask;
+
122
+ +
128
+ +
138
+ +
144
+
148 int32_t intr;
+
149
+
155 int32_t intr_signal;
+
156
+
167 int32_t remember;
+
168
+
185 int32_t hard_remove;
+
186
+
198 int32_t use_ino;
+
199
+
207 int32_t readdir_ino;
+
208
+
226 int32_t direct_io;
+
227
+ +
246
+
253 int32_t auto_cache;
+
254
+
255 /*
+
256 * The timeout in seconds for which file attributes are cached
+
257 * for the purpose of checking if auto_cache should flush the
+
258 * file data on open.
+
259 */
+
260 int32_t ac_attr_timeout_set;
+
261 double ac_attr_timeout;
+
262
+
273 int32_t nullpath_ok;
+
274
+
279 int32_t show_help;
+
280 char *modules;
+
281 int32_t debug;
+
282
+
288 uint32_t fmask;
+
289 uint32_t dmask;
+
290
+ +
298
+ +
313
+
314
+
318 uint32_t flags;
+
319
+
323 uint64_t reserved[48];
+
324};
+
+
325
+
326
+
+ +
361 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
362
+
371 int (*readlink) (const char *, char *, size_t);
+
372
+
379 int (*mknod) (const char *, mode_t, dev_t);
+
380
+
387 int (*mkdir) (const char *, mode_t);
+
388
+
390 int (*unlink) (const char *);
+
391
+
393 int (*rmdir) (const char *);
+
394
+
396 int (*symlink) (const char *, const char *);
+
397
+
407 int (*rename) (const char *, const char *, unsigned int flags);
+
408
+
410 int (*link) (const char *, const char *);
+
411
+
417 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
418
+
427 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
428
+
437 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
438
+
486 int (*open) (const char *, struct fuse_file_info *);
+
487
+
497 int (*read) (const char *, char *, size_t, off_t,
+
498 struct fuse_file_info *);
+
499
+
509 int (*write) (const char *, const char *, size_t, off_t,
+
510 struct fuse_file_info *);
+
511
+
516 int (*statfs) (const char *, struct statvfs *);
+
517
+
546 int (*flush) (const char *, struct fuse_file_info *);
+
547
+
560 int (*release) (const char *, struct fuse_file_info *);
+
561
+
567 int (*fsync) (const char *, int, struct fuse_file_info *);
+
568
+
570 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
571
+
573 int (*getxattr) (const char *, const char *, char *, size_t);
+
574
+
576 int (*listxattr) (const char *, char *, size_t);
+
577
+
579 int (*removexattr) (const char *, const char *);
+
580
+
589 int (*opendir) (const char *, struct fuse_file_info *);
+
590
+
613 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
614 struct fuse_file_info *, enum fuse_readdir_flags);
+
615
+
621 int (*releasedir) (const char *, struct fuse_file_info *);
+
622
+
631 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
632
+
641 void *(*init) (struct fuse_conn_info *conn,
+
642 struct fuse_config *cfg);
+
643
+
649 void (*destroy) (void *private_data);
+
650
+
660 int (*access) (const char *, int);
+
661
+
672 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
673
+
704 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
705 struct flock *);
+
706
+
719 int (*utimens) (const char *, const struct timespec tv[2],
+
720 struct fuse_file_info *fi);
+
721
+
728 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
729
+
730#if FUSE_USE_VERSION < 35
+
731 int (*ioctl) (const char *, int cmd, void *arg,
+
732 struct fuse_file_info *, unsigned int flags, void *data);
+
733#else
+
750 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
751 struct fuse_file_info *, unsigned int flags, void *data);
+
752#endif
+
753
+
769 int (*poll) (const char *, struct fuse_file_info *,
+
770 struct fuse_pollhandle *ph, unsigned *reventsp);
+
771
+
781 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
782 struct fuse_file_info *);
+
783
+
798 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
799 size_t size, off_t off, struct fuse_file_info *);
+
818 int (*flock) (const char *, struct fuse_file_info *, int op);
+
819
+
828 int (*fallocate) (const char *, int, off_t, off_t,
+
829 struct fuse_file_info *);
+
830
+
843 ssize_t (*copy_file_range) (const char *path_in,
+
844 struct fuse_file_info *fi_in,
+
845 off_t offset_in, const char *path_out,
+
846 struct fuse_file_info *fi_out,
+
847 off_t offset_out, size_t size, int flags);
+
848
+
852 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
853};
+
+
854
+
+ +
862 struct fuse *fuse;
+
863
+
865 uid_t uid;
+
866
+
868 gid_t gid;
+
869
+
871 pid_t pid;
+
872
+ +
875
+
877 mode_t umask;
+
878};
+
+
879
+
885int fuse_main_real_versioned(int argc, char *argv[],
+
886 const struct fuse_operations *op, size_t op_size,
+
887 struct libfuse_version *version, void *user_data);
+
888static inline int fuse_main_real(int argc, char *argv[],
+
889 const struct fuse_operations *op,
+
890 size_t op_size, void *user_data)
+
891{
+
892 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
893 .minor = FUSE_MINOR_VERSION,
+
894 .hotfix = FUSE_HOTFIX_VERSION,
+
895 .padding = 0 };
+
896
+
897 fuse_log(FUSE_LOG_ERR,
+
898 "%s is a libfuse internal function, please use fuse_main()\n",
+
899 __func__);
+
900
+
901 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
902 user_data);
+
903}
+
904
+
959int fuse_main_real_versioned(int argc, char *argv[],
+
960 const struct fuse_operations *op, size_t op_size,
+
961 struct libfuse_version *version, void *user_data);
+
962static inline int fuse_main_fn(int argc, char *argv[],
+
963 const struct fuse_operations *op,
+
964 void *user_data)
+
965{
+
966 struct libfuse_version version = {
+
967 .major = FUSE_MAJOR_VERSION,
+
968 .minor = FUSE_MINOR_VERSION,
+
969 .hotfix = FUSE_HOTFIX_VERSION,
+
970 .padding = 0
+
971 };
+
972
+
973 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
974 user_data);
+
975}
+
976#define fuse_main(argc, argv, op, user_data) \
+
977 fuse_main_fn(argc, argv, op, user_data)
+
978
+
979/* ----------------------------------------------------------- *
+
980 * More detailed API *
+
981 * ----------------------------------------------------------- */
+
982
+
994void fuse_lib_help(struct fuse_args *args);
+
995
+
996/* Do not call this directly, use fuse_new() instead */
+
997struct fuse *_fuse_new_30(struct fuse_args *args,
+
998 const struct fuse_operations *op, size_t op_size,
+
999 struct libfuse_version *version, void *user_data);
+
1000struct fuse *_fuse_new_31(struct fuse_args *args,
+
1001 const struct fuse_operations *op, size_t op_size,
+
1002 struct libfuse_version *version, void *user_data);
+
1003
+
1031#if FUSE_USE_VERSION == 30
+
1032static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1033 const struct fuse_operations *op,
+
1034 size_t op_size, void *user_data)
+
1035{
+
1036 struct libfuse_version version = {
+
1037 .major = FUSE_MAJOR_VERSION,
+
1038 .minor = FUSE_MINOR_VERSION,
+
1039 .hotfix = FUSE_HOTFIX_VERSION,
+
1040 .padding = 0
+
1041 };
+
1042
+
1043 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1044}
+
1045#else /* FUSE_USE_VERSION */
+
1046static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1047 const struct fuse_operations *op,
+
1048 size_t op_size, void *user_data)
+
1049{
+
1050 struct libfuse_version version = {
+
1051 .major = FUSE_MAJOR_VERSION,
+
1052 .minor = FUSE_MINOR_VERSION,
+
1053 .hotfix = FUSE_HOTFIX_VERSION,
+
1054 .padding = 0
+
1055 };
+
1056
+
1057 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1058}
+
1059#endif
+
1060#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1061
+
1070int fuse_mount(struct fuse *f, const char *mountpoint);
+
1071
+
1079void fuse_unmount(struct fuse *f);
+
1080
+
1089void fuse_destroy(struct fuse *f);
+
1090
+
1106int fuse_loop(struct fuse *f);
+
1107
+
1116void fuse_exit(struct fuse *f);
+
1117
+
1118#if FUSE_USE_VERSION < 32
+
1119int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1120#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1121#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1122int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1123#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1124#else
+
1156#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1157int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1158#else
+
1159#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1160#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1161#endif
+
1162
+
1163
+
1172struct fuse_context *fuse_get_context(void);
+
1173
+
1192int fuse_getgroups(int size, gid_t list[]);
+
1193
+
1199int fuse_interrupted(void);
+
1200
+
1212int fuse_invalidate_path(struct fuse *f, const char *path);
+
1213
+ +
1222
+
1229void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1230
+
1240int fuse_clean_cache(struct fuse *fuse);
+
1241
+
1242/*
+
1243 * Stacking API
+
1244 */
+
1245
+
1251struct fuse_fs;
+
1252
+
1253/*
+
1254 * These functions call the relevant filesystem operation, and return
+
1255 * the result.
+
1256 *
+
1257 * If the operation is not defined, they return -ENOSYS, with the
+
1258 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1259 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1260 */
+
1261
+
1262int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1263 struct fuse_file_info *fi);
+
1264int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1265 const char *newpath, unsigned int flags);
+
1266int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1267int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1268int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1269 const char *path);
+
1270int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1271int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1272 struct fuse_file_info *fi);
+
1273int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1274 struct fuse_file_info *fi);
+
1275int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1276 off_t off, struct fuse_file_info *fi);
+
1277int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1278 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1279 struct fuse_file_info *fi);
+
1280int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1281 size_t size, off_t off, struct fuse_file_info *fi);
+
1282int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1283 struct fuse_bufvec *buf, off_t off,
+
1284 struct fuse_file_info *fi);
+
1285int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1286 struct fuse_file_info *fi);
+
1287int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1290int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1291 struct fuse_file_info *fi);
+
1292int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1293 fuse_fill_dir_t filler, off_t off,
+
1294 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1295int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1296 struct fuse_file_info *fi);
+
1297int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1298 struct fuse_file_info *fi);
+
1299int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1300 struct fuse_file_info *fi);
+
1301int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1302 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1303int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1304 struct fuse_file_info *fi, int op);
+
1305int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1306 struct fuse_file_info *fi);
+
1307int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1308 struct fuse_file_info *fi);
+
1309int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1310 struct fuse_file_info *fi);
+
1311int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1312 const struct timespec tv[2], struct fuse_file_info *fi);
+
1313int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1314int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1315 size_t len);
+
1316int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1317 dev_t rdev);
+
1318int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1319int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1320 const char *value, size_t size, int flags);
+
1321int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1322 char *value, size_t size);
+
1323int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1324 size_t size);
+
1325int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1326 const char *name);
+
1327int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1328 uint64_t *idx);
+
1329#if FUSE_USE_VERSION < 35
+
1330int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1331 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1332 void *data);
+
1333#else
+
1334int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1335 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1336 void *data);
+
1337#endif
+
1338int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1339 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1340 unsigned *reventsp);
+
1341int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1342 off_t offset, off_t length, struct fuse_file_info *fi);
+
1343ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1344 struct fuse_file_info *fi_in, off_t off_in,
+
1345 const char *path_out,
+
1346 struct fuse_file_info *fi_out, off_t off_out,
+
1347 size_t len, int flags);
+
1348off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1349 struct fuse_file_info *fi);
+
1350void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1351 struct fuse_config *cfg);
+
1352void fuse_fs_destroy(struct fuse_fs *fs);
+
1353
+
1354int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1355
+
1369struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1370 void *private_data);
+
1371
+
1386typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1387 struct fuse_fs *fs[]);
+
+
1398#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1399 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1400
+
1402struct fuse_session *fuse_get_session(struct fuse *f);
+
1403
+
1412int fuse_open_channel(const char *mountpoint, const char *options);
+
1413
+
1414#ifdef __cplusplus
+
1415}
+
1416#endif
+
1417
+
1418#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4679
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
int fuse_interrupted(void)
Definition fuse.c:4688
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4929
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4664
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4458
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:475
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4937
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h.html new file mode 100644 index 0000000..1c23230 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h.html @@ -0,0 +1,1139 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include <stdbool.h>
+#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1 << 0)
 
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
 
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
 
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
 
#define FUSE_CAP_DONT_MASK   (1 << 6)
 
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
 
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
 
#define FUSE_CAP_SPLICE_READ   (1 << 9)
 
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
 
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
 
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
 
#define FUSE_CAP_READDIRPLUS   (1 << 13)
 
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
 
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
 
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
 
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
 
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
 
#define FUSE_CAP_POSIX_ACL   (1 << 19)
 
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
 
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
 
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
 
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
 
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
 
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
 
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
 
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
 
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
 
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
 
#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 675 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 338 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1 << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 187 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 204 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 291 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 428 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 498 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1 << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 224 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 482 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 466 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 216 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 262 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 398 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 415 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 269 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 518 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 362 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 443 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 370 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 510 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1 << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 389 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 195 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1 << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 299 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 327 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 489 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 240 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1 << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 249 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 232 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 347 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 530 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 841 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 810 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 412 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 459 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5234 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1907 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 180 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 158 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 138 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5229 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h_source.html new file mode 100644 index 0000000..8ff963a --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__common_8h_source.html @@ -0,0 +1,518 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file COPYING.LIB.
+
6*/
+
7
+
10#include <stdbool.h>
+
11#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
12#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
13#endif
+
14
+
15#ifndef FUSE_COMMON_H_
+
16#define FUSE_COMMON_H_
+
17
+
18#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
19#include "fuse_config.h"
+
20#endif
+
21
+
22#include "libfuse_config.h"
+
23
+
24#include "fuse_opt.h"
+
25#include "fuse_log.h"
+
26#include <stdint.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#if (defined(__cplusplus) && __cplusplus >= 201103L) || \
+
34 (!defined(__cplusplus) && defined(__STDC_VERSION__) && \
+
35 __STDC_VERSION__ >= 201112L)
+
36#define fuse_static_assert(condition, message) static_assert(condition, message)
+
37#else
+
38#define fuse_static_assert(condition, message)
+
39#endif
+
40
+
41#ifdef __cplusplus
+
42extern "C" {
+
43#endif
+
44
+
+ +
60 int32_t flags;
+
61
+
68 uint32_t writepage : 1;
+
69
+
71 uint32_t direct_io : 1;
+
72
+
77 uint32_t keep_cache : 1;
+
78
+
82 uint32_t flush : 1;
+
83
+
86 uint32_t nonseekable : 1;
+
87
+
88 /* Indicates that flock locks for this file should be
+
89 released. If set, lock_owner shall contain a valid value.
+
90 May only be set in ->release(). */
+
91 uint32_t flock_release : 1;
+
92
+
97 uint32_t cache_readdir : 1;
+
98
+
101 uint32_t noflush : 1;
+
102
+ +
106
+
108 uint32_t padding : 23;
+
109 uint32_t padding2 : 32;
+
110 uint32_t padding3 : 32;
+
111
+
115 uint64_t fh;
+
116
+
118 uint64_t lock_owner;
+
119
+
122 uint32_t poll_events;
+
123
+
127 int32_t backing_id;
+
128
+
130 uint64_t compat_flags;
+
131
+
132 uint64_t reserved[2];
+
133};
+
+
134fuse_static_assert(sizeof(struct fuse_file_info) == 64,
+
135 "fuse_file_info size mismatch");
+
136
+
147#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
148struct fuse_loop_config_v1; /* forward declaration */
+
+ +
150#else
+
151struct fuse_loop_config_v1 {
+
152#endif
+ +
158
+
169 unsigned int max_idle_threads;
+
170};
+
+
171
+
172
+
173/**************************************************************************
+
174 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
175 **************************************************************************/
+
176
+
187#define FUSE_CAP_ASYNC_READ (1 << 0)
+
188
+
195#define FUSE_CAP_POSIX_LOCKS (1 << 1)
+
196
+
204#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
+
205
+
216#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
+
217
+
224#define FUSE_CAP_DONT_MASK (1 << 6)
+
225
+
232#define FUSE_CAP_SPLICE_WRITE (1 << 7)
+
233
+
240#define FUSE_CAP_SPLICE_MOVE (1 << 8)
+
241
+
249#define FUSE_CAP_SPLICE_READ (1 << 9)
+
250
+
262#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
+
263
+
269#define FUSE_CAP_IOCTL_DIR (1 << 11)
+
270
+
291#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12)
+
292
+
299#define FUSE_CAP_READDIRPLUS (1 << 13)
+
300
+
327#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14)
+
328
+
338#define FUSE_CAP_ASYNC_DIO (1 << 15)
+
339
+
347#define FUSE_CAP_WRITEBACK_CACHE (1 << 16)
+
348
+
362#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17)
+
363
+
370#define FUSE_CAP_PARALLEL_DIROPS (1 << 18)
+
371
+
389#define FUSE_CAP_POSIX_ACL (1 << 19)
+
390
+
398#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20)
+
399
+
415#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21)
+
416
+
428#define FUSE_CAP_CACHE_SYMLINKS (1 << 23)
+
429
+
443#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
+
444
+
466#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25)
+
467
+
482#define FUSE_CAP_EXPIRE_ONLY (1 << 26)
+
483
+
489#define FUSE_CAP_SETXATTR_EXT (1 << 27)
+
490
+
498#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1 << 28)
+
499
+
510#define FUSE_CAP_PASSTHROUGH (1 << 29)
+
511
+
518#define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30)
+
519
+
530#define FUSE_IOCTL_COMPAT (1 << 0)
+
531#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
532#define FUSE_IOCTL_RETRY (1 << 2)
+
533#define FUSE_IOCTL_DIR (1 << 4)
+
534
+
535#define FUSE_IOCTL_MAX_IOV 256
+
536
+
+ +
552 uint32_t proto_major;
+
553
+
557 uint32_t proto_minor;
+
558
+
562 uint32_t max_write;
+
563
+
576 uint32_t max_read;
+
577
+ +
582
+
588 uint32_t capable;
+
589
+
600 uint32_t want;
+
601
+ +
631
+ +
641
+
657 uint32_t time_gran;
+
658
+
675#define FUSE_BACKING_STACKED_UNDER (0)
+
676#define FUSE_BACKING_STACKED_OVER (1)
+
677 uint32_t max_backing_stack_depth;
+
678
+
687 uint32_t no_interrupt : 1;
+
688
+
689 /* reserved bits for future use */
+
690 uint32_t padding : 31;
+
691
+
696 uint64_t capable_ext;
+
697
+
706 uint64_t want_ext;
+
707
+
711 uint32_t reserved[16];
+
712};
+
+
713fuse_static_assert(sizeof(struct fuse_conn_info) == 128,
+
714 "Size of struct fuse_conn_info must be 128 bytes");
+
715
+
716struct fuse_session;
+
717struct fuse_pollhandle;
+
718struct fuse_conn_info_opts;
+
719
+
762struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
763
+
771void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
772 struct fuse_conn_info *conn);
+
773
+
780int fuse_daemonize(int foreground);
+
781
+
787int fuse_version(void);
+
788
+
794const char *fuse_pkgversion(void);
+
795
+
801void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
802
+
803/* ----------------------------------------------------------- *
+
804 * Data buffer *
+
805 * ----------------------------------------------------------- */
+
806
+
+ +
817 FUSE_BUF_IS_FD = (1 << 1),
+
818
+ +
827
+
835 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
837
+
+ + +
852
+ +
860
+ +
869
+ + +
+
879
+
+
886struct fuse_buf {
+
890 size_t size;
+
891
+ +
896
+
902 void *mem;
+
903
+
909 int fd;
+
910
+
916 off_t pos;
+
917
+
924 size_t mem_size;
+
925};
+
+
926
+
+ +
939 size_t count;
+
940
+
944 size_t idx;
+
945
+
949 size_t off;
+
950
+
954 struct fuse_buf buf[1];
+
955};
+
+
956
+
+ +
962{
+
963 uint32_t major;
+
964 uint32_t minor;
+
965 uint32_t hotfix;
+
966 uint32_t padding;
+
967};
+
+
968
+
969/* Initialize bufvec with a single buffer of given size */
+
970#define FUSE_BUFVEC_INIT(size__) \
+
971 ((struct fuse_bufvec) { \
+
972 /* .count= */ 1, \
+
973 /* .idx = */ 0, \
+
974 /* .off = */ 0, \
+
975 /* .buf = */ { /* [0] = */ { \
+
976 /* .size = */ (size__), \
+
977 /* .flags = */ (enum fuse_buf_flags) 0, \
+
978 /* .mem = */ NULL, \
+
979 /* .fd = */ -1, \
+
980 /* .pos = */ 0, \
+
981 /* .mem_size = */ 0, \
+
982 } } \
+
983 } )
+
984
+
991size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
992
+
1001ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1002 enum fuse_buf_copy_flags flags);
+
1003
+
1004/* ----------------------------------------------------------- *
+
1005 * Signal handling *
+
1006 * ----------------------------------------------------------- */
+
1007
+
1023int fuse_set_signal_handlers(struct fuse_session *se);
+
1024
+
1040int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1041
+
1053void fuse_remove_signal_handlers(struct fuse_session *se);
+
1054
+
1060#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1066struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1067
+
1071void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1072
+
1076void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1077 unsigned int value);
+
1078
+
1082void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1083 unsigned int value);
+
1084
+
1088void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1089 unsigned int value);
+
1090
+
1097void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1098 struct fuse_loop_config_v1 *v1_conf);
+
1099#endif
+
1100
+
1101
+
1102static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
1103 uint64_t flag)
+
1104{
+
1105 if (conn->capable_ext & flag) {
+
1106 conn->want_ext |= flag;
+
1107 return true;
+
1108 }
+
1109 return false;
+
1110}
+
1111
+
1112static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
1113 uint64_t flag)
+
1114{
+
1115 conn->want_ext &= ~flag;
+
1116}
+
1117
+
1118static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
1119 uint64_t flag)
+
1120{
+
1121 return conn->capable_ext & flag ? true : false;
+
1122}
+
1123
+
1124/* ----------------------------------------------------------- *
+
1125 * Compatibility stuff *
+
1126 * ----------------------------------------------------------- */
+
1127
+
1128#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1129# error only API version 30 or greater is supported
+
1130#endif
+
1131
+
1132#ifdef __cplusplus
+
1133}
+
1134#endif
+
1135
+
1136
+
1137/*
+
1138 * This interface uses 64 bit off_t.
+
1139 *
+
1140 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1141 */
+
1142
+
1143#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1144_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1145#else
+
1146struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1147 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1148#endif
+
1149
+
1150#endif /* FUSE_COMMON_H_ */
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:459
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5229
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
fuse_buf_flags
+ + + + +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
off_t pos
+
void * mem
+
size_t size
+ + + + + +
uint32_t time_gran
+
uint32_t proto_major
+ +
uint32_t congestion_threshold
+
uint32_t proto_minor
+
uint32_t max_write
+
uint64_t capable_ext
+
uint32_t max_readahead
+
uint32_t no_interrupt
+
uint32_t max_read
+
uint32_t max_background
+
uint32_t capable
+
uint64_t want_ext
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t padding
+
uint32_t noflush
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
unsigned int max_idle_threads
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__kernel_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..e30062d --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__kernel_8h_source.html @@ -0,0 +1,1097 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 */
+
221
+
222#ifndef _LINUX_FUSE_H
+
223#define _LINUX_FUSE_H
+
224
+
225#ifdef __KERNEL__
+
226#include <linux/types.h>
+
227#else
+
228#include <stdint.h>
+
229#endif
+
230
+
231/*
+
232 * Version negotiation:
+
233 *
+
234 * Both the kernel and userspace send the version they support in the
+
235 * INIT request and reply respectively.
+
236 *
+
237 * If the major versions match then both shall use the smallest
+
238 * of the two minor versions for communication.
+
239 *
+
240 * If the kernel supports a larger major version, then userspace shall
+
241 * reply with the major version it supports, ignore the rest of the
+
242 * INIT message and expect a new INIT message from the kernel with a
+
243 * matching major version.
+
244 *
+
245 * If the library supports a larger major version, then it shall fall
+
246 * back to the major protocol version sent by the kernel for
+
247 * communication and reply with that major version (and an arbitrary
+
248 * supported minor version).
+
249 */
+
250
+
252#define FUSE_KERNEL_VERSION 7
+
253
+
255#define FUSE_KERNEL_MINOR_VERSION 40
+
256
+
258#define FUSE_ROOT_ID 1
+
259
+
260/* Make sure all structures are padded to 64bit boundary, so 32bit
+
261 userspace works under 64bit kernels */
+
262
+
263struct fuse_attr {
+
264 uint64_t ino;
+
265 uint64_t size;
+
266 uint64_t blocks;
+
267 uint64_t atime;
+
268 uint64_t mtime;
+
269 uint64_t ctime;
+
270 uint32_t atimensec;
+
271 uint32_t mtimensec;
+
272 uint32_t ctimensec;
+
273 uint32_t mode;
+
274 uint32_t nlink;
+
275 uint32_t uid;
+
276 uint32_t gid;
+
277 uint32_t rdev;
+
278 uint32_t blksize;
+
279 uint32_t flags;
+
280};
+
281
+
282/*
+
283 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
284 * Linux.
+
285 */
+
286struct fuse_sx_time {
+
287 int64_t tv_sec;
+
288 uint32_t tv_nsec;
+
289 int32_t __reserved;
+
290};
+
291
+
292struct fuse_statx {
+
293 uint32_t mask;
+
294 uint32_t blksize;
+
295 uint64_t attributes;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint16_t mode;
+
300 uint16_t __spare0[1];
+
301 uint64_t ino;
+
302 uint64_t size;
+
303 uint64_t blocks;
+
304 uint64_t attributes_mask;
+
305 struct fuse_sx_time atime;
+
306 struct fuse_sx_time btime;
+
307 struct fuse_sx_time ctime;
+
308 struct fuse_sx_time mtime;
+
309 uint32_t rdev_major;
+
310 uint32_t rdev_minor;
+
311 uint32_t dev_major;
+
312 uint32_t dev_minor;
+
313 uint64_t __spare2[14];
+
314};
+
315
+
316struct fuse_kstatfs {
+
317 uint64_t blocks;
+
318 uint64_t bfree;
+
319 uint64_t bavail;
+
320 uint64_t files;
+
321 uint64_t ffree;
+
322 uint32_t bsize;
+
323 uint32_t namelen;
+
324 uint32_t frsize;
+
325 uint32_t padding;
+
326 uint32_t spare[6];
+
327};
+
328
+
329struct fuse_file_lock {
+
330 uint64_t start;
+
331 uint64_t end;
+
332 uint32_t type;
+
333 uint32_t pid; /* tgid */
+
334};
+
335
+
339#define FATTR_MODE (1 << 0)
+
340#define FATTR_UID (1 << 1)
+
341#define FATTR_GID (1 << 2)
+
342#define FATTR_SIZE (1 << 3)
+
343#define FATTR_ATIME (1 << 4)
+
344#define FATTR_MTIME (1 << 5)
+
345#define FATTR_FH (1 << 6)
+
346#define FATTR_ATIME_NOW (1 << 7)
+
347#define FATTR_MTIME_NOW (1 << 8)
+
348#define FATTR_LOCKOWNER (1 << 9)
+
349#define FATTR_CTIME (1 << 10)
+
350#define FATTR_KILL_SUIDGID (1 << 11)
+
351
+
364#define FOPEN_DIRECT_IO (1 << 0)
+
365#define FOPEN_KEEP_CACHE (1 << 1)
+
366#define FOPEN_NONSEEKABLE (1 << 2)
+
367#define FOPEN_CACHE_DIR (1 << 3)
+
368#define FOPEN_STREAM (1 << 4)
+
369#define FOPEN_NOFLUSH (1 << 5)
+
370#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
371#define FOPEN_PASSTHROUGH (1 << 7)
+
372
+
425#define FUSE_ASYNC_READ (1 << 0)
+
426#define FUSE_POSIX_LOCKS (1 << 1)
+
427#define FUSE_FILE_OPS (1 << 2)
+
428#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
429#define FUSE_EXPORT_SUPPORT (1 << 4)
+
430#define FUSE_BIG_WRITES (1 << 5)
+
431#define FUSE_DONT_MASK (1 << 6)
+
432#define FUSE_SPLICE_WRITE (1 << 7)
+
433#define FUSE_SPLICE_MOVE (1 << 8)
+
434#define FUSE_SPLICE_READ (1 << 9)
+
435#define FUSE_FLOCK_LOCKS (1 << 10)
+
436#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
437#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
438#define FUSE_DO_READDIRPLUS (1 << 13)
+
439#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
440#define FUSE_ASYNC_DIO (1 << 15)
+
441#define FUSE_WRITEBACK_CACHE (1 << 16)
+
442#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
443#define FUSE_PARALLEL_DIROPS (1 << 18)
+
444#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
445#define FUSE_POSIX_ACL (1 << 20)
+
446#define FUSE_ABORT_ERROR (1 << 21)
+
447#define FUSE_MAX_PAGES (1 << 22)
+
448#define FUSE_CACHE_SYMLINKS (1 << 23)
+
449#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
450#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
451#define FUSE_MAP_ALIGNMENT (1 << 26)
+
452#define FUSE_SUBMOUNTS (1 << 27)
+
453#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
454#define FUSE_SETXATTR_EXT (1 << 29)
+
455#define FUSE_INIT_EXT (1 << 30)
+
456#define FUSE_INIT_RESERVED (1 << 31)
+
457/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
458#define FUSE_SECURITY_CTX (1ULL << 32)
+
459#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
460#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
461#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
462#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
463#define FUSE_PASSTHROUGH (1ULL << 37)
+
464#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
465#define FUSE_HAS_RESEND (1ULL << 39)
+
466
+
467/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
468#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
469
+
475#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
476
+
480#define FUSE_RELEASE_FLUSH (1 << 0)
+
481#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
482
+
486#define FUSE_GETATTR_FH (1 << 0)
+
487
+
491#define FUSE_LK_FLOCK (1 << 0)
+
492
+
500#define FUSE_WRITE_CACHE (1 << 0)
+
501#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
502#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
503
+
504/* Obsolete alias; this flag implies killing suid/sgid only. */
+
505#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
506
+
510#define FUSE_READ_LOCKOWNER (1 << 1)
+
511
+
524#define FUSE_IOCTL_COMPAT (1 << 0)
+
525#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
526#define FUSE_IOCTL_RETRY (1 << 2)
+
527#define FUSE_IOCTL_32BIT (1 << 3)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
530
+
531#define FUSE_IOCTL_MAX_IOV 256
+
532
+
538#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
539
+
545#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
546
+
553#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
554#define FUSE_ATTR_DAX (1 << 1)
+
555
+
560#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
561
+
566#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
567
+
572#define FUSE_EXPIRE_ONLY (1 << 0)
+
573
+
579enum fuse_ext_type {
+
580 /* Types 0..31 are reserved for fuse_secctx_header */
+
581 FUSE_MAX_NR_SECCTX = 31,
+
582 FUSE_EXT_GROUPS = 32,
+
583};
+
584
+
585enum fuse_opcode {
+
586 FUSE_LOOKUP = 1,
+
587 FUSE_FORGET = 2, /* no reply */
+
588 FUSE_GETATTR = 3,
+
589 FUSE_SETATTR = 4,
+
590 FUSE_READLINK = 5,
+
591 FUSE_SYMLINK = 6,
+
592 FUSE_MKNOD = 8,
+
593 FUSE_MKDIR = 9,
+
594 FUSE_UNLINK = 10,
+
595 FUSE_RMDIR = 11,
+
596 FUSE_RENAME = 12,
+
597 FUSE_LINK = 13,
+
598 FUSE_OPEN = 14,
+
599 FUSE_READ = 15,
+
600 FUSE_WRITE = 16,
+
601 FUSE_STATFS = 17,
+
602 FUSE_RELEASE = 18,
+
603 FUSE_FSYNC = 20,
+
604 FUSE_SETXATTR = 21,
+
605 FUSE_GETXATTR = 22,
+
606 FUSE_LISTXATTR = 23,
+
607 FUSE_REMOVEXATTR = 24,
+
608 FUSE_FLUSH = 25,
+
609 FUSE_INIT = 26,
+
610 FUSE_OPENDIR = 27,
+
611 FUSE_READDIR = 28,
+
612 FUSE_RELEASEDIR = 29,
+
613 FUSE_FSYNCDIR = 30,
+
614 FUSE_GETLK = 31,
+
615 FUSE_SETLK = 32,
+
616 FUSE_SETLKW = 33,
+
617 FUSE_ACCESS = 34,
+
618 FUSE_CREATE = 35,
+
619 FUSE_INTERRUPT = 36,
+
620 FUSE_BMAP = 37,
+
621 FUSE_DESTROY = 38,
+
622 FUSE_IOCTL = 39,
+
623 FUSE_POLL = 40,
+
624 FUSE_NOTIFY_REPLY = 41,
+
625 FUSE_BATCH_FORGET = 42,
+
626 FUSE_FALLOCATE = 43,
+
627 FUSE_READDIRPLUS = 44,
+
628 FUSE_RENAME2 = 45,
+
629 FUSE_LSEEK = 46,
+
630 FUSE_COPY_FILE_RANGE = 47,
+
631 FUSE_SETUPMAPPING = 48,
+
632 FUSE_REMOVEMAPPING = 49,
+
633 FUSE_SYNCFS = 50,
+
634 FUSE_TMPFILE = 51,
+
635 FUSE_STATX = 52,
+
636
+
637 /* CUSE specific operations */
+
638 CUSE_INIT = 4096,
+
639
+
640 /* Reserved opcodes: helpful to detect structure endian-ness */
+
641 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
642 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
643};
+
644
+
645enum fuse_notify_code {
+
646 FUSE_NOTIFY_POLL = 1,
+
647 FUSE_NOTIFY_INVAL_INODE = 2,
+
648 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
649 FUSE_NOTIFY_STORE = 4,
+
650 FUSE_NOTIFY_RETRIEVE = 5,
+
651 FUSE_NOTIFY_DELETE = 6,
+
652 FUSE_NOTIFY_RESEND = 7,
+
653 FUSE_NOTIFY_CODE_MAX,
+
654};
+
655
+
656/* The read buffer is required to be at least 8k, but may be much larger */
+
657#define FUSE_MIN_READ_BUFFER 8192
+
658
+
659#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
660
+
661struct fuse_entry_out {
+
662 uint64_t nodeid; /* Inode ID */
+
663 uint64_t generation; /* Inode generation: nodeid:gen must
+
664 be unique for the fs's lifetime */
+
665 uint64_t entry_valid; /* Cache timeout for the name */
+
666 uint64_t attr_valid; /* Cache timeout for the attributes */
+
667 uint32_t entry_valid_nsec;
+
668 uint32_t attr_valid_nsec;
+
669 struct fuse_attr attr;
+
670};
+
671
+
672struct fuse_forget_in {
+
673 uint64_t nlookup;
+
674};
+
675
+
676struct fuse_forget_one {
+
677 uint64_t nodeid;
+
678 uint64_t nlookup;
+
679};
+
680
+
681struct fuse_batch_forget_in {
+
682 uint32_t count;
+
683 uint32_t dummy;
+
684};
+
685
+
686struct fuse_getattr_in {
+
687 uint32_t getattr_flags;
+
688 uint32_t dummy;
+
689 uint64_t fh;
+
690};
+
691
+
692#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
693
+
694struct fuse_attr_out {
+
695 uint64_t attr_valid; /* Cache timeout for the attributes */
+
696 uint32_t attr_valid_nsec;
+
697 uint32_t dummy;
+
698 struct fuse_attr attr;
+
699};
+
700
+
701struct fuse_statx_in {
+
702 uint32_t getattr_flags;
+
703 uint32_t reserved;
+
704 uint64_t fh;
+
705 uint32_t sx_flags;
+
706 uint32_t sx_mask;
+
707};
+
708
+
709struct fuse_statx_out {
+
710 uint64_t attr_valid; /* Cache timeout for the attributes */
+
711 uint32_t attr_valid_nsec;
+
712 uint32_t flags;
+
713 uint64_t spare[2];
+
714 struct fuse_statx stat;
+
715};
+
716
+
717#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
718
+
719struct fuse_mknod_in {
+
720 uint32_t mode;
+
721 uint32_t rdev;
+
722 uint32_t umask;
+
723 uint32_t padding;
+
724};
+
725
+
726struct fuse_mkdir_in {
+
727 uint32_t mode;
+
728 uint32_t umask;
+
729};
+
730
+
731struct fuse_rename_in {
+
732 uint64_t newdir;
+
733};
+
734
+
735struct fuse_rename2_in {
+
736 uint64_t newdir;
+
737 uint32_t flags;
+
738 uint32_t padding;
+
739};
+
740
+
741struct fuse_link_in {
+
742 uint64_t oldnodeid;
+
743};
+
744
+
745struct fuse_setattr_in {
+
746 uint32_t valid;
+
747 uint32_t padding;
+
748 uint64_t fh;
+
749 uint64_t size;
+
750 uint64_t lock_owner;
+
751 uint64_t atime;
+
752 uint64_t mtime;
+
753 uint64_t ctime;
+
754 uint32_t atimensec;
+
755 uint32_t mtimensec;
+
756 uint32_t ctimensec;
+
757 uint32_t mode;
+
758 uint32_t unused4;
+
759 uint32_t uid;
+
760 uint32_t gid;
+
761 uint32_t unused5;
+
762};
+
763
+
764struct fuse_open_in {
+
765 uint32_t flags;
+
766 uint32_t open_flags; /* FUSE_OPEN_... */
+
767};
+
768
+
769struct fuse_create_in {
+
770 uint32_t flags;
+
771 uint32_t mode;
+
772 uint32_t umask;
+
773 uint32_t open_flags; /* FUSE_OPEN_... */
+
774};
+
775
+
776struct fuse_open_out {
+
777 uint64_t fh;
+
778 uint32_t open_flags;
+
779 int32_t backing_id;
+
780};
+
781
+
782struct fuse_release_in {
+
783 uint64_t fh;
+
784 uint32_t flags;
+
785 uint32_t release_flags;
+
786 uint64_t lock_owner;
+
787};
+
788
+
789struct fuse_flush_in {
+
790 uint64_t fh;
+
791 uint32_t unused;
+
792 uint32_t padding;
+
793 uint64_t lock_owner;
+
794};
+
795
+
796struct fuse_read_in {
+
797 uint64_t fh;
+
798 uint64_t offset;
+
799 uint32_t size;
+
800 uint32_t read_flags;
+
801 uint64_t lock_owner;
+
802 uint32_t flags;
+
803 uint32_t padding;
+
804};
+
805
+
806#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
807
+
808struct fuse_write_in {
+
809 uint64_t fh;
+
810 uint64_t offset;
+
811 uint32_t size;
+
812 uint32_t write_flags;
+
813 uint64_t lock_owner;
+
814 uint32_t flags;
+
815 uint32_t padding;
+
816};
+
817
+
818struct fuse_write_out {
+
819 uint32_t size;
+
820 uint32_t padding;
+
821};
+
822
+
823#define FUSE_COMPAT_STATFS_SIZE 48
+
824
+
825struct fuse_statfs_out {
+
826 struct fuse_kstatfs st;
+
827};
+
828
+
829struct fuse_fsync_in {
+
830 uint64_t fh;
+
831 uint32_t fsync_flags;
+
832 uint32_t padding;
+
833};
+
834
+
835#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
836
+
837struct fuse_setxattr_in {
+
838 uint32_t size;
+
839 uint32_t flags;
+
840 uint32_t setxattr_flags;
+
841 uint32_t padding;
+
842};
+
843
+
844struct fuse_getxattr_in {
+
845 uint32_t size;
+
846 uint32_t padding;
+
847};
+
848
+
849struct fuse_getxattr_out {
+
850 uint32_t size;
+
851 uint32_t padding;
+
852};
+
853
+
854struct fuse_lk_in {
+
855 uint64_t fh;
+
856 uint64_t owner;
+
857 struct fuse_file_lock lk;
+
858 uint32_t lk_flags;
+
859 uint32_t padding;
+
860};
+
861
+
862struct fuse_lk_out {
+
863 struct fuse_file_lock lk;
+
864};
+
865
+
866struct fuse_access_in {
+
867 uint32_t mask;
+
868 uint32_t padding;
+
869};
+
870
+
871struct fuse_init_in {
+
872 uint32_t major;
+
873 uint32_t minor;
+
874 uint32_t max_readahead;
+
875 uint32_t flags;
+
876 uint32_t flags2;
+
877 uint32_t unused[11];
+
878};
+
879
+
880#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
881#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
882
+
883struct fuse_init_out {
+
884 uint32_t major;
+
885 uint32_t minor;
+
886 uint32_t max_readahead;
+
887 uint32_t flags;
+
888 uint16_t max_background;
+
889 uint16_t congestion_threshold;
+
890 uint32_t max_write;
+
891 uint32_t time_gran;
+
892 uint16_t max_pages;
+
893 uint16_t map_alignment;
+
894 uint32_t flags2;
+
895 uint32_t max_stack_depth;
+
896 uint32_t unused[6];
+
897};
+
898
+
899#define CUSE_INIT_INFO_MAX 4096
+
900
+
901struct cuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t unused;
+
905 uint32_t flags;
+
906};
+
907
+
908struct cuse_init_out {
+
909 uint32_t major;
+
910 uint32_t minor;
+
911 uint32_t unused;
+
912 uint32_t flags;
+
913 uint32_t max_read;
+
914 uint32_t max_write;
+
915 uint32_t dev_major; /* chardev major */
+
916 uint32_t dev_minor; /* chardev minor */
+
917 uint32_t spare[10];
+
918};
+
919
+
920struct fuse_interrupt_in {
+
921 uint64_t unique;
+
922};
+
923
+
924struct fuse_bmap_in {
+
925 uint64_t block;
+
926 uint32_t blocksize;
+
927 uint32_t padding;
+
928};
+
929
+
930struct fuse_bmap_out {
+
931 uint64_t block;
+
932};
+
933
+
934struct fuse_ioctl_in {
+
935 uint64_t fh;
+
936 uint32_t flags;
+
937 uint32_t cmd;
+
938 uint64_t arg;
+
939 uint32_t in_size;
+
940 uint32_t out_size;
+
941};
+
942
+
943struct fuse_ioctl_iovec {
+
944 uint64_t base;
+
945 uint64_t len;
+
946};
+
947
+
948struct fuse_ioctl_out {
+
949 int32_t result;
+
950 uint32_t flags;
+
951 uint32_t in_iovs;
+
952 uint32_t out_iovs;
+
953};
+
954
+
955struct fuse_poll_in {
+
956 uint64_t fh;
+
957 uint64_t kh;
+
958 uint32_t flags;
+
959 uint32_t events;
+
960};
+
961
+
962struct fuse_poll_out {
+
963 uint32_t revents;
+
964 uint32_t padding;
+
965};
+
966
+
967struct fuse_notify_poll_wakeup_out {
+
968 uint64_t kh;
+
969};
+
970
+
971struct fuse_fallocate_in {
+
972 uint64_t fh;
+
973 uint64_t offset;
+
974 uint64_t length;
+
975 uint32_t mode;
+
976 uint32_t padding;
+
977};
+
978
+
985#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
986
+
987struct fuse_in_header {
+
988 uint32_t len;
+
989 uint32_t opcode;
+
990 uint64_t unique;
+
991 uint64_t nodeid;
+
992 uint32_t uid;
+
993 uint32_t gid;
+
994 uint32_t pid;
+
995 uint16_t total_extlen; /* length of extensions in 8byte units */
+
996 uint16_t padding;
+
997};
+
998
+
999struct fuse_out_header {
+
1000 uint32_t len;
+
1001 int32_t error;
+
1002 uint64_t unique;
+
1003};
+
1004
+
1005struct fuse_dirent {
+
1006 uint64_t ino;
+
1007 uint64_t off;
+
1008 uint32_t namelen;
+
1009 uint32_t type;
+
1010 char name[];
+
1011};
+
1012
+
1013/* Align variable length records to 64bit boundary */
+
1014#define FUSE_REC_ALIGN(x) \
+
1015 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1016
+
1017#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1018#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1019#define FUSE_DIRENT_SIZE(d) \
+
1020 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1021
+
1022struct fuse_direntplus {
+
1023 struct fuse_entry_out entry_out;
+
1024 struct fuse_dirent dirent;
+
1025};
+
1026
+
1027#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1028 offsetof(struct fuse_direntplus, dirent.name)
+
1029#define FUSE_DIRENTPLUS_SIZE(d) \
+
1030 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1031
+
1032struct fuse_notify_inval_inode_out {
+
1033 uint64_t ino;
+
1034 int64_t off;
+
1035 int64_t len;
+
1036};
+
1037
+
1038struct fuse_notify_inval_entry_out {
+
1039 uint64_t parent;
+
1040 uint32_t namelen;
+
1041 uint32_t flags;
+
1042};
+
1043
+
1044struct fuse_notify_delete_out {
+
1045 uint64_t parent;
+
1046 uint64_t child;
+
1047 uint32_t namelen;
+
1048 uint32_t padding;
+
1049};
+
1050
+
1051struct fuse_notify_store_out {
+
1052 uint64_t nodeid;
+
1053 uint64_t offset;
+
1054 uint32_t size;
+
1055 uint32_t padding;
+
1056};
+
1057
+
1058struct fuse_notify_retrieve_out {
+
1059 uint64_t notify_unique;
+
1060 uint64_t nodeid;
+
1061 uint64_t offset;
+
1062 uint32_t size;
+
1063 uint32_t padding;
+
1064};
+
1065
+
1066/* Matches the size of fuse_write_in */
+
1067struct fuse_notify_retrieve_in {
+
1068 uint64_t dummy1;
+
1069 uint64_t offset;
+
1070 uint32_t size;
+
1071 uint32_t dummy2;
+
1072 uint64_t dummy3;
+
1073 uint64_t dummy4;
+
1074};
+
1075
+
1076struct fuse_backing_map {
+
1077 int32_t fd;
+
1078 uint32_t flags;
+
1079 uint64_t padding;
+
1080};
+
1081
+
1082/* Device ioctls: */
+
1083#define FUSE_DEV_IOC_MAGIC 229
+
1084#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1085#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1086 struct fuse_backing_map)
+
1087#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1088
+
1089struct fuse_lseek_in {
+
1090 uint64_t fh;
+
1091 uint64_t offset;
+
1092 uint32_t whence;
+
1093 uint32_t padding;
+
1094};
+
1095
+
1096struct fuse_lseek_out {
+
1097 uint64_t offset;
+
1098};
+
1099
+
1100struct fuse_copy_file_range_in {
+
1101 uint64_t fh_in;
+
1102 uint64_t off_in;
+
1103 uint64_t nodeid_out;
+
1104 uint64_t fh_out;
+
1105 uint64_t off_out;
+
1106 uint64_t len;
+
1107 uint64_t flags;
+
1108};
+
1109
+
1110#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1111#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1112struct fuse_setupmapping_in {
+
1113 /* An already open handle */
+
1114 uint64_t fh;
+
1115 /* Offset into the file to start the mapping */
+
1116 uint64_t foffset;
+
1117 /* Length of mapping required */
+
1118 uint64_t len;
+
1119 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1120 uint64_t flags;
+
1121 /* Offset in Memory Window */
+
1122 uint64_t moffset;
+
1123};
+
1124
+
1125struct fuse_removemapping_in {
+
1126 /* number of fuse_removemapping_one follows */
+
1127 uint32_t count;
+
1128};
+
1129
+
1130struct fuse_removemapping_one {
+
1131 /* Offset into the dax window start the unmapping */
+
1132 uint64_t moffset;
+
1133 /* Length of mapping required */
+
1134 uint64_t len;
+
1135};
+
1136
+
1137#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1138 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1139
+
1140struct fuse_syncfs_in {
+
1141 uint64_t padding;
+
1142};
+
1143
+
1144/*
+
1145 * For each security context, send fuse_secctx with size of security context
+
1146 * fuse_secctx will be followed by security context name and this in turn
+
1147 * will be followed by actual context label.
+
1148 * fuse_secctx, name, context
+
1149 */
+
1150struct fuse_secctx {
+
1151 uint32_t size;
+
1152 uint32_t padding;
+
1153};
+
1154
+
1155/*
+
1156 * Contains the information about how many fuse_secctx structures are being
+
1157 * sent and what's the total size of all security contexts (including
+
1158 * size of fuse_secctx_header).
+
1159 *
+
1160 */
+
1161struct fuse_secctx_header {
+
1162 uint32_t size;
+
1163 uint32_t nr_secctx;
+
1164};
+
1165
+
+ +
1175 uint32_t size;
+
1176 uint32_t type;
+
1177};
+
+
1178
+
+ +
1185 uint32_t nr_groups;
+
1186 uint32_t groups[];
+
1187};
+
+
1188
+
1189#endif /* _LINUX_FUSE_H */
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h.html new file mode 100644 index 0000000..a2aee80 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...)
 
void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 77 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h_source.html new file mode 100644 index 0000000..76df427 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__log_8h_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...);
+
77
+
83void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
84
+
88void fuse_log_close_syslog(void);
+
89
+
90#ifdef __cplusplus
+
91}
+
92#endif
+
93
+
94#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..0d9c083 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h.html @@ -0,0 +1,2363 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1948 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 148 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 290 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 380 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 2941 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2503 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2490 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2484 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2416 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2399 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2609 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2529 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2934 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 484 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 464 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 977 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 528 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 448 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 916 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 432 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 335 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1075 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1096 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1005 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 269 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 960 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1130 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 340 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 509 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1120 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 479 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 938 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 518 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 950 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2659 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3533 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2664 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2677 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2654 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 2951 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3454 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3399 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2771 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3218 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3459 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..35dc9ee --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,683 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
57struct fuse_session;
+
58
+
+ + +
69
+
80 uint64_t generation;
+
81
+
89 struct stat attr;
+
90
+ +
96
+ +
102};
+
+
103
+
+
112struct fuse_ctx {
+
114 uid_t uid;
+
115
+
117 gid_t gid;
+
118
+
120 pid_t pid;
+
121
+
123 mode_t umask;
+
124};
+
+
125
+
126struct fuse_forget_data {
+
127 fuse_ino_t ino;
+
128 uint64_t nlookup;
+
129};
+
130
+
131struct fuse_custom_io {
+
132 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
133 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
134 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
135 off_t *offout, size_t len,
+
136 unsigned int flags, void *userdata);
+
137 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 int (*clone_fd)(int master_fd);
+
141};
+
142
+
+ +
149 FUSE_LL_INVALIDATE = 0,
+
150 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
151};
+
+
152
+
153/* 'to_set' flags in setattr */
+
154#define FUSE_SET_ATTR_MODE (1 << 0)
+
155#define FUSE_SET_ATTR_UID (1 << 1)
+
156#define FUSE_SET_ATTR_GID (1 << 2)
+
157#define FUSE_SET_ATTR_SIZE (1 << 3)
+
158#define FUSE_SET_ATTR_ATIME (1 << 4)
+
159#define FUSE_SET_ATTR_MTIME (1 << 5)
+
160#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
161#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
162#define FUSE_SET_ATTR_FORCE (1 << 9)
+
163#define FUSE_SET_ATTR_CTIME (1 << 10)
+
164#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
165#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
166#define FUSE_SET_ATTR_FILE (1 << 13)
+
167#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
168#define FUSE_SET_ATTR_OPEN (1 << 15)
+
169#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
170#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
171
+
172/* ----------------------------------------------------------- *
+
173 * Request methods and replies *
+
174 * ----------------------------------------------------------- */
+
175
+
+ +
223 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
224
+
236 void (*destroy) (void *userdata);
+
237
+
249 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
250
+
287 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
288
+
308 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
309 struct fuse_file_info *fi);
+
310
+
345 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
346 int to_set, struct fuse_file_info *fi);
+
347
+
358 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
359
+
376 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
377 mode_t mode, dev_t rdev);
+
378
+
391 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
392 mode_t mode);
+
393
+
409 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
410
+
426 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
427
+
440 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
441 const char *name);
+
442
+
472 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
473 fuse_ino_t newparent, const char *newname,
+
474 unsigned int flags);
+
475
+
488 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
489 const char *newname);
+
490
+
554 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
555 struct fuse_file_info *fi);
+
556
+
582 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
583 struct fuse_file_info *fi);
+
584
+
611 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
612 size_t size, off_t off, struct fuse_file_info *fi);
+
613
+
652 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
653 struct fuse_file_info *fi);
+
654
+
680 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
681 struct fuse_file_info *fi);
+
682
+
702 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
703 struct fuse_file_info *fi);
+
704
+
734 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
735 struct fuse_file_info *fi);
+
736
+
780 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
781 struct fuse_file_info *fi);
+
782
+ +
800 struct fuse_file_info *fi);
+
801
+
824 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
825 struct fuse_file_info *fi);
+
826
+
837 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
838
+
850 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
851 const char *value, size_t size, int flags);
+
852
+
881 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
882 size_t size);
+
883
+
912 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
913
+
929 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
930
+
951 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
952
+
980 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
981 mode_t mode, struct fuse_file_info *fi);
+
982
+
995 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
996 struct fuse_file_info *fi, struct flock *lock);
+
997
+
1020 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1021 struct fuse_file_info *fi,
+
1022 struct flock *lock, int sleep);
+
1023
+
1044 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1045 uint64_t idx);
+
1046
+
1047#if FUSE_USE_VERSION < 35
+
1048 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1049 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1050 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1051#else
+
1080 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1081 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1082 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1083#endif
+
1084
+
1117 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1118 struct fuse_pollhandle *ph);
+
1119
+ +
1148 struct fuse_bufvec *bufv, off_t off,
+
1149 struct fuse_file_info *fi);
+
1150
+
1163 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1164 off_t offset, struct fuse_bufvec *bufv);
+
1165
+
1177 void (*forget_multi) (fuse_req_t req, size_t count,
+
1178 struct fuse_forget_data *forgets);
+
1179
+
1195 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1196 struct fuse_file_info *fi, int op);
+
1197
+
1218 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1219 off_t offset, off_t length, struct fuse_file_info *fi);
+
1220
+
1246 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1247 struct fuse_file_info *fi);
+
1248
+ +
1280 off_t off_in, struct fuse_file_info *fi_in,
+
1281 fuse_ino_t ino_out, off_t off_out,
+
1282 struct fuse_file_info *fi_out, size_t len,
+
1283 int flags);
+
1284
+
1303 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1304 struct fuse_file_info *fi);
+
1305
+
1306
+
1325 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1326 mode_t mode, struct fuse_file_info *fi);
+
1327
+
1328};
+
+
1329
+
1351int fuse_reply_err(fuse_req_t req, int err);
+
1352
+
1363void fuse_reply_none(fuse_req_t req);
+
1364
+
1378int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1379
+
1398int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1399 const struct fuse_file_info *fi);
+
1400
+
1412int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1413 double attr_timeout);
+
1414
+
1425int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1426
+
1439int fuse_passthrough_open(fuse_req_t req, int fd);
+
1440int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1441
+
1456int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1457
+
1468int fuse_reply_write(fuse_req_t req, size_t count);
+
1469
+
1481int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1482
+
1526int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1528
+
1540int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1541
+
1552int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1553
+
1564int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1565
+
1576int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1577
+
1588int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1589
+
1590/* ----------------------------------------------------------- *
+
1591 * Filling a buffer in readdir *
+
1592 * ----------------------------------------------------------- */
+
1593
+
1621size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1622 const char *name, const struct stat *stbuf,
+
1623 off_t off);
+
1624
+
1638size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1639 const char *name,
+
1640 const struct fuse_entry_param *e, off_t off);
+
1641
+ +
1658 const struct iovec *in_iov, size_t in_count,
+
1659 const struct iovec *out_iov, size_t out_count);
+
1660
+
1672int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1673
+
1685int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1686 int count);
+
1687
+
1694int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1695
+
1706int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1707
+
1708/* ----------------------------------------------------------- *
+
1709 * Notification *
+
1710 * ----------------------------------------------------------- */
+
1711
+
1719int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1720
+
1744int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1745 off_t off, off_t len);
+
1746
+
1771int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1772 const char *name, size_t namelen);
+
1773
+
1802int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1803 const char *name, size_t namelen);
+
1804
+
1833int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1834 fuse_ino_t parent, fuse_ino_t child,
+
1835 const char *name, size_t namelen);
+
1836
+
1862int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1863 off_t offset, struct fuse_bufvec *bufv,
+ +
1894int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1895 size_t size, off_t offset, void *cookie);
+
1896
+
1897
+
1898/* ----------------------------------------------------------- *
+
1899 * Utility functions *
+
1900 * ----------------------------------------------------------- */
+
1901
+
1908void *fuse_req_userdata(fuse_req_t req);
+
1909
+
1919const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1920
+
1940int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1941
+
1948typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1949
+ +
1962 void *data);
+
1963
+ +
1971
+
1972
+
1973/* ----------------------------------------------------------- *
+
1974 * Inquiry functions *
+
1975 * ----------------------------------------------------------- */
+
1976
+
1980void fuse_lowlevel_version(void);
+
1981
+
1987void fuse_lowlevel_help(void);
+
1988
+
1992void fuse_cmdline_help(void);
+
1993
+
1994/* ----------------------------------------------------------- *
+
1995 * Filesystem setup & teardown *
+
1996 * ----------------------------------------------------------- */
+
1997
+
+ +
2004 int singlethread;
+
2005 int foreground;
+
2006 int debug;
+
2007 int nodefault_subtype;
+
2008 char *mountpoint;
+
2009 int show_version;
+
2010 int show_help;
+
2011 int clone_fd;
+
2012 unsigned int max_idle_threads; /* discouraged, due to thread
+
2013 * destruct overhead */
+
2014
+
2015 /* Added in libfuse-3.12 */
+
2016 unsigned int max_threads;
+
2017};
+
+
2018
+
2037#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2038int fuse_parse_cmdline(struct fuse_args *args,
+
2039 struct fuse_cmdline_opts *opts);
+
2040#else
+
2041#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2042int fuse_parse_cmdline_30(struct fuse_args *args,
+
2043 struct fuse_cmdline_opts *opts);
+
2044#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2045#else
+
2046int fuse_parse_cmdline_312(struct fuse_args *args,
+
2047 struct fuse_cmdline_opts *opts);
+
2048#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2049#endif
+
2050#endif
+
2051
+
2052/* Do not call this directly, use fuse_session_new() instead */
+
2053struct fuse_session *
+
2054fuse_session_new_versioned(struct fuse_args *args,
+
2055 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2056 struct libfuse_version *version, void *userdata);
+
2057
+
2086static inline struct fuse_session *
+
2087fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2088 size_t op_size, void *userdata)
+
2089{
+
2090 struct libfuse_version version = {
+
2091 .major = FUSE_MAJOR_VERSION,
+
2092 .minor = FUSE_MINOR_VERSION,
+
2093 .hotfix = FUSE_HOTFIX_VERSION,
+
2094 .padding = 0
+
2095 };
+
2096
+
2097 return fuse_session_new_versioned(args, op, op_size, &version,
+
2098 userdata);
+
2099}
+
2100#define fuse_session_new(args, op, op_size, userdata) \
+
2101 fuse_session_new_fn(args, op, op_size, userdata)
+
2102
+
2103/*
+
2104 * This should mostly not be called directly, but instead the
+
2105 * fuse_session_custom_io() should be used.
+
2106 */
+
2107int fuse_session_custom_io_317(struct fuse_session *se,
+
2108 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2109
+
2137#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2138static inline int fuse_session_custom_io(struct fuse_session *se,
+
2139 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2140{
+
2141 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2142}
+
2143#else
+
2144static inline int fuse_session_custom_io(struct fuse_session *se,
+
2145 const struct fuse_custom_io *io, int fd)
+
2146{
+
2147 return fuse_session_custom_io_317(se, io,
+
2148 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2149}
+
2150#endif
+
2151
+
2160int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2161
+
2184int fuse_session_loop(struct fuse_session *se);
+
2185
+
2186#if FUSE_USE_VERSION < 32
+
2187 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2188 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2189#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2190 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2191 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2192#else
+
2193 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2205 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2206 #else
+
2207 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2208 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2209 #endif
+
2210#endif
+
2211
+
2224void fuse_session_exit(struct fuse_session *se);
+
2225
+
2231void fuse_session_reset(struct fuse_session *se);
+
2232
+
2239int fuse_session_exited(struct fuse_session *se);
+
2240
+
2265void fuse_session_unmount(struct fuse_session *se);
+
2266
+
2272void fuse_session_destroy(struct fuse_session *se);
+
2273
+
2274/* ----------------------------------------------------------- *
+
2275 * Custom event loop support *
+
2276 * ----------------------------------------------------------- */
+
2277
+
2292int fuse_session_fd(struct fuse_session *se);
+
2293
+
2302void fuse_session_process_buf(struct fuse_session *se,
+
2303 const struct fuse_buf *buf);
+
2304
+
2316int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2317
+
2318#ifdef __cplusplus
+
2319}
+
2320#endif
+
2321
+
2322#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ +
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__mount__compat_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..c4ff766 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h.html new file mode 100644 index 0000000..86ed2db --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h_source.html b/doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..78525a6 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2include_2fuse__opt_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+ +
118};
+
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2buffer_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2buffer_8c_source.html new file mode 100644 index 0000000..38adaa9 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2buffer_8c_source.html @@ -0,0 +1,410 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2compat_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2compat_8c_source.html new file mode 100644 index 0000000..d6901e1 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2cuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..b1121b8 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,451 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
196{
+
197 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
198 struct cuse_init_out outarg;
+
199 struct fuse_session *se = req->se;
+
200 struct cuse_data *cd = se->cuse_data;
+
201 size_t bufsize = se->bufsize;
+
202 struct cuse_lowlevel_ops *clop = req_clop(req);
+
203
+
204 (void) nodeid;
+
205 if (se->debug) {
+
206 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
207 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
208 }
+
209 se->conn.proto_major = arg->major;
+
210 se->conn.proto_minor = arg->minor;
+
211
+
212 /* XXX This is not right.*/
+
213 se->conn.capable_ext = 0;
+
214 se->conn.want_ext = 0;
+
215
+
216 if (arg->major < 7) {
+
217 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
218 arg->major, arg->minor);
+
219 fuse_reply_err(req, EPROTO);
+
220 return;
+
221 }
+
222
+
223 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
224 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
225 bufsize);
+
226 bufsize = FUSE_MIN_READ_BUFFER;
+
227 }
+
228
+
229 bufsize -= 4096;
+
230 if (bufsize < se->conn.max_write)
+
231 se->conn.max_write = bufsize;
+
232
+
233 se->got_init = 1;
+
234 if (se->op.init)
+
235 se->op.init(se->userdata, &se->conn);
+
236
+
237 memset(&outarg, 0, sizeof(outarg));
+
238 outarg.major = FUSE_KERNEL_VERSION;
+
239 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
240 outarg.flags = cd->flags;
+
241 outarg.max_read = cd->max_read;
+
242 outarg.max_write = se->conn.max_write;
+
243 outarg.dev_major = cd->dev_major;
+
244 outarg.dev_minor = cd->dev_minor;
+
245
+
246 if (se->debug) {
+
247 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
248 outarg.major, outarg.minor);
+
249 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
250 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
251 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
252 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
253 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
255 cd->dev_info);
+
256 }
+
257
+
258 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
259
+
260 if (clop->init_done)
+
261 clop->init_done(se->userdata);
+
262
+
263 fuse_free_req(req);
+
264}
+
265
+
266struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
267 const struct cuse_info *ci,
+
268 const struct cuse_lowlevel_ops *clop,
+
269 int *multithreaded, void *userdata)
+
270{
+
271 const char *devname = "/dev/cuse";
+
272 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
275 };
+
276 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
277 struct fuse_session *se;
+
278 struct fuse_cmdline_opts opts;
+
279 int fd;
+
280 int res;
+
281
+
282 if (fuse_parse_cmdline(&args, &opts) == -1)
+
283 return NULL;
+
284 *multithreaded = !opts.singlethread;
+
285
+
286 /* Remove subtype= option */
+
287 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
288 if (res == -1)
+
289 goto out1;
+
290
+
291 /*
+
292 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
293 * would ensue.
+
294 */
+
295 do {
+
296 fd = open("/dev/null", O_RDWR);
+
297 if (fd > 2)
+
298 close(fd);
+
299 } while (fd >= 0 && fd <= 2);
+
300
+
301 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
302 if (se == NULL)
+
303 goto out1;
+
304
+
305 fd = open(devname, O_RDWR);
+
306 if (fd == -1) {
+
307 if (errno == ENODEV || errno == ENOENT)
+
308 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
309 else
+
310 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
311 devname, strerror(errno));
+
312 goto err_se;
+
313 }
+
314 se->fd = fd;
+
315
+
316 res = fuse_set_signal_handlers(se);
+
317 if (res == -1)
+
318 goto err_se;
+
319
+
320 res = fuse_daemonize(opts.foreground);
+
321 if (res == -1)
+
322 goto err_sig;
+
323
+
324 fuse_opt_free_args(&args);
+
325 return se;
+
326
+
327err_sig:
+ +
329err_se:
+ +
331out1:
+
332 free(opts.mountpoint);
+
333 fuse_opt_free_args(&args);
+
334 return NULL;
+
335}
+
336
+
337void cuse_lowlevel_teardown(struct fuse_session *se)
+
338{
+ + +
341}
+
342
+
343int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
344 const struct cuse_lowlevel_ops *clop, void *userdata)
+
345{
+
346 struct fuse_session *se;
+
347 int multithreaded;
+
348 int res;
+
349
+
350 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
351 userdata);
+
352 if (se == NULL)
+
353 return 1;
+
354
+
355 if (multithreaded) {
+
356 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
357 res = fuse_session_loop_mt(se, config);
+
358 fuse_loop_cfg_destroy(config);
+
359 }
+
360 else
+
361 res = fuse_session_loop(se);
+
362
+
363 cuse_lowlevel_teardown(se);
+
364 if (res == -1)
+
365 return 1;
+
366
+
367 return 0;
+
368}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse_8c_source.html new file mode 100644 index 0000000..2504b1b --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse_8c_source.html @@ -0,0 +1,5456 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define container_of(ptr, type, member) ({ \
+
96 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
97 (type *)( (char *)__mptr - offsetof(type,member) );})
+
98
+
99#define list_entry(ptr, type, member) \
+
100 container_of(ptr, type, member)
+
101
+
102struct list_head {
+
103 struct list_head *next;
+
104 struct list_head *prev;
+
105};
+
106
+
107struct node_slab {
+
108 struct list_head list; /* must be the first member */
+
109 struct list_head freelist;
+
110 int used;
+
111};
+
112
+
113struct fuse {
+
114 struct fuse_session *se;
+
115 struct node_table name_table;
+
116 struct node_table id_table;
+
117 struct list_head lru_table;
+
118 fuse_ino_t ctr;
+
119 unsigned int generation;
+
120 unsigned int hidectr;
+
121 pthread_mutex_t lock;
+
122 struct fuse_config conf;
+
123 int intr_installed;
+
124 struct fuse_fs *fs;
+
125 struct lock_queue_element *lockq;
+
126 int pagesize;
+
127 struct list_head partial_slabs;
+
128 struct list_head full_slabs;
+
129 pthread_t prune_thread;
+
130};
+
131
+
132struct lock {
+
133 int type;
+
134 off_t start;
+
135 off_t end;
+
136 pid_t pid;
+
137 uint64_t owner;
+
138 struct lock *next;
+
139};
+
140
+
141struct node {
+
142 struct node *name_next;
+
143 struct node *id_next;
+
144 fuse_ino_t nodeid;
+
145 unsigned int generation;
+
146 int refctr;
+
147 struct node *parent;
+
148 char *name;
+
149 uint64_t nlookup;
+
150 int open_count;
+
151 struct timespec stat_updated;
+
152 struct timespec mtime;
+
153 off_t size;
+
154 struct lock *locks;
+
155 unsigned int is_hidden : 1;
+
156 unsigned int cache_valid : 1;
+
157 int treelock;
+
158 char inline_name[32];
+
159};
+
160
+
161#define TREELOCK_WRITE -1
+
162#define TREELOCK_WAIT_OFFSET INT_MIN
+
163
+
164struct node_lru {
+
165 struct node node;
+
166 struct list_head lru;
+
167 struct timespec forget_time;
+
168};
+
169
+
170struct fuse_direntry {
+
171 struct stat stat;
+
172 enum fuse_fill_dir_flags flags;
+
173 char *name;
+
174 struct fuse_direntry *next;
+
175};
+
176
+
177struct fuse_dh {
+
178 pthread_mutex_t lock;
+
179 struct fuse *fuse;
+
180 fuse_req_t req;
+
181 char *contents;
+
182 struct fuse_direntry *first;
+
183 struct fuse_direntry **last;
+
184 unsigned len;
+
185 unsigned size;
+
186 unsigned needlen;
+
187 int filled;
+
188 uint64_t fh;
+
189 int error;
+
190 fuse_ino_t nodeid;
+
191};
+
192
+
193struct fuse_context_i {
+
194 struct fuse_context ctx;
+
195 fuse_req_t req;
+
196};
+
197
+
198/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
199extern fuse_module_factory_t fuse_module_subdir_factory;
+
200#ifdef HAVE_ICONV
+
201extern fuse_module_factory_t fuse_module_iconv_factory;
+
202#endif
+
203
+
204static pthread_key_t fuse_context_key;
+
205static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
206static int fuse_context_ref;
+
207static struct fuse_module *fuse_modules = NULL;
+
208
+
209static int fuse_register_module(const char *name,
+
210 fuse_module_factory_t factory,
+
211 struct fusemod_so *so)
+
212{
+
213 struct fuse_module *mod;
+
214
+
215 mod = calloc(1, sizeof(struct fuse_module));
+
216 if (!mod) {
+
217 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
218 return -1;
+
219 }
+
220 mod->name = strdup(name);
+
221 if (!mod->name) {
+
222 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
223 free(mod);
+
224 return -1;
+
225 }
+
226 mod->factory = factory;
+
227 mod->ctr = 0;
+
228 mod->so = so;
+
229 if (mod->so)
+
230 mod->so->ctr++;
+
231 mod->next = fuse_modules;
+
232 fuse_modules = mod;
+
233
+
234 return 0;
+
235}
+
236
+
237static void fuse_unregister_module(struct fuse_module *m)
+
238{
+
239 struct fuse_module **mp;
+
240 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
241 if (*mp == m) {
+
242 *mp = (*mp)->next;
+
243 break;
+
244 }
+
245 }
+
246 free(m->name);
+
247 free(m);
+
248}
+
249
+
250static int fuse_load_so_module(const char *module)
+
251{
+
252 int ret = -1;
+
253 char *tmp;
+
254 struct fusemod_so *so;
+
255 fuse_module_factory_t *factory;
+
256
+
257 tmp = malloc(strlen(module) + 64);
+
258 if (!tmp) {
+
259 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
260 return -1;
+
261 }
+
262 sprintf(tmp, "libfusemod_%s.so", module);
+
263 so = calloc(1, sizeof(struct fusemod_so));
+
264 if (!so) {
+
265 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
266 goto out;
+
267 }
+
268
+
269 so->handle = dlopen(tmp, RTLD_NOW);
+
270 if (so->handle == NULL) {
+
271 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
272 tmp, dlerror());
+
273 goto out_free_so;
+
274 }
+
275
+
276 sprintf(tmp, "fuse_module_%s_factory", module);
+
277 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
278 if (factory == NULL) {
+
279 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
280 tmp, dlerror());
+
281 goto out_dlclose;
+
282 }
+
283 ret = fuse_register_module(module, *factory, so);
+
284 if (ret)
+
285 goto out_dlclose;
+
286
+
287out:
+
288 free(tmp);
+
289 return ret;
+
290
+
291out_dlclose:
+
292 dlclose(so->handle);
+
293out_free_so:
+
294 free(so);
+
295 goto out;
+
296}
+
297
+
298static struct fuse_module *fuse_find_module(const char *module)
+
299{
+
300 struct fuse_module *m;
+
301 for (m = fuse_modules; m; m = m->next) {
+
302 if (strcmp(module, m->name) == 0) {
+
303 m->ctr++;
+
304 break;
+
305 }
+
306 }
+
307 return m;
+
308}
+
309
+
310static struct fuse_module *fuse_get_module(const char *module)
+
311{
+
312 struct fuse_module *m;
+
313
+
314 pthread_mutex_lock(&fuse_context_lock);
+
315 m = fuse_find_module(module);
+
316 if (!m) {
+
317 int err = fuse_load_so_module(module);
+
318 if (!err)
+
319 m = fuse_find_module(module);
+
320 }
+
321 pthread_mutex_unlock(&fuse_context_lock);
+
322 return m;
+
323}
+
324
+
325static void fuse_put_module(struct fuse_module *m)
+
326{
+
327 pthread_mutex_lock(&fuse_context_lock);
+
328 if (m->so)
+
329 assert(m->ctr > 0);
+
330 /* Builtin modules may already have m->ctr == 0 */
+
331 if (m->ctr > 0)
+
332 m->ctr--;
+
333 if (!m->ctr && m->so) {
+
334 struct fusemod_so *so = m->so;
+
335 assert(so->ctr > 0);
+
336 so->ctr--;
+
337 if (!so->ctr) {
+
338 struct fuse_module **mp;
+
339 for (mp = &fuse_modules; *mp;) {
+
340 if ((*mp)->so == so)
+
341 fuse_unregister_module(*mp);
+
342 else
+
343 mp = &(*mp)->next;
+
344 }
+
345 dlclose(so->handle);
+
346 free(so);
+
347 }
+
348 } else if (!m->ctr) {
+
349 fuse_unregister_module(m);
+
350 }
+
351 pthread_mutex_unlock(&fuse_context_lock);
+
352}
+
353
+
354static void init_list_head(struct list_head *list)
+
355{
+
356 list->next = list;
+
357 list->prev = list;
+
358}
+
359
+
360static int list_empty(const struct list_head *head)
+
361{
+
362 return head->next == head;
+
363}
+
364
+
365static void list_add(struct list_head *new, struct list_head *prev,
+
366 struct list_head *next)
+
367{
+
368 next->prev = new;
+
369 new->next = next;
+
370 new->prev = prev;
+
371 prev->next = new;
+
372}
+
373
+
374static inline void list_add_head(struct list_head *new, struct list_head *head)
+
375{
+
376 list_add(new, head, head->next);
+
377}
+
378
+
379static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
380{
+
381 list_add(new, head->prev, head);
+
382}
+
383
+
384static inline void list_del(struct list_head *entry)
+
385{
+
386 struct list_head *prev = entry->prev;
+
387 struct list_head *next = entry->next;
+
388
+
389 next->prev = prev;
+
390 prev->next = next;
+
391}
+
392
+
393static inline int lru_enabled(struct fuse *f)
+
394{
+
395 return f->conf.remember > 0;
+
396}
+
397
+
398static struct node_lru *node_lru(struct node *node)
+
399{
+
400 return (struct node_lru *) node;
+
401}
+
402
+
403static size_t get_node_size(struct fuse *f)
+
404{
+
405 if (lru_enabled(f))
+
406 return sizeof(struct node_lru);
+
407 else
+
408 return sizeof(struct node);
+
409}
+
410
+
411#ifdef FUSE_NODE_SLAB
+
412static struct node_slab *list_to_slab(struct list_head *head)
+
413{
+
414 return (struct node_slab *) head;
+
415}
+
416
+
417static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
418{
+
419 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
420}
+
421
+
422static int alloc_slab(struct fuse *f)
+
423{
+
424 void *mem;
+
425 struct node_slab *slab;
+
426 char *start;
+
427 size_t num;
+
428 size_t i;
+
429 size_t node_size = get_node_size(f);
+
430
+
431 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
432 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
433
+
434 if (mem == MAP_FAILED)
+
435 return -1;
+
436
+
437 slab = mem;
+
438 init_list_head(&slab->freelist);
+
439 slab->used = 0;
+
440 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
441
+
442 start = (char *) mem + f->pagesize - num * node_size;
+
443 for (i = 0; i < num; i++) {
+
444 struct list_head *n;
+
445
+
446 n = (struct list_head *) (start + i * node_size);
+
447 list_add_tail(n, &slab->freelist);
+
448 }
+
449 list_add_tail(&slab->list, &f->partial_slabs);
+
450
+
451 return 0;
+
452}
+
453
+
454static struct node *alloc_node(struct fuse *f)
+
455{
+
456 struct node_slab *slab;
+
457 struct list_head *node;
+
458
+
459 if (list_empty(&f->partial_slabs)) {
+
460 int res = alloc_slab(f);
+
461 if (res != 0)
+
462 return NULL;
+
463 }
+
464 slab = list_to_slab(f->partial_slabs.next);
+
465 slab->used++;
+
466 node = slab->freelist.next;
+
467 list_del(node);
+
468 if (list_empty(&slab->freelist)) {
+
469 list_del(&slab->list);
+
470 list_add_tail(&slab->list, &f->full_slabs);
+
471 }
+
472 memset(node, 0, sizeof(struct node));
+
473
+
474 return (struct node *) node;
+
475}
+
476
+
477static void free_slab(struct fuse *f, struct node_slab *slab)
+
478{
+
479 int res;
+
480
+
481 list_del(&slab->list);
+
482 res = munmap(slab, f->pagesize);
+
483 if (res == -1)
+
484 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
485 slab);
+
486}
+
487
+
488static void free_node_mem(struct fuse *f, struct node *node)
+
489{
+
490 struct node_slab *slab = node_to_slab(f, node);
+
491 struct list_head *n = (struct list_head *) node;
+
492
+
493 slab->used--;
+
494 if (slab->used) {
+
495 if (list_empty(&slab->freelist)) {
+
496 list_del(&slab->list);
+
497 list_add_tail(&slab->list, &f->partial_slabs);
+
498 }
+
499 list_add_head(n, &slab->freelist);
+
500 } else {
+
501 free_slab(f, slab);
+
502 }
+
503}
+
504#else
+
505static struct node *alloc_node(struct fuse *f)
+
506{
+
507 return (struct node *) calloc(1, get_node_size(f));
+
508}
+
509
+
510static void free_node_mem(struct fuse *f, struct node *node)
+
511{
+
512 (void) f;
+
513 free(node);
+
514}
+
515#endif
+
516
+
517static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
518{
+
519 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
520 uint64_t oldhash = hash % (f->id_table.size / 2);
+
521
+
522 if (oldhash >= f->id_table.split)
+
523 return oldhash;
+
524 else
+
525 return hash;
+
526}
+
527
+
528static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
529{
+
530 size_t hash = id_hash(f, nodeid);
+
531 struct node *node;
+
532
+
533 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
534 if (node->nodeid == nodeid)
+
535 return node;
+
536
+
537 return NULL;
+
538}
+
539
+
540static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
541{
+
542 struct node *node = get_node_nocheck(f, nodeid);
+
543 if (!node) {
+
544 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
545 (unsigned long long) nodeid);
+
546 abort();
+
547 }
+
548 return node;
+
549}
+
550
+
551static void curr_time(struct timespec *now);
+
552static double diff_timespec(const struct timespec *t1,
+
553 const struct timespec *t2);
+
554
+
555static void remove_node_lru(struct node *node)
+
556{
+
557 struct node_lru *lnode = node_lru(node);
+
558 list_del(&lnode->lru);
+
559 init_list_head(&lnode->lru);
+
560}
+
561
+
562static void set_forget_time(struct fuse *f, struct node *node)
+
563{
+
564 struct node_lru *lnode = node_lru(node);
+
565
+
566 list_del(&lnode->lru);
+
567 list_add_tail(&lnode->lru, &f->lru_table);
+
568 curr_time(&lnode->forget_time);
+
569}
+
570
+
571static void free_node(struct fuse *f, struct node *node)
+
572{
+
573 if (node->name != node->inline_name)
+
574 free(node->name);
+
575 free_node_mem(f, node);
+
576}
+
577
+
578static void node_table_reduce(struct node_table *t)
+
579{
+
580 size_t newsize = t->size / 2;
+
581 void *newarray;
+
582
+
583 if (newsize < NODE_TABLE_MIN_SIZE)
+
584 return;
+
585
+
586 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
587 if (newarray != NULL)
+
588 t->array = newarray;
+
589
+
590 t->size = newsize;
+
591 t->split = t->size / 2;
+
592}
+
593
+
594static void remerge_id(struct fuse *f)
+
595{
+
596 struct node_table *t = &f->id_table;
+
597 int iter;
+
598
+
599 if (t->split == 0)
+
600 node_table_reduce(t);
+
601
+
602 for (iter = 8; t->split > 0 && iter; iter--) {
+
603 struct node **upper;
+
604
+
605 t->split--;
+
606 upper = &t->array[t->split + t->size / 2];
+
607 if (*upper) {
+
608 struct node **nodep;
+
609
+
610 for (nodep = &t->array[t->split]; *nodep;
+
611 nodep = &(*nodep)->id_next);
+
612
+
613 *nodep = *upper;
+
614 *upper = NULL;
+
615 break;
+
616 }
+
617 }
+
618}
+
619
+
620static void unhash_id(struct fuse *f, struct node *node)
+
621{
+
622 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
623
+
624 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
625 if (*nodep == node) {
+
626 *nodep = node->id_next;
+
627 f->id_table.use--;
+
628
+
629 if(f->id_table.use < f->id_table.size / 4)
+
630 remerge_id(f);
+
631 return;
+
632 }
+
633}
+
634
+
635static int node_table_resize(struct node_table *t)
+
636{
+
637 size_t newsize = t->size * 2;
+
638 void *newarray;
+
639
+
640 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
641 if (newarray == NULL)
+
642 return -1;
+
643
+
644 t->array = newarray;
+
645 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
646 t->size = newsize;
+
647 t->split = 0;
+
648
+
649 return 0;
+
650}
+
651
+
652static void rehash_id(struct fuse *f)
+
653{
+
654 struct node_table *t = &f->id_table;
+
655 struct node **nodep;
+
656 struct node **next;
+
657 size_t hash;
+
658
+
659 if (t->split == t->size / 2)
+
660 return;
+
661
+
662 hash = t->split;
+
663 t->split++;
+
664 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
665 struct node *node = *nodep;
+
666 size_t newhash = id_hash(f, node->nodeid);
+
667
+
668 if (newhash != hash) {
+
669 next = nodep;
+
670 *nodep = node->id_next;
+
671 node->id_next = t->array[newhash];
+
672 t->array[newhash] = node;
+
673 } else {
+
674 next = &node->id_next;
+
675 }
+
676 }
+
677 if (t->split == t->size / 2)
+
678 node_table_resize(t);
+
679}
+
680
+
681static void hash_id(struct fuse *f, struct node *node)
+
682{
+
683 size_t hash = id_hash(f, node->nodeid);
+
684 node->id_next = f->id_table.array[hash];
+
685 f->id_table.array[hash] = node;
+
686 f->id_table.use++;
+
687
+
688 if (f->id_table.use >= f->id_table.size / 2)
+
689 rehash_id(f);
+
690}
+
691
+
692static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
693 const char *name)
+
694{
+
695 uint64_t hash = parent;
+
696 uint64_t oldhash;
+
697
+
698 for (; *name; name++)
+
699 hash = hash * 31 + (unsigned char) *name;
+
700
+
701 hash %= f->name_table.size;
+
702 oldhash = hash % (f->name_table.size / 2);
+
703 if (oldhash >= f->name_table.split)
+
704 return oldhash;
+
705 else
+
706 return hash;
+
707}
+
708
+
709static void unref_node(struct fuse *f, struct node *node);
+
710
+
711static void remerge_name(struct fuse *f)
+
712{
+
713 struct node_table *t = &f->name_table;
+
714 int iter;
+
715
+
716 if (t->split == 0)
+
717 node_table_reduce(t);
+
718
+
719 for (iter = 8; t->split > 0 && iter; iter--) {
+
720 struct node **upper;
+
721
+
722 t->split--;
+
723 upper = &t->array[t->split + t->size / 2];
+
724 if (*upper) {
+
725 struct node **nodep;
+
726
+
727 for (nodep = &t->array[t->split]; *nodep;
+
728 nodep = &(*nodep)->name_next);
+
729
+
730 *nodep = *upper;
+
731 *upper = NULL;
+
732 break;
+
733 }
+
734 }
+
735}
+
736
+
737static void unhash_name(struct fuse *f, struct node *node)
+
738{
+
739 if (node->name) {
+
740 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
741 struct node **nodep = &f->name_table.array[hash];
+
742
+
743 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
744 if (*nodep == node) {
+
745 *nodep = node->name_next;
+
746 node->name_next = NULL;
+
747 unref_node(f, node->parent);
+
748 if (node->name != node->inline_name)
+
749 free(node->name);
+
750 node->name = NULL;
+
751 node->parent = NULL;
+
752 f->name_table.use--;
+
753
+
754 if (f->name_table.use < f->name_table.size / 4)
+
755 remerge_name(f);
+
756 return;
+
757 }
+
758 fuse_log(FUSE_LOG_ERR,
+
759 "fuse internal error: unable to unhash node: %llu\n",
+
760 (unsigned long long) node->nodeid);
+
761 abort();
+
762 }
+
763}
+
764
+
765static void rehash_name(struct fuse *f)
+
766{
+
767 struct node_table *t = &f->name_table;
+
768 struct node **nodep;
+
769 struct node **next;
+
770 size_t hash;
+
771
+
772 if (t->split == t->size / 2)
+
773 return;
+
774
+
775 hash = t->split;
+
776 t->split++;
+
777 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
778 struct node *node = *nodep;
+
779 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
780
+
781 if (newhash != hash) {
+
782 next = nodep;
+
783 *nodep = node->name_next;
+
784 node->name_next = t->array[newhash];
+
785 t->array[newhash] = node;
+
786 } else {
+
787 next = &node->name_next;
+
788 }
+
789 }
+
790 if (t->split == t->size / 2)
+
791 node_table_resize(t);
+
792}
+
793
+
794static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
795 const char *name)
+
796{
+
797 size_t hash = name_hash(f, parentid, name);
+
798 struct node *parent = get_node(f, parentid);
+
799 if (strlen(name) < sizeof(node->inline_name)) {
+
800 strcpy(node->inline_name, name);
+
801 node->name = node->inline_name;
+
802 } else {
+
803 node->name = strdup(name);
+
804 if (node->name == NULL)
+
805 return -1;
+
806 }
+
807
+
808 parent->refctr ++;
+
809 node->parent = parent;
+
810 node->name_next = f->name_table.array[hash];
+
811 f->name_table.array[hash] = node;
+
812 f->name_table.use++;
+
813
+
814 if (f->name_table.use >= f->name_table.size / 2)
+
815 rehash_name(f);
+
816
+
817 return 0;
+
818}
+
819
+
820static void delete_node(struct fuse *f, struct node *node)
+
821{
+
822 if (f->conf.debug)
+
823 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
824 (unsigned long long) node->nodeid);
+
825
+
826 assert(node->treelock == 0);
+
827 unhash_name(f, node);
+
828 if (lru_enabled(f))
+
829 remove_node_lru(node);
+
830 unhash_id(f, node);
+
831 free_node(f, node);
+
832}
+
833
+
834static void unref_node(struct fuse *f, struct node *node)
+
835{
+
836 assert(node->refctr > 0);
+
837 node->refctr --;
+
838 if (!node->refctr)
+
839 delete_node(f, node);
+
840}
+
841
+
842static fuse_ino_t next_id(struct fuse *f)
+
843{
+
844 do {
+
845 f->ctr = (f->ctr + 1) & 0xffffffff;
+
846 if (!f->ctr)
+
847 f->generation ++;
+
848 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
849 get_node_nocheck(f, f->ctr) != NULL);
+
850 return f->ctr;
+
851}
+
852
+
853static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
854 const char *name)
+
855{
+
856 size_t hash = name_hash(f, parent, name);
+
857 struct node *node;
+
858
+
859 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
860 if (node->parent->nodeid == parent &&
+
861 strcmp(node->name, name) == 0)
+
862 return node;
+
863
+
864 return NULL;
+
865}
+
866
+
867static void inc_nlookup(struct node *node)
+
868{
+
869 if (!node->nlookup)
+
870 node->refctr++;
+
871 node->nlookup++;
+
872}
+
873
+
874static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
875 const char *name)
+
876{
+
877 struct node *node;
+
878
+
879 pthread_mutex_lock(&f->lock);
+
880 if (!name)
+
881 node = get_node(f, parent);
+
882 else
+
883 node = lookup_node(f, parent, name);
+
884 if (node == NULL) {
+
885 node = alloc_node(f);
+
886 if (node == NULL)
+
887 goto out_err;
+
888
+
889 node->nodeid = next_id(f);
+
890 node->generation = f->generation;
+
891 if (f->conf.remember)
+
892 inc_nlookup(node);
+
893
+
894 if (hash_name(f, node, parent, name) == -1) {
+
895 free_node(f, node);
+
896 node = NULL;
+
897 goto out_err;
+
898 }
+
899 hash_id(f, node);
+
900 if (lru_enabled(f)) {
+
901 struct node_lru *lnode = node_lru(node);
+
902 init_list_head(&lnode->lru);
+
903 }
+
904 } else if (lru_enabled(f) && node->nlookup == 1) {
+
905 remove_node_lru(node);
+
906 }
+
907 inc_nlookup(node);
+
908out_err:
+
909 pthread_mutex_unlock(&f->lock);
+
910 return node;
+
911}
+
912
+
913static int lookup_path_in_cache(struct fuse *f,
+
914 const char *path, fuse_ino_t *inop)
+
915{
+
916 char *tmp = strdup(path);
+
917 if (!tmp)
+
918 return -ENOMEM;
+
919
+
920 pthread_mutex_lock(&f->lock);
+
921 fuse_ino_t ino = FUSE_ROOT_ID;
+
922
+
923 int err = 0;
+
924 char *save_ptr;
+
925 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
926 while (path_element != NULL) {
+
927 struct node *node = lookup_node(f, ino, path_element);
+
928 if (node == NULL) {
+
929 err = -ENOENT;
+
930 break;
+
931 }
+
932 ino = node->nodeid;
+
933 path_element = strtok_r(NULL, "/", &save_ptr);
+
934 }
+
935 pthread_mutex_unlock(&f->lock);
+
936 free(tmp);
+
937
+
938 if (!err)
+
939 *inop = ino;
+
940 return err;
+
941}
+
942
+
943static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
944{
+
945 size_t len = strlen(name);
+
946
+
947 if (s - len <= *buf) {
+
948 unsigned pathlen = *bufsize - (s - *buf);
+
949 unsigned newbufsize = *bufsize;
+
950 char *newbuf;
+
951
+
952 while (newbufsize < pathlen + len + 1) {
+
953 if (newbufsize >= 0x80000000)
+
954 newbufsize = 0xffffffff;
+
955 else
+
956 newbufsize *= 2;
+
957 }
+
958
+
959 newbuf = realloc(*buf, newbufsize);
+
960 if (newbuf == NULL)
+
961 return NULL;
+
962
+
963 *buf = newbuf;
+
964 s = newbuf + newbufsize - pathlen;
+
965 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
966 *bufsize = newbufsize;
+
967 }
+
968 s -= len;
+
969 memcpy(s, name, len);
+
970 s--;
+
971 *s = '/';
+
972
+
973 return s;
+
974}
+
975
+
976static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
977 struct node *end)
+
978{
+
979 struct node *node;
+
980
+
981 if (wnode) {
+
982 assert(wnode->treelock == TREELOCK_WRITE);
+
983 wnode->treelock = 0;
+
984 }
+
985
+
986 for (node = get_node(f, nodeid);
+
987 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
988 assert(node->treelock != 0);
+
989 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
990 assert(node->treelock != TREELOCK_WRITE);
+
991 node->treelock--;
+
992 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
993 node->treelock = 0;
+
994 }
+
995}
+
996
+
997static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
998 char **path, struct node **wnodep, bool need_lock)
+
999{
+
1000 unsigned bufsize = 256;
+
1001 char *buf;
+
1002 char *s;
+
1003 struct node *node;
+
1004 struct node *wnode = NULL;
+
1005 int err;
+
1006
+
1007 *path = NULL;
+
1008
+
1009 err = -ENOMEM;
+
1010 buf = malloc(bufsize);
+
1011 if (buf == NULL)
+
1012 goto out_err;
+
1013
+
1014 s = buf + bufsize - 1;
+
1015 *s = '\0';
+
1016
+
1017 if (name != NULL) {
+
1018 s = add_name(&buf, &bufsize, s, name);
+
1019 err = -ENOMEM;
+
1020 if (s == NULL)
+
1021 goto out_free;
+
1022 }
+
1023
+
1024 if (wnodep) {
+
1025 assert(need_lock);
+
1026 wnode = lookup_node(f, nodeid, name);
+
1027 if (wnode) {
+
1028 if (wnode->treelock != 0) {
+
1029 if (wnode->treelock > 0)
+
1030 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1031 err = -EAGAIN;
+
1032 goto out_free;
+
1033 }
+
1034 wnode->treelock = TREELOCK_WRITE;
+
1035 }
+
1036 }
+
1037
+
1038 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1039 node = node->parent) {
+
1040 err = -ESTALE;
+
1041 if (node->name == NULL || node->parent == NULL)
+
1042 goto out_unlock;
+
1043
+
1044 err = -ENOMEM;
+
1045 s = add_name(&buf, &bufsize, s, node->name);
+
1046 if (s == NULL)
+
1047 goto out_unlock;
+
1048
+
1049 if (need_lock) {
+
1050 err = -EAGAIN;
+
1051 if (node->treelock < 0)
+
1052 goto out_unlock;
+
1053
+
1054 node->treelock++;
+
1055 }
+
1056 }
+
1057
+
1058 if (s[0])
+
1059 memmove(buf, s, bufsize - (s - buf));
+
1060 else
+
1061 strcpy(buf, "/");
+
1062
+
1063 *path = buf;
+
1064 if (wnodep)
+
1065 *wnodep = wnode;
+
1066
+
1067 return 0;
+
1068
+
1069 out_unlock:
+
1070 if (need_lock)
+
1071 unlock_path(f, nodeid, wnode, node);
+
1072 out_free:
+
1073 free(buf);
+
1074
+
1075 out_err:
+
1076 return err;
+
1077}
+
1078
+
1079static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1080 fuse_ino_t nodeid2, const char *name2,
+
1081 char **path1, char **path2,
+
1082 struct node **wnode1, struct node **wnode2)
+
1083{
+
1084 int err;
+
1085
+
1086 /* FIXME: locking two paths needs deadlock checking */
+
1087 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1088 if (!err) {
+
1089 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1090 if (err) {
+
1091 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1092
+
1093 unlock_path(f, nodeid1, wn1, NULL);
+
1094 free(*path1);
+
1095 }
+
1096 }
+
1097 return err;
+
1098}
+
1099
+
1100static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1101{
+
1102 int err;
+
1103
+
1104 if (!qe->path1) {
+
1105 /* Just waiting for it to be unlocked */
+
1106 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1107 pthread_cond_signal(&qe->cond);
+
1108
+
1109 return;
+
1110 }
+
1111
+
1112 if (qe->done)
+
1113 return; // Don't try to double-lock the element
+
1114
+
1115 if (!qe->path2) {
+
1116 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1117 qe->wnode1, true);
+
1118 } else {
+
1119 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1120 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1121 qe->wnode2);
+
1122 }
+
1123
+
1124 if (err == -EAGAIN)
+
1125 return; /* keep trying */
+
1126
+
1127 qe->err = err;
+
1128 qe->done = true;
+
1129 pthread_cond_signal(&qe->cond);
+
1130}
+
1131
+
1132static void wake_up_queued(struct fuse *f)
+
1133{
+
1134 struct lock_queue_element *qe;
+
1135
+
1136 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1137 queue_element_wakeup(f, qe);
+
1138}
+
1139
+
1140static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1141 const char *name, bool wr)
+
1142{
+
1143 if (f->conf.debug) {
+
1144 struct node *wnode = NULL;
+
1145
+
1146 if (wr)
+
1147 wnode = lookup_node(f, nodeid, name);
+
1148
+
1149 if (wnode) {
+
1150 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1151 msg, (unsigned long long) wnode->nodeid);
+
1152 } else {
+
1153 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1154 msg, (unsigned long long) nodeid);
+
1155 }
+
1156 }
+
1157}
+
1158
+
1159static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1160{
+
1161 struct lock_queue_element **qp;
+
1162
+
1163 qe->done = false;
+
1164 pthread_cond_init(&qe->cond, NULL);
+
1165 qe->next = NULL;
+
1166 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1167 *qp = qe;
+
1168}
+
1169
+
1170static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1171{
+
1172 struct lock_queue_element **qp;
+
1173
+
1174 pthread_cond_destroy(&qe->cond);
+
1175 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1176 *qp = qe->next;
+
1177}
+
1178
+
1179static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1180{
+
1181 queue_path(f, qe);
+
1182
+
1183 do {
+
1184 pthread_cond_wait(&qe->cond, &f->lock);
+
1185 } while (!qe->done);
+
1186
+
1187 dequeue_path(f, qe);
+
1188
+
1189 return qe->err;
+
1190}
+
1191
+
1192static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1193 char **path, struct node **wnode)
+
1194{
+
1195 int err;
+
1196
+
1197 pthread_mutex_lock(&f->lock);
+
1198 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1199 if (err == -EAGAIN) {
+
1200 struct lock_queue_element qe = {
+
1201 .nodeid1 = nodeid,
+
1202 .name1 = name,
+
1203 .path1 = path,
+
1204 .wnode1 = wnode,
+
1205 };
+
1206 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1207 err = wait_path(f, &qe);
+
1208 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1209 }
+
1210 pthread_mutex_unlock(&f->lock);
+
1211
+
1212 return err;
+
1213}
+
1214
+
1215static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1216{
+
1217 return get_path_common(f, nodeid, NULL, path, NULL);
+
1218}
+
1219
+
1220static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1221{
+
1222 int err = 0;
+
1223
+
1224 if (f->conf.nullpath_ok) {
+
1225 *path = NULL;
+
1226 } else {
+
1227 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1228 if (err == -ESTALE)
+
1229 err = 0;
+
1230 }
+
1231
+
1232 return err;
+
1233}
+
1234
+
1235static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1236 char **path)
+
1237{
+
1238 return get_path_common(f, nodeid, name, path, NULL);
+
1239}
+
1240
+
1241static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1242 char **path, struct node **wnode)
+
1243{
+
1244 return get_path_common(f, nodeid, name, path, wnode);
+
1245}
+
1246
+
1247#if defined(__FreeBSD__)
+
1248#define CHECK_DIR_LOOP
+
1249#endif
+
1250
+
1251#if defined(CHECK_DIR_LOOP)
+
1252static int check_dir_loop(struct fuse *f,
+
1253 fuse_ino_t nodeid1, const char *name1,
+
1254 fuse_ino_t nodeid2, const char *name2)
+
1255{
+
1256 struct node *node, *node1, *node2;
+
1257 fuse_ino_t id1, id2;
+
1258
+
1259 node1 = lookup_node(f, nodeid1, name1);
+
1260 id1 = node1 ? node1->nodeid : nodeid1;
+
1261
+
1262 node2 = lookup_node(f, nodeid2, name2);
+
1263 id2 = node2 ? node2->nodeid : nodeid2;
+
1264
+
1265 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1266 node = node->parent) {
+
1267 if (node->name == NULL || node->parent == NULL)
+
1268 break;
+
1269
+
1270 if (node->nodeid != id2 && node->nodeid == id1)
+
1271 return -EINVAL;
+
1272 }
+
1273
+
1274 if (node2)
+
1275 {
+
1276 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1277 node = node->parent) {
+
1278 if (node->name == NULL || node->parent == NULL)
+
1279 break;
+
1280
+
1281 if (node->nodeid != id1 && node->nodeid == id2)
+
1282 return -ENOTEMPTY;
+
1283 }
+
1284 }
+
1285
+
1286 return 0;
+
1287}
+
1288#endif
+
1289
+
1290static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1291 fuse_ino_t nodeid2, const char *name2,
+
1292 char **path1, char **path2,
+
1293 struct node **wnode1, struct node **wnode2)
+
1294{
+
1295 int err;
+
1296
+
1297 pthread_mutex_lock(&f->lock);
+
1298
+
1299#if defined(CHECK_DIR_LOOP)
+
1300 if (name1)
+
1301 {
+
1302 // called during rename; perform dir loop check
+
1303 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1304 if (err)
+
1305 goto out_unlock;
+
1306 }
+
1307#endif
+
1308
+
1309 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1310 path1, path2, wnode1, wnode2);
+
1311 if (err == -EAGAIN) {
+
1312 struct lock_queue_element qe = {
+
1313 .nodeid1 = nodeid1,
+
1314 .name1 = name1,
+
1315 .path1 = path1,
+
1316 .wnode1 = wnode1,
+
1317 .nodeid2 = nodeid2,
+
1318 .name2 = name2,
+
1319 .path2 = path2,
+
1320 .wnode2 = wnode2,
+
1321 };
+
1322
+
1323 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1324 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1325 err = wait_path(f, &qe);
+
1326 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1327 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1328 }
+
1329
+
1330#if defined(CHECK_DIR_LOOP)
+
1331out_unlock:
+
1332#endif
+
1333 pthread_mutex_unlock(&f->lock);
+
1334
+
1335 return err;
+
1336}
+
1337
+
1338static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1339 struct node *wnode, char *path)
+
1340{
+
1341 pthread_mutex_lock(&f->lock);
+
1342 unlock_path(f, nodeid, wnode, NULL);
+
1343 if (f->lockq)
+
1344 wake_up_queued(f);
+
1345 pthread_mutex_unlock(&f->lock);
+
1346 free(path);
+
1347}
+
1348
+
1349static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1350{
+
1351 if (path)
+
1352 free_path_wrlock(f, nodeid, NULL, path);
+
1353}
+
1354
+
1355static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1356 struct node *wnode1, struct node *wnode2,
+
1357 char *path1, char *path2)
+
1358{
+
1359 pthread_mutex_lock(&f->lock);
+
1360 unlock_path(f, nodeid1, wnode1, NULL);
+
1361 unlock_path(f, nodeid2, wnode2, NULL);
+
1362 wake_up_queued(f);
+
1363 pthread_mutex_unlock(&f->lock);
+
1364 free(path1);
+
1365 free(path2);
+
1366}
+
1367
+
1368static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1369{
+
1370 struct node *node;
+
1371 if (nodeid == FUSE_ROOT_ID)
+
1372 return;
+
1373 pthread_mutex_lock(&f->lock);
+
1374 node = get_node(f, nodeid);
+
1375
+
1376 /*
+
1377 * Node may still be locked due to interrupt idiocy in open,
+
1378 * create and opendir
+
1379 */
+
1380 while (node->nlookup == nlookup && node->treelock) {
+
1381 struct lock_queue_element qe = {
+
1382 .nodeid1 = nodeid,
+
1383 };
+
1384
+
1385 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1386 queue_path(f, &qe);
+
1387
+
1388 do {
+
1389 pthread_cond_wait(&qe.cond, &f->lock);
+
1390 } while (node->nlookup == nlookup && node->treelock);
+
1391
+
1392 dequeue_path(f, &qe);
+
1393 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1394 }
+
1395
+
1396 assert(node->nlookup >= nlookup);
+
1397 node->nlookup -= nlookup;
+
1398 if (!node->nlookup) {
+
1399 unref_node(f, node);
+
1400 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1401 set_forget_time(f, node);
+
1402 }
+
1403 pthread_mutex_unlock(&f->lock);
+
1404}
+
1405
+
1406static void unlink_node(struct fuse *f, struct node *node)
+
1407{
+
1408 if (f->conf.remember) {
+
1409 assert(node->nlookup > 1);
+
1410 node->nlookup--;
+
1411 }
+
1412 unhash_name(f, node);
+
1413}
+
1414
+
1415static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1416{
+
1417 struct node *node;
+
1418
+
1419 pthread_mutex_lock(&f->lock);
+
1420 node = lookup_node(f, dir, name);
+
1421 if (node != NULL)
+
1422 unlink_node(f, node);
+
1423 pthread_mutex_unlock(&f->lock);
+
1424}
+
1425
+
1426static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1427 fuse_ino_t newdir, const char *newname, int hide)
+
1428{
+
1429 struct node *node;
+
1430 struct node *newnode;
+
1431 int err = 0;
+
1432
+
1433 pthread_mutex_lock(&f->lock);
+
1434 node = lookup_node(f, olddir, oldname);
+
1435 newnode = lookup_node(f, newdir, newname);
+
1436 if (node == NULL)
+
1437 goto out;
+
1438
+
1439 if (newnode != NULL) {
+
1440 if (hide) {
+
1441 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1442 err = -EBUSY;
+
1443 goto out;
+
1444 }
+
1445 unlink_node(f, newnode);
+
1446 }
+
1447
+
1448 unhash_name(f, node);
+
1449 if (hash_name(f, node, newdir, newname) == -1) {
+
1450 err = -ENOMEM;
+
1451 goto out;
+
1452 }
+
1453
+
1454 if (hide)
+
1455 node->is_hidden = 1;
+
1456
+
1457out:
+
1458 pthread_mutex_unlock(&f->lock);
+
1459 return err;
+
1460}
+
1461
+
1462static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1463 fuse_ino_t newdir, const char *newname)
+
1464{
+
1465 struct node *oldnode;
+
1466 struct node *newnode;
+
1467 int err;
+
1468
+
1469 pthread_mutex_lock(&f->lock);
+
1470 oldnode = lookup_node(f, olddir, oldname);
+
1471 newnode = lookup_node(f, newdir, newname);
+
1472
+
1473 if (oldnode)
+
1474 unhash_name(f, oldnode);
+
1475 if (newnode)
+
1476 unhash_name(f, newnode);
+
1477
+
1478 err = -ENOMEM;
+
1479 if (oldnode) {
+
1480 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1481 goto out;
+
1482 }
+
1483 if (newnode) {
+
1484 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1485 goto out;
+
1486 }
+
1487 err = 0;
+
1488out:
+
1489 pthread_mutex_unlock(&f->lock);
+
1490 return err;
+
1491}
+
1492
+
1493static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1494{
+
1495 if (!f->conf.use_ino)
+
1496 stbuf->st_ino = nodeid;
+
1497 if (f->conf.set_mode) {
+
1498 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1499 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1500 (0777 & ~f->conf.dmask);
+
1501 else if (f->conf.fmask)
+
1502 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1503 (0777 & ~f->conf.fmask);
+
1504 else
+
1505 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1506 (0777 & ~f->conf.umask);
+
1507 }
+
1508 if (f->conf.set_uid)
+
1509 stbuf->st_uid = f->conf.uid;
+
1510 if (f->conf.set_gid)
+
1511 stbuf->st_gid = f->conf.gid;
+
1512}
+
1513
+
1514static struct fuse *req_fuse(fuse_req_t req)
+
1515{
+
1516 return (struct fuse *) fuse_req_userdata(req);
+
1517}
+
1518
+
1519static void fuse_intr_sighandler(int sig)
+
1520{
+
1521 (void) sig;
+
1522 /* Nothing to do */
+
1523}
+
1524
+
1525struct fuse_intr_data {
+
1526 pthread_t id;
+
1527 pthread_cond_t cond;
+
1528 int finished;
+
1529};
+
1530
+
1531static void fuse_interrupt(fuse_req_t req, void *d_)
+
1532{
+
1533 struct fuse_intr_data *d = d_;
+
1534 struct fuse *f = req_fuse(req);
+
1535
+
1536 if (d->id == pthread_self())
+
1537 return;
+
1538
+
1539 pthread_mutex_lock(&f->lock);
+
1540 while (!d->finished) {
+
1541 struct timeval now;
+
1542 struct timespec timeout;
+
1543
+
1544 pthread_kill(d->id, f->conf.intr_signal);
+
1545 gettimeofday(&now, NULL);
+
1546 timeout.tv_sec = now.tv_sec + 1;
+
1547 timeout.tv_nsec = now.tv_usec * 1000;
+
1548 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1549 }
+
1550 pthread_mutex_unlock(&f->lock);
+
1551}
+
1552
+
1553static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1554 struct fuse_intr_data *d)
+
1555{
+
1556 pthread_mutex_lock(&f->lock);
+
1557 d->finished = 1;
+
1558 pthread_cond_broadcast(&d->cond);
+
1559 pthread_mutex_unlock(&f->lock);
+
1560 fuse_req_interrupt_func(req, NULL, NULL);
+
1561 pthread_cond_destroy(&d->cond);
+
1562}
+
1563
+
1564static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1565{
+
1566 d->id = pthread_self();
+
1567 pthread_cond_init(&d->cond, NULL);
+
1568 d->finished = 0;
+
1569 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1570}
+
1571
+
1572static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1573 struct fuse_intr_data *d)
+
1574{
+
1575 if (f->conf.intr)
+
1576 fuse_do_finish_interrupt(f, req, d);
+
1577}
+
1578
+
1579static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1580 struct fuse_intr_data *d)
+
1581{
+
1582 if (f->conf.intr)
+
1583 fuse_do_prepare_interrupt(req, d);
+
1584}
+
1585
+
1586static const char* file_info_string(struct fuse_file_info *fi,
+
1587 char* buf, size_t len)
+
1588{
+
1589 if(fi == NULL)
+
1590 return "NULL";
+
1591 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1592 return buf;
+
1593}
+
1594
+
1595int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1596 struct fuse_file_info *fi)
+
1597{
+
1598 fuse_get_context()->private_data = fs->user_data;
+
1599 if (fs->op.getattr) {
+
1600 if (fs->debug) {
+
1601 char buf[10];
+
1602 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1603 file_info_string(fi, buf, sizeof(buf)),
+
1604 path);
+
1605 }
+
1606 return fs->op.getattr(path, buf, fi);
+
1607 } else {
+
1608 return -ENOSYS;
+
1609 }
+
1610}
+
1611
+
1612int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1613 const char *newpath, unsigned int flags)
+
1614{
+
1615 fuse_get_context()->private_data = fs->user_data;
+
1616 if (fs->op.rename) {
+
1617 if (fs->debug)
+
1618 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1619 flags);
+
1620
+
1621 return fs->op.rename(oldpath, newpath, flags);
+
1622 } else {
+
1623 return -ENOSYS;
+
1624 }
+
1625}
+
1626
+
1627int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1628{
+
1629 fuse_get_context()->private_data = fs->user_data;
+
1630 if (fs->op.unlink) {
+
1631 if (fs->debug)
+
1632 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1633
+
1634 return fs->op.unlink(path);
+
1635 } else {
+
1636 return -ENOSYS;
+
1637 }
+
1638}
+
1639
+
1640int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1641{
+
1642 fuse_get_context()->private_data = fs->user_data;
+
1643 if (fs->op.rmdir) {
+
1644 if (fs->debug)
+
1645 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1646
+
1647 return fs->op.rmdir(path);
+
1648 } else {
+
1649 return -ENOSYS;
+
1650 }
+
1651}
+
1652
+
1653int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1654{
+
1655 fuse_get_context()->private_data = fs->user_data;
+
1656 if (fs->op.symlink) {
+
1657 if (fs->debug)
+
1658 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1659
+
1660 return fs->op.symlink(linkname, path);
+
1661 } else {
+
1662 return -ENOSYS;
+
1663 }
+
1664}
+
1665
+
1666int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1667{
+
1668 fuse_get_context()->private_data = fs->user_data;
+
1669 if (fs->op.link) {
+
1670 if (fs->debug)
+
1671 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1672
+
1673 return fs->op.link(oldpath, newpath);
+
1674 } else {
+
1675 return -ENOSYS;
+
1676 }
+
1677}
+
1678
+
1679int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1680 struct fuse_file_info *fi)
+
1681{
+
1682 fuse_get_context()->private_data = fs->user_data;
+
1683 if (fs->op.release) {
+
1684 if (fs->debug)
+
1685 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1686 fi->flush ? "+flush" : "",
+
1687 (unsigned long long) fi->fh, fi->flags);
+
1688
+
1689 return fs->op.release(path, fi);
+
1690 } else {
+
1691 return 0;
+
1692 }
+
1693}
+
1694
+
1695int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1696 struct fuse_file_info *fi)
+
1697{
+
1698 fuse_get_context()->private_data = fs->user_data;
+
1699 if (fs->op.opendir) {
+
1700 int err;
+
1701
+
1702 if (fs->debug)
+
1703 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1704 path);
+
1705
+
1706 err = fs->op.opendir(path, fi);
+
1707
+
1708 if (fs->debug && !err)
+
1709 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1710 (unsigned long long) fi->fh, fi->flags, path);
+
1711
+
1712 return err;
+
1713 } else {
+
1714 return 0;
+
1715 }
+
1716}
+
1717
+
1718int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1719 struct fuse_file_info *fi)
+
1720{
+
1721 fuse_get_context()->private_data = fs->user_data;
+
1722 if (fs->op.open) {
+
1723 int err;
+
1724
+
1725 if (fs->debug)
+
1726 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1727 path);
+
1728
+
1729 err = fs->op.open(path, fi);
+
1730
+
1731 if (fs->debug && !err)
+
1732 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1733 (unsigned long long) fi->fh, fi->flags, path);
+
1734
+
1735 return err;
+
1736 } else {
+
1737 return 0;
+
1738 }
+
1739}
+
1740
+
1741static void fuse_free_buf(struct fuse_bufvec *buf)
+
1742{
+
1743 if (buf != NULL) {
+
1744 size_t i;
+
1745
+
1746 for (i = 0; i < buf->count; i++)
+
1747 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1748 free(buf->buf[i].mem);
+
1749 free(buf);
+
1750 }
+
1751}
+
1752
+
1753int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1754 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1755 struct fuse_file_info *fi)
+
1756{
+
1757 fuse_get_context()->private_data = fs->user_data;
+
1758 if (fs->op.read || fs->op.read_buf) {
+
1759 int res;
+
1760
+
1761 if (fs->debug)
+
1762 fuse_log(FUSE_LOG_DEBUG,
+
1763 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1764 (unsigned long long) fi->fh,
+
1765 size, (unsigned long long) off, fi->flags);
+
1766
+
1767 if (fs->op.read_buf) {
+
1768 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1769 } else {
+
1770 struct fuse_bufvec *buf;
+
1771 void *mem;
+
1772
+
1773 buf = malloc(sizeof(struct fuse_bufvec));
+
1774 if (buf == NULL)
+
1775 return -ENOMEM;
+
1776
+
1777 mem = malloc(size);
+
1778 if (mem == NULL) {
+
1779 free(buf);
+
1780 return -ENOMEM;
+
1781 }
+
1782 *buf = FUSE_BUFVEC_INIT(size);
+
1783 buf->buf[0].mem = mem;
+
1784 *bufp = buf;
+
1785
+
1786 res = fs->op.read(path, mem, size, off, fi);
+
1787 if (res >= 0)
+
1788 buf->buf[0].size = res;
+
1789 }
+
1790
+
1791 if (fs->debug && res >= 0)
+
1792 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1793 (unsigned long long) fi->fh,
+
1794 fuse_buf_size(*bufp),
+
1795 (unsigned long long) off);
+
1796 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1797 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1798
+
1799 if (res < 0)
+
1800 return res;
+
1801
+
1802 return 0;
+
1803 } else {
+
1804 return -ENOSYS;
+
1805 }
+
1806}
+
1807
+
1808int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1809 off_t off, struct fuse_file_info *fi)
+
1810{
+
1811 fuse_get_context()->private_data = fs->user_data;
+
1812 if (fs->op.read || fs->op.read_buf) {
+
1813 int res;
+
1814
+
1815 if (fs->debug)
+
1816 fuse_log(FUSE_LOG_DEBUG,
+
1817 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1818 (unsigned long long) fi->fh,
+
1819 size, (unsigned long long) off, fi->flags);
+
1820
+
1821 if (fs->op.read_buf) {
+
1822 struct fuse_bufvec *buf = NULL;
+
1823
+
1824 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1825 if (res == 0) {
+
1826 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1827
+
1828 dst.buf[0].mem = mem;
+
1829 res = fuse_buf_copy(&dst, buf, 0);
+
1830 }
+
1831 fuse_free_buf(buf);
+
1832 } else {
+
1833 res = fs->op.read(path, mem, size, off, fi);
+
1834 }
+
1835
+
1836 if (fs->debug && res >= 0)
+
1837 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1838 (unsigned long long) fi->fh,
+
1839 res,
+
1840 (unsigned long long) off);
+
1841 if (res >= 0 && res > (int) size)
+
1842 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1843
+
1844 return res;
+
1845 } else {
+
1846 return -ENOSYS;
+
1847 }
+
1848}
+
1849
+
1850int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1851 struct fuse_bufvec *buf, off_t off,
+
1852 struct fuse_file_info *fi)
+
1853{
+
1854 fuse_get_context()->private_data = fs->user_data;
+
1855 if (fs->op.write_buf || fs->op.write) {
+
1856 int res;
+
1857 size_t size = fuse_buf_size(buf);
+
1858
+
1859 assert(buf->idx == 0 && buf->off == 0);
+
1860 if (fs->debug)
+
1861 fuse_log(FUSE_LOG_DEBUG,
+
1862 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1863 fi->writepage ? "page" : "",
+
1864 (unsigned long long) fi->fh,
+
1865 size,
+
1866 (unsigned long long) off,
+
1867 fi->flags);
+
1868
+
1869 if (fs->op.write_buf) {
+
1870 res = fs->op.write_buf(path, buf, off, fi);
+
1871 } else {
+
1872 void *mem = NULL;
+
1873 struct fuse_buf *flatbuf;
+
1874 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1875
+
1876 if (buf->count == 1 &&
+
1877 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1878 flatbuf = &buf->buf[0];
+
1879 } else {
+
1880 res = -ENOMEM;
+
1881 mem = malloc(size);
+
1882 if (mem == NULL)
+
1883 goto out;
+
1884
+
1885 tmp.buf[0].mem = mem;
+
1886 res = fuse_buf_copy(&tmp, buf, 0);
+
1887 if (res <= 0)
+
1888 goto out_free;
+
1889
+
1890 tmp.buf[0].size = res;
+
1891 flatbuf = &tmp.buf[0];
+
1892 }
+
1893
+
1894 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1895 off, fi);
+
1896out_free:
+
1897 free(mem);
+
1898 }
+
1899out:
+
1900 if (fs->debug && res >= 0)
+
1901 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1902 fi->writepage ? "page" : "",
+
1903 (unsigned long long) fi->fh, res,
+
1904 (unsigned long long) off);
+
1905 if (res > (int) size)
+
1906 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1907
+
1908 return res;
+
1909 } else {
+
1910 return -ENOSYS;
+
1911 }
+
1912}
+
1913
+
1914int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1915 size_t size, off_t off, struct fuse_file_info *fi)
+
1916{
+
1917 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1918
+
1919 bufv.buf[0].mem = (void *) mem;
+
1920
+
1921 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1922}
+
1923
+
1924int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1925 struct fuse_file_info *fi)
+
1926{
+
1927 fuse_get_context()->private_data = fs->user_data;
+
1928 if (fs->op.fsync) {
+
1929 if (fs->debug)
+
1930 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1931 (unsigned long long) fi->fh, datasync);
+
1932
+
1933 return fs->op.fsync(path, datasync, fi);
+
1934 } else {
+
1935 return -ENOSYS;
+
1936 }
+
1937}
+
1938
+
1939int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1940 struct fuse_file_info *fi)
+
1941{
+
1942 fuse_get_context()->private_data = fs->user_data;
+
1943 if (fs->op.fsyncdir) {
+
1944 if (fs->debug)
+
1945 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1946 (unsigned long long) fi->fh, datasync);
+
1947
+
1948 return fs->op.fsyncdir(path, datasync, fi);
+
1949 } else {
+
1950 return -ENOSYS;
+
1951 }
+
1952}
+
1953
+
1954int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1955 struct fuse_file_info *fi)
+
1956{
+
1957 fuse_get_context()->private_data = fs->user_data;
+
1958 if (fs->op.flush) {
+
1959 if (fs->debug)
+
1960 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1961 (unsigned long long) fi->fh);
+
1962
+
1963 return fs->op.flush(path, fi);
+
1964 } else {
+
1965 return -ENOSYS;
+
1966 }
+
1967}
+
1968
+
1969int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1970{
+
1971 fuse_get_context()->private_data = fs->user_data;
+
1972 if (fs->op.statfs) {
+
1973 if (fs->debug)
+
1974 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1975
+
1976 return fs->op.statfs(path, buf);
+
1977 } else {
+
1978 buf->f_namemax = 255;
+
1979 buf->f_bsize = 512;
+
1980 return 0;
+
1981 }
+
1982}
+
1983
+
1984int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1985 struct fuse_file_info *fi)
+
1986{
+
1987 fuse_get_context()->private_data = fs->user_data;
+
1988 if (fs->op.releasedir) {
+
1989 if (fs->debug)
+
1990 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1991 (unsigned long long) fi->fh, fi->flags);
+
1992
+
1993 return fs->op.releasedir(path, fi);
+
1994 } else {
+
1995 return 0;
+
1996 }
+
1997}
+
1998
+
1999int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
2000 fuse_fill_dir_t filler, off_t off,
+
2001 struct fuse_file_info *fi,
+
2002 enum fuse_readdir_flags flags)
+
2003{
+
2004 fuse_get_context()->private_data = fs->user_data;
+
2005 if (fs->op.readdir) {
+
2006 if (fs->debug) {
+
2007 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2008 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2009 (unsigned long long) fi->fh,
+
2010 (unsigned long long) off);
+
2011 }
+
2012
+
2013 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2014 } else {
+
2015 return -ENOSYS;
+
2016 }
+
2017}
+
2018
+
2019int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2020 struct fuse_file_info *fi)
+
2021{
+
2022 fuse_get_context()->private_data = fs->user_data;
+
2023 if (fs->op.create) {
+
2024 int err;
+
2025
+
2026 if (fs->debug)
+
2027 fuse_log(FUSE_LOG_DEBUG,
+
2028 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2029 fi->flags, path, mode,
+
2030 fuse_get_context()->umask);
+
2031
+
2032 err = fs->op.create(path, mode, fi);
+
2033
+
2034 if (fs->debug && !err)
+
2035 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2036 (unsigned long long) fi->fh, fi->flags, path);
+
2037
+
2038 return err;
+
2039 } else {
+
2040 return -ENOSYS;
+
2041 }
+
2042}
+
2043
+
2044int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2045 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2046{
+
2047 fuse_get_context()->private_data = fs->user_data;
+
2048 if (fs->op.lock) {
+
2049 if (fs->debug)
+
2050 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2051 (unsigned long long) fi->fh,
+
2052 (cmd == F_GETLK ? "F_GETLK" :
+
2053 (cmd == F_SETLK ? "F_SETLK" :
+
2054 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2055 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2056 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2057 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2058 "???"))),
+
2059 (unsigned long long) lock->l_start,
+
2060 (unsigned long long) lock->l_len,
+
2061 (unsigned long long) lock->l_pid);
+
2062
+
2063 return fs->op.lock(path, fi, cmd, lock);
+
2064 } else {
+
2065 return -ENOSYS;
+
2066 }
+
2067}
+
2068
+
2069int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2070 struct fuse_file_info *fi, int op)
+
2071{
+
2072 fuse_get_context()->private_data = fs->user_data;
+
2073 if (fs->op.flock) {
+
2074 if (fs->debug) {
+
2075 int xop = op & ~LOCK_NB;
+
2076
+
2077 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2078 (unsigned long long) fi->fh,
+
2079 xop == LOCK_SH ? "LOCK_SH" :
+
2080 (xop == LOCK_EX ? "LOCK_EX" :
+
2081 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2082 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2083 }
+
2084 return fs->op.flock(path, fi, op);
+
2085 } else {
+
2086 return -ENOSYS;
+
2087 }
+
2088}
+
2089
+
2090int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2091 gid_t gid, struct fuse_file_info *fi)
+
2092{
+
2093 fuse_get_context()->private_data = fs->user_data;
+
2094 if (fs->op.chown) {
+
2095 if (fs->debug) {
+
2096 char buf[10];
+
2097 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2098 file_info_string(fi, buf, sizeof(buf)),
+
2099 path, (unsigned long) uid, (unsigned long) gid);
+
2100 }
+
2101 return fs->op.chown(path, uid, gid, fi);
+
2102 } else {
+
2103 return -ENOSYS;
+
2104 }
+
2105}
+
2106
+
2107int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2108 struct fuse_file_info *fi)
+
2109{
+
2110 fuse_get_context()->private_data = fs->user_data;
+
2111 if (fs->op.truncate) {
+
2112 if (fs->debug) {
+
2113 char buf[10];
+
2114 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2115 file_info_string(fi, buf, sizeof(buf)),
+
2116 (unsigned long long) size);
+
2117 }
+
2118 return fs->op.truncate(path, size, fi);
+
2119 } else {
+
2120 return -ENOSYS;
+
2121 }
+
2122}
+
2123
+
2124int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2125 const struct timespec tv[2], struct fuse_file_info *fi)
+
2126{
+
2127 fuse_get_context()->private_data = fs->user_data;
+
2128 if (fs->op.utimens) {
+
2129 if (fs->debug) {
+
2130 char buf[10];
+
2131 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n",
+
2132 file_info_string(fi, buf, sizeof(buf)),
+
2133 path, tv[0].tv_sec, tv[0].tv_nsec,
+
2134 tv[1].tv_sec, tv[1].tv_nsec);
+
2135 }
+
2136 return fs->op.utimens(path, tv, fi);
+
2137 } else {
+
2138 return -ENOSYS;
+
2139 }
+
2140}
+
2141
+
2142int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2143{
+
2144 fuse_get_context()->private_data = fs->user_data;
+
2145 if (fs->op.access) {
+
2146 if (fs->debug)
+
2147 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2148
+
2149 return fs->op.access(path, mask);
+
2150 } else {
+
2151 return -ENOSYS;
+
2152 }
+
2153}
+
2154
+
2155int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2156 size_t len)
+
2157{
+
2158 fuse_get_context()->private_data = fs->user_data;
+
2159 if (fs->op.readlink) {
+
2160 if (fs->debug)
+
2161 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2162 (unsigned long) len);
+
2163
+
2164 return fs->op.readlink(path, buf, len);
+
2165 } else {
+
2166 return -ENOSYS;
+
2167 }
+
2168}
+
2169
+
2170int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2171 dev_t rdev)
+
2172{
+
2173 fuse_get_context()->private_data = fs->user_data;
+
2174 if (fs->op.mknod) {
+
2175 if (fs->debug)
+
2176 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2177 path, mode, (unsigned long long) rdev,
+
2178 fuse_get_context()->umask);
+
2179
+
2180 return fs->op.mknod(path, mode, rdev);
+
2181 } else {
+
2182 return -ENOSYS;
+
2183 }
+
2184}
+
2185
+
2186int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2187{
+
2188 fuse_get_context()->private_data = fs->user_data;
+
2189 if (fs->op.mkdir) {
+
2190 if (fs->debug)
+
2191 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2192 path, mode, fuse_get_context()->umask);
+
2193
+
2194 return fs->op.mkdir(path, mode);
+
2195 } else {
+
2196 return -ENOSYS;
+
2197 }
+
2198}
+
2199
+
2200int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2201 const char *value, size_t size, int flags)
+
2202{
+
2203 fuse_get_context()->private_data = fs->user_data;
+
2204 if (fs->op.setxattr) {
+
2205 if (fs->debug)
+
2206 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2207 path, name, (unsigned long) size, flags);
+
2208
+
2209 return fs->op.setxattr(path, name, value, size, flags);
+
2210 } else {
+
2211 return -ENOSYS;
+
2212 }
+
2213}
+
2214
+
2215int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2216 char *value, size_t size)
+
2217{
+
2218 fuse_get_context()->private_data = fs->user_data;
+
2219 if (fs->op.getxattr) {
+
2220 if (fs->debug)
+
2221 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2222 path, name, (unsigned long) size);
+
2223
+
2224 return fs->op.getxattr(path, name, value, size);
+
2225 } else {
+
2226 return -ENOSYS;
+
2227 }
+
2228}
+
2229
+
2230int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2231 size_t size)
+
2232{
+
2233 fuse_get_context()->private_data = fs->user_data;
+
2234 if (fs->op.listxattr) {
+
2235 if (fs->debug)
+
2236 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2237 path, (unsigned long) size);
+
2238
+
2239 return fs->op.listxattr(path, list, size);
+
2240 } else {
+
2241 return -ENOSYS;
+
2242 }
+
2243}
+
2244
+
2245int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2246 uint64_t *idx)
+
2247{
+
2248 fuse_get_context()->private_data = fs->user_data;
+
2249 if (fs->op.bmap) {
+
2250 if (fs->debug)
+
2251 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2252 path, (unsigned long) blocksize,
+
2253 (unsigned long long) *idx);
+
2254
+
2255 return fs->op.bmap(path, blocksize, idx);
+
2256 } else {
+
2257 return -ENOSYS;
+
2258 }
+
2259}
+
2260
+
2261int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2262{
+
2263 fuse_get_context()->private_data = fs->user_data;
+
2264 if (fs->op.removexattr) {
+
2265 if (fs->debug)
+
2266 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2267
+
2268 return fs->op.removexattr(path, name);
+
2269 } else {
+
2270 return -ENOSYS;
+
2271 }
+
2272}
+
2273
+
2274int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2275 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2276 void *data)
+
2277{
+
2278 fuse_get_context()->private_data = fs->user_data;
+
2279 if (fs->op.ioctl) {
+
2280 if (fs->debug)
+
2281 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2282 (unsigned long long) fi->fh, cmd, flags);
+
2283
+
2284 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2285 } else
+
2286 return -ENOSYS;
+
2287}
+
2288
+
2289int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2290 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2291 unsigned *reventsp)
+
2292{
+
2293 fuse_get_context()->private_data = fs->user_data;
+
2294 if (fs->op.poll) {
+
2295 int res;
+
2296
+
2297 if (fs->debug)
+
2298 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2299 (unsigned long long) fi->fh, ph,
+
2300 fi->poll_events);
+
2301
+
2302 res = fs->op.poll(path, fi, ph, reventsp);
+
2303
+
2304 if (fs->debug && !res)
+
2305 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2306 (unsigned long long) fi->fh, *reventsp);
+
2307
+
2308 return res;
+
2309 } else
+
2310 return -ENOSYS;
+
2311}
+
2312
+
2313int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2314 off_t offset, off_t length, struct fuse_file_info *fi)
+
2315{
+
2316 fuse_get_context()->private_data = fs->user_data;
+
2317 if (fs->op.fallocate) {
+
2318 if (fs->debug)
+
2319 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2320 path,
+
2321 mode,
+
2322 (unsigned long long) offset,
+
2323 (unsigned long long) length);
+
2324
+
2325 return fs->op.fallocate(path, mode, offset, length, fi);
+
2326 } else
+
2327 return -ENOSYS;
+
2328}
+
2329
+
2330ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2331 struct fuse_file_info *fi_in, off_t off_in,
+
2332 const char *path_out,
+
2333 struct fuse_file_info *fi_out, off_t off_out,
+
2334 size_t len, int flags)
+
2335{
+
2336 fuse_get_context()->private_data = fs->user_data;
+
2337 if (fs->op.copy_file_range) {
+
2338 if (fs->debug)
+
2339 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2340 "%s:%llu, length: %llu\n",
+
2341 path_in,
+
2342 (unsigned long long) off_in,
+
2343 path_out,
+
2344 (unsigned long long) off_out,
+
2345 (unsigned long long) len);
+
2346
+
2347 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2348 fi_out, off_out, len, flags);
+
2349 } else
+
2350 return -ENOSYS;
+
2351}
+
2352
+
2353off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2354 struct fuse_file_info *fi)
+
2355{
+
2356 fuse_get_context()->private_data = fs->user_data;
+
2357 if (fs->op.lseek) {
+
2358 if (fs->debug) {
+
2359 char buf[10];
+
2360 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2361 file_info_string(fi, buf, sizeof(buf)),
+
2362 (unsigned long long) off, whence);
+
2363 }
+
2364 return fs->op.lseek(path, off, whence, fi);
+
2365 } else {
+
2366 return -ENOSYS;
+
2367 }
+
2368}
+
2369
+
2370static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2371{
+
2372 struct node *node;
+
2373 int isopen = 0;
+
2374 pthread_mutex_lock(&f->lock);
+
2375 node = lookup_node(f, dir, name);
+
2376 if (node && node->open_count > 0)
+
2377 isopen = 1;
+
2378 pthread_mutex_unlock(&f->lock);
+
2379 return isopen;
+
2380}
+
2381
+
2382static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2383 char *newname, size_t bufsize)
+
2384{
+
2385 struct stat buf;
+
2386 struct node *node;
+
2387 struct node *newnode;
+
2388 char *newpath;
+
2389 int res;
+
2390 int failctr = 10;
+
2391
+
2392 do {
+
2393 pthread_mutex_lock(&f->lock);
+
2394 node = lookup_node(f, dir, oldname);
+
2395 if (node == NULL) {
+
2396 pthread_mutex_unlock(&f->lock);
+
2397 return NULL;
+
2398 }
+
2399 do {
+
2400 f->hidectr ++;
+
2401 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2402 (unsigned int) node->nodeid, f->hidectr);
+
2403 newnode = lookup_node(f, dir, newname);
+
2404 } while(newnode);
+
2405
+
2406 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2407 pthread_mutex_unlock(&f->lock);
+
2408 if (res)
+
2409 break;
+
2410
+
2411 memset(&buf, 0, sizeof(buf));
+
2412 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2413 if (res == -ENOENT)
+
2414 break;
+
2415 free(newpath);
+
2416 newpath = NULL;
+
2417 } while(res == 0 && --failctr);
+
2418
+
2419 return newpath;
+
2420}
+
2421
+
2422static int hide_node(struct fuse *f, const char *oldpath,
+
2423 fuse_ino_t dir, const char *oldname)
+
2424{
+
2425 char newname[64];
+
2426 char *newpath;
+
2427 int err = -EBUSY;
+
2428
+
2429 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2430 if (newpath) {
+
2431 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2432 if (!err)
+
2433 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2434 free(newpath);
+
2435 }
+
2436 return err;
+
2437}
+
2438
+
2439static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2440{
+
2441 return stbuf->st_mtime == ts->tv_sec &&
+
2442 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2443}
+
2444
+
2445#ifndef CLOCK_MONOTONIC
+
2446#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2447#endif
+
2448
+
2449static void curr_time(struct timespec *now)
+
2450{
+
2451 static clockid_t clockid = CLOCK_MONOTONIC;
+
2452 int res = clock_gettime(clockid, now);
+
2453 if (res == -1 && errno == EINVAL) {
+
2454 clockid = CLOCK_REALTIME;
+
2455 res = clock_gettime(clockid, now);
+
2456 }
+
2457 if (res == -1) {
+
2458 perror("fuse: clock_gettime");
+
2459 abort();
+
2460 }
+
2461}
+
2462
+
2463static void update_stat(struct node *node, const struct stat *stbuf)
+
2464{
+
2465 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2466 stbuf->st_size != node->size))
+
2467 node->cache_valid = 0;
+
2468 node->mtime.tv_sec = stbuf->st_mtime;
+
2469 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2470 node->size = stbuf->st_size;
+
2471 curr_time(&node->stat_updated);
+
2472}
+
2473
+
2474static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2475 struct fuse_entry_param *e)
+
2476{
+
2477 struct node *node;
+
2478
+
2479 node = find_node(f, nodeid, name);
+
2480 if (node == NULL)
+
2481 return -ENOMEM;
+
2482
+
2483 e->ino = node->nodeid;
+
2484 e->generation = node->generation;
+
2485 e->entry_timeout = f->conf.entry_timeout;
+
2486 e->attr_timeout = f->conf.attr_timeout;
+
2487 if (f->conf.auto_cache) {
+
2488 pthread_mutex_lock(&f->lock);
+
2489 update_stat(node, &e->attr);
+
2490 pthread_mutex_unlock(&f->lock);
+
2491 }
+
2492 set_stat(f, e->ino, &e->attr);
+
2493 return 0;
+
2494}
+
2495
+
2496static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2497 const char *name, const char *path,
+
2498 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2499{
+
2500 int res;
+
2501
+
2502 memset(e, 0, sizeof(struct fuse_entry_param));
+
2503 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2504 if (res == 0) {
+
2505 res = do_lookup(f, nodeid, name, e);
+
2506 if (res == 0 && f->conf.debug) {
+
2507 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2508 (unsigned long long) e->ino);
+
2509 }
+
2510 }
+
2511 return res;
+
2512}
+
2513
+
2514static struct fuse_context_i *fuse_get_context_internal(void)
+
2515{
+
2516 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2517}
+
2518
+
2519static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2520{
+
2521 struct fuse_context_i *c = fuse_get_context_internal();
+
2522 if (c == NULL) {
+
2523 c = (struct fuse_context_i *)
+
2524 calloc(1, sizeof(struct fuse_context_i));
+
2525 if (c == NULL) {
+
2526 /* This is hard to deal with properly, so just
+
2527 abort. If memory is so low that the
+
2528 context cannot be allocated, there's not
+
2529 much hope for the filesystem anyway */
+
2530 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2531 abort();
+
2532 }
+
2533 pthread_setspecific(fuse_context_key, c);
+
2534 } else {
+
2535 memset(c, 0, sizeof(*c));
+
2536 }
+
2537 c->ctx.fuse = f;
+
2538
+
2539 return c;
+
2540}
+
2541
+
2542static void fuse_freecontext(void *data)
+
2543{
+
2544 free(data);
+
2545}
+
2546
+
2547static int fuse_create_context_key(void)
+
2548{
+
2549 int err = 0;
+
2550 pthread_mutex_lock(&fuse_context_lock);
+
2551 if (!fuse_context_ref) {
+
2552 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2553 if (err) {
+
2554 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2555 strerror(err));
+
2556 pthread_mutex_unlock(&fuse_context_lock);
+
2557 return -1;
+
2558 }
+
2559 }
+
2560 fuse_context_ref++;
+
2561 pthread_mutex_unlock(&fuse_context_lock);
+
2562 return 0;
+
2563}
+
2564
+
2565static void fuse_delete_context_key(void)
+
2566{
+
2567 pthread_mutex_lock(&fuse_context_lock);
+
2568 fuse_context_ref--;
+
2569 if (!fuse_context_ref) {
+
2570 free(pthread_getspecific(fuse_context_key));
+
2571 pthread_key_delete(fuse_context_key);
+
2572 }
+
2573 pthread_mutex_unlock(&fuse_context_lock);
+
2574}
+
2575
+
2576static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2577{
+
2578 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2579 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2580 c->req = req;
+
2581 c->ctx.uid = ctx->uid;
+
2582 c->ctx.gid = ctx->gid;
+
2583 c->ctx.pid = ctx->pid;
+
2584 c->ctx.umask = ctx->umask;
+
2585 return c->ctx.fuse;
+
2586}
+
2587
+
2588static inline void reply_err(fuse_req_t req, int err)
+
2589{
+
2590 /* fuse_reply_err() uses non-negated errno values */
+
2591 fuse_reply_err(req, -err);
+
2592}
+
2593
+
2594static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2595 int err)
+
2596{
+
2597 if (!err) {
+
2598 struct fuse *f = req_fuse(req);
+
2599 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2600 /* Skip forget for negative result */
+
2601 if (e->ino != 0)
+
2602 forget_node(f, e->ino, 1);
+
2603 }
+
2604 } else
+
2605 reply_err(req, err);
+
2606}
+
2607
+
2608void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2609 struct fuse_config *cfg)
+
2610{
+
2611 fuse_get_context()->private_data = fs->user_data;
+
2612 if (!fs->op.write_buf)
+
2613 fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ);
+
2614 if (!fs->op.lock)
+
2615 fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS);
+
2616 if (!fs->op.flock)
+
2617 fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
2618 if (fs->op.init) {
+
2619 uint64_t want_ext_default = conn->want_ext;
+
2620 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
+
2621 int rc;
+
2622
+
2623 conn->want = want_default;
+
2624 fs->user_data = fs->op.init(conn, cfg);
+
2625
+
2626 rc = convert_to_conn_want_ext(conn, want_ext_default,
+
2627 want_default);
+
2628
+
2629 if (rc != 0) {
+
2630 /*
+
2631 * This is a grave developer error, but
+
2632 * we cannot return an error here, as the function
+
2633 * signature does not allow it.
+
2634 */
+
2635 fuse_log(
+
2636 FUSE_LOG_ERR,
+
2637 "fuse: Aborting due to invalid conn want flags.\n");
+
2638 _exit(EXIT_FAILURE);
+
2639 }
+
2640 }
+
2641}
+
2642
+
2643static int fuse_init_intr_signal(int signum, int *installed);
+
2644
+
2645static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2646{
+
2647 struct fuse *f = (struct fuse *) data;
+
2648
+
2649 fuse_create_context(f);
+
2650 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
+
2651 fuse_fs_init(f->fs, conn, &f->conf);
+
2652
+
2653 if (f->conf.intr) {
+
2654 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2655 &f->intr_installed) == -1)
+
2656 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2657 } else {
+
2658 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2659 conn->no_interrupt = 1;
+
2660 }
+
2661}
+
2662
+
2663void fuse_fs_destroy(struct fuse_fs *fs)
+
2664{
+
2665 fuse_get_context()->private_data = fs->user_data;
+
2666 if (fs->op.destroy)
+
2667 fs->op.destroy(fs->user_data);
+
2668}
+
2669
+
2670static void fuse_lib_destroy(void *data)
+
2671{
+
2672 struct fuse *f = (struct fuse *) data;
+
2673
+
2674 fuse_create_context(f);
+
2675 fuse_fs_destroy(f->fs);
+
2676}
+
2677
+
2678static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2679 const char *name)
+
2680{
+
2681 struct fuse *f = req_fuse_prepare(req);
+
2682 struct fuse_entry_param e;
+
2683 char *path;
+
2684 int err;
+
2685 struct node *dot = NULL;
+
2686
+
2687 if (name[0] == '.') {
+
2688 int len = strlen(name);
+
2689
+
2690 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2691 pthread_mutex_lock(&f->lock);
+
2692 if (len == 1) {
+
2693 if (f->conf.debug)
+
2694 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2695 dot = get_node_nocheck(f, parent);
+
2696 if (dot == NULL) {
+
2697 pthread_mutex_unlock(&f->lock);
+
2698 reply_entry(req, &e, -ESTALE);
+
2699 return;
+
2700 }
+
2701 dot->refctr++;
+
2702 } else {
+
2703 if (f->conf.debug)
+
2704 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2705 parent = get_node(f, parent)->parent->nodeid;
+
2706 }
+
2707 pthread_mutex_unlock(&f->lock);
+
2708 name = NULL;
+
2709 }
+
2710 }
+
2711
+
2712 err = get_path_name(f, parent, name, &path);
+
2713 if (!err) {
+
2714 struct fuse_intr_data d;
+
2715 if (f->conf.debug)
+
2716 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2717 fuse_prepare_interrupt(f, req, &d);
+
2718 err = lookup_path(f, parent, name, path, &e, NULL);
+
2719 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2720 e.ino = 0;
+
2721 e.entry_timeout = f->conf.negative_timeout;
+
2722 err = 0;
+
2723 }
+
2724 fuse_finish_interrupt(f, req, &d);
+
2725 free_path(f, parent, path);
+
2726 }
+
2727 if (dot) {
+
2728 pthread_mutex_lock(&f->lock);
+
2729 unref_node(f, dot);
+
2730 pthread_mutex_unlock(&f->lock);
+
2731 }
+
2732 reply_entry(req, &e, err);
+
2733}
+
2734
+
2735static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2736{
+
2737 if (f->conf.debug)
+
2738 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2739 (unsigned long long) nlookup);
+
2740 forget_node(f, ino, nlookup);
+
2741}
+
2742
+
2743static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2744{
+
2745 do_forget(req_fuse(req), ino, nlookup);
+
2746 fuse_reply_none(req);
+
2747}
+
2748
+
2749static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2750 struct fuse_forget_data *forgets)
+
2751{
+
2752 struct fuse *f = req_fuse(req);
+
2753 size_t i;
+
2754
+
2755 for (i = 0; i < count; i++)
+
2756 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2757
+
2758 fuse_reply_none(req);
+
2759}
+
2760
+
2761
+
2762static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2763 struct fuse_file_info *fi)
+
2764{
+
2765 struct fuse *f = req_fuse_prepare(req);
+
2766 struct stat buf;
+
2767 char *path;
+
2768 int err;
+
2769
+
2770 memset(&buf, 0, sizeof(buf));
+
2771
+
2772 if (fi != NULL)
+
2773 err = get_path_nullok(f, ino, &path);
+
2774 else
+
2775 err = get_path(f, ino, &path);
+
2776 if (!err) {
+
2777 struct fuse_intr_data d;
+
2778 fuse_prepare_interrupt(f, req, &d);
+
2779 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2780 fuse_finish_interrupt(f, req, &d);
+
2781 free_path(f, ino, path);
+
2782 }
+
2783 if (!err) {
+
2784 struct node *node;
+
2785
+
2786 pthread_mutex_lock(&f->lock);
+
2787 node = get_node(f, ino);
+
2788 if (node->is_hidden && buf.st_nlink > 0)
+
2789 buf.st_nlink--;
+
2790 if (f->conf.auto_cache)
+
2791 update_stat(node, &buf);
+
2792 pthread_mutex_unlock(&f->lock);
+
2793 set_stat(f, ino, &buf);
+
2794 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2795 } else
+
2796 reply_err(req, err);
+
2797}
+
2798
+
2799int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2800 struct fuse_file_info *fi)
+
2801{
+
2802 fuse_get_context()->private_data = fs->user_data;
+
2803 if (fs->op.chmod) {
+
2804 if (fs->debug) {
+
2805 char buf[10];
+
2806 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2807 file_info_string(fi, buf, sizeof(buf)),
+
2808 path, (unsigned long long) mode);
+
2809 }
+
2810 return fs->op.chmod(path, mode, fi);
+
2811 }
+
2812 else
+
2813 return -ENOSYS;
+
2814}
+
2815
+
2816static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2817 int valid, struct fuse_file_info *fi)
+
2818{
+
2819 struct fuse *f = req_fuse_prepare(req);
+
2820 struct stat buf;
+
2821 char *path;
+
2822 int err;
+
2823
+
2824 memset(&buf, 0, sizeof(buf));
+
2825 if (fi != NULL)
+
2826 err = get_path_nullok(f, ino, &path);
+
2827 else
+
2828 err = get_path(f, ino, &path);
+
2829 if (!err) {
+
2830 struct fuse_intr_data d;
+
2831 fuse_prepare_interrupt(f, req, &d);
+
2832 err = 0;
+
2833 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2834 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2835 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2836 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2837 attr->st_uid : (uid_t) -1;
+
2838 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2839 attr->st_gid : (gid_t) -1;
+
2840 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2841 }
+
2842 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2843 err = fuse_fs_truncate(f->fs, path,
+
2844 attr->st_size, fi);
+
2845 }
+
2846#ifdef HAVE_UTIMENSAT
+
2847 if (!err &&
+
2848 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2849 struct timespec tv[2];
+
2850
+
2851 tv[0].tv_sec = 0;
+
2852 tv[1].tv_sec = 0;
+
2853 tv[0].tv_nsec = UTIME_OMIT;
+
2854 tv[1].tv_nsec = UTIME_OMIT;
+
2855
+
2856 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2857 tv[0].tv_nsec = UTIME_NOW;
+
2858 else if (valid & FUSE_SET_ATTR_ATIME)
+
2859 tv[0] = attr->st_atim;
+
2860
+
2861 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2862 tv[1].tv_nsec = UTIME_NOW;
+
2863 else if (valid & FUSE_SET_ATTR_MTIME)
+
2864 tv[1] = attr->st_mtim;
+
2865
+
2866 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2867 } else
+
2868#endif
+
2869 if (!err &&
+
2870 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2871 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2872 struct timespec tv[2];
+
2873 tv[0].tv_sec = attr->st_atime;
+
2874 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2875 tv[1].tv_sec = attr->st_mtime;
+
2876 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2877 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2878 }
+
2879 if (!err) {
+
2880 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2881 }
+
2882 fuse_finish_interrupt(f, req, &d);
+
2883 free_path(f, ino, path);
+
2884 }
+
2885 if (!err) {
+
2886 if (f->conf.auto_cache) {
+
2887 pthread_mutex_lock(&f->lock);
+
2888 update_stat(get_node(f, ino), &buf);
+
2889 pthread_mutex_unlock(&f->lock);
+
2890 }
+
2891 set_stat(f, ino, &buf);
+
2892 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2893 } else
+
2894 reply_err(req, err);
+
2895}
+
2896
+
2897static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2898{
+
2899 struct fuse *f = req_fuse_prepare(req);
+
2900 char *path;
+
2901 int err;
+
2902
+
2903 err = get_path(f, ino, &path);
+
2904 if (!err) {
+
2905 struct fuse_intr_data d;
+
2906
+
2907 fuse_prepare_interrupt(f, req, &d);
+
2908 err = fuse_fs_access(f->fs, path, mask);
+
2909 fuse_finish_interrupt(f, req, &d);
+
2910 free_path(f, ino, path);
+
2911 }
+
2912 reply_err(req, err);
+
2913}
+
2914
+
2915static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2916{
+
2917 struct fuse *f = req_fuse_prepare(req);
+
2918 char linkname[PATH_MAX + 1];
+
2919 char *path;
+
2920 int err;
+
2921
+
2922 err = get_path(f, ino, &path);
+
2923 if (!err) {
+
2924 struct fuse_intr_data d;
+
2925 fuse_prepare_interrupt(f, req, &d);
+
2926 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2927 fuse_finish_interrupt(f, req, &d);
+
2928 free_path(f, ino, path);
+
2929 }
+
2930 if (!err) {
+
2931 linkname[PATH_MAX] = '\0';
+
2932 fuse_reply_readlink(req, linkname);
+
2933 } else
+
2934 reply_err(req, err);
+
2935}
+
2936
+
2937static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2938 mode_t mode, dev_t rdev)
+
2939{
+
2940 struct fuse *f = req_fuse_prepare(req);
+
2941 struct fuse_entry_param e;
+
2942 char *path;
+
2943 int err;
+
2944
+
2945 err = get_path_name(f, parent, name, &path);
+
2946 if (!err) {
+
2947 struct fuse_intr_data d;
+
2948
+
2949 fuse_prepare_interrupt(f, req, &d);
+
2950 err = -ENOSYS;
+
2951 if (S_ISREG(mode)) {
+
2952 struct fuse_file_info fi;
+
2953
+
2954 memset(&fi, 0, sizeof(fi));
+
2955 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2956 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2957 if (!err) {
+
2958 err = lookup_path(f, parent, name, path, &e,
+
2959 &fi);
+
2960 fuse_fs_release(f->fs, path, &fi);
+
2961 }
+
2962 }
+
2963 if (err == -ENOSYS) {
+
2964 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2965 if (!err)
+
2966 err = lookup_path(f, parent, name, path, &e,
+
2967 NULL);
+
2968 }
+
2969 fuse_finish_interrupt(f, req, &d);
+
2970 free_path(f, parent, path);
+
2971 }
+
2972 reply_entry(req, &e, err);
+
2973}
+
2974
+
2975static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2976 mode_t mode)
+
2977{
+
2978 struct fuse *f = req_fuse_prepare(req);
+
2979 struct fuse_entry_param e;
+
2980 char *path;
+
2981 int err;
+
2982
+
2983 err = get_path_name(f, parent, name, &path);
+
2984 if (!err) {
+
2985 struct fuse_intr_data d;
+
2986
+
2987 fuse_prepare_interrupt(f, req, &d);
+
2988 err = fuse_fs_mkdir(f->fs, path, mode);
+
2989 if (!err)
+
2990 err = lookup_path(f, parent, name, path, &e, NULL);
+
2991 fuse_finish_interrupt(f, req, &d);
+
2992 free_path(f, parent, path);
+
2993 }
+
2994 reply_entry(req, &e, err);
+
2995}
+
2996
+
2997static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2998 const char *name)
+
2999{
+
3000 struct fuse *f = req_fuse_prepare(req);
+
3001 struct node *wnode;
+
3002 char *path;
+
3003 int err;
+
3004
+
3005 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3006 if (!err) {
+
3007 struct fuse_intr_data d;
+
3008
+
3009 fuse_prepare_interrupt(f, req, &d);
+
3010 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
3011 err = hide_node(f, path, parent, name);
+
3012 if (!err) {
+
3013 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
3014 if (!is_open(f, parent, wnode->name)) {
+
3015 char *unlinkpath;
+
3016
+
3017 /* get the hidden file path, to unlink it */
+
3018 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
3019 err = fuse_fs_unlink(f->fs, unlinkpath);
+
3020 if (!err)
+
3021 remove_node(f, parent, wnode->name);
+
3022 free(unlinkpath);
+
3023 }
+
3024 }
+
3025 }
+
3026 } else {
+
3027 err = fuse_fs_unlink(f->fs, path);
+
3028 if (!err)
+
3029 remove_node(f, parent, name);
+
3030 }
+
3031 fuse_finish_interrupt(f, req, &d);
+
3032 free_path_wrlock(f, parent, wnode, path);
+
3033 }
+
3034 reply_err(req, err);
+
3035}
+
3036
+
3037static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3038{
+
3039 struct fuse *f = req_fuse_prepare(req);
+
3040 struct node *wnode;
+
3041 char *path;
+
3042 int err;
+
3043
+
3044 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3045 if (!err) {
+
3046 struct fuse_intr_data d;
+
3047
+
3048 fuse_prepare_interrupt(f, req, &d);
+
3049 err = fuse_fs_rmdir(f->fs, path);
+
3050 fuse_finish_interrupt(f, req, &d);
+
3051 if (!err)
+
3052 remove_node(f, parent, name);
+
3053 free_path_wrlock(f, parent, wnode, path);
+
3054 }
+
3055 reply_err(req, err);
+
3056}
+
3057
+
3058static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3059 fuse_ino_t parent, const char *name)
+
3060{
+
3061 struct fuse *f = req_fuse_prepare(req);
+
3062 struct fuse_entry_param e;
+
3063 char *path;
+
3064 int err;
+
3065
+
3066 err = get_path_name(f, parent, name, &path);
+
3067 if (!err) {
+
3068 struct fuse_intr_data d;
+
3069
+
3070 fuse_prepare_interrupt(f, req, &d);
+
3071 err = fuse_fs_symlink(f->fs, linkname, path);
+
3072 if (!err)
+
3073 err = lookup_path(f, parent, name, path, &e, NULL);
+
3074 fuse_finish_interrupt(f, req, &d);
+
3075 free_path(f, parent, path);
+
3076 }
+
3077 reply_entry(req, &e, err);
+
3078}
+
3079
+
3080static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3081 const char *oldname, fuse_ino_t newdir,
+
3082 const char *newname, unsigned int flags)
+
3083{
+
3084 struct fuse *f = req_fuse_prepare(req);
+
3085 char *oldpath;
+
3086 char *newpath;
+
3087 struct node *wnode1;
+
3088 struct node *wnode2;
+
3089 int err;
+
3090
+
3091 err = get_path2(f, olddir, oldname, newdir, newname,
+
3092 &oldpath, &newpath, &wnode1, &wnode2);
+
3093 if (!err) {
+
3094 struct fuse_intr_data d;
+
3095 err = 0;
+
3096 fuse_prepare_interrupt(f, req, &d);
+
3097 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3098 is_open(f, newdir, newname))
+
3099 err = hide_node(f, newpath, newdir, newname);
+
3100 if (!err) {
+
3101 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3102 if (!err) {
+
3103 if (flags & RENAME_EXCHANGE) {
+
3104 err = exchange_node(f, olddir, oldname,
+
3105 newdir, newname);
+
3106 } else {
+
3107 err = rename_node(f, olddir, oldname,
+
3108 newdir, newname, 0);
+
3109 }
+
3110 }
+
3111 }
+
3112 fuse_finish_interrupt(f, req, &d);
+
3113 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3114 }
+
3115 reply_err(req, err);
+
3116}
+
3117
+
3118static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3119 const char *newname)
+
3120{
+
3121 struct fuse *f = req_fuse_prepare(req);
+
3122 struct fuse_entry_param e;
+
3123 char *oldpath;
+
3124 char *newpath;
+
3125 int err;
+
3126
+
3127 err = get_path2(f, ino, NULL, newparent, newname,
+
3128 &oldpath, &newpath, NULL, NULL);
+
3129 if (!err) {
+
3130 struct fuse_intr_data d;
+
3131
+
3132 fuse_prepare_interrupt(f, req, &d);
+
3133 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3134 if (!err)
+
3135 err = lookup_path(f, newparent, newname, newpath,
+
3136 &e, NULL);
+
3137 fuse_finish_interrupt(f, req, &d);
+
3138 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3139 }
+
3140 reply_entry(req, &e, err);
+
3141}
+
3142
+
3143static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3144 struct fuse_file_info *fi)
+
3145{
+
3146 struct node *node;
+
3147 int unlink_hidden = 0;
+
3148
+
3149 fuse_fs_release(f->fs, path, fi);
+
3150
+
3151 pthread_mutex_lock(&f->lock);
+
3152 node = get_node(f, ino);
+
3153 assert(node->open_count > 0);
+
3154 --node->open_count;
+
3155 if (node->is_hidden && !node->open_count) {
+
3156 unlink_hidden = 1;
+
3157 node->is_hidden = 0;
+
3158 }
+
3159 pthread_mutex_unlock(&f->lock);
+
3160
+
3161 if(unlink_hidden) {
+
3162 if (path) {
+
3163 fuse_fs_unlink(f->fs, path);
+
3164 } else if (f->conf.nullpath_ok) {
+
3165 char *unlinkpath;
+
3166
+
3167 if (get_path(f, ino, &unlinkpath) == 0)
+
3168 fuse_fs_unlink(f->fs, unlinkpath);
+
3169
+
3170 free_path(f, ino, unlinkpath);
+
3171 }
+
3172 }
+
3173}
+
3174
+
3175static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3176 const char *name, mode_t mode,
+
3177 struct fuse_file_info *fi)
+
3178{
+
3179 struct fuse *f = req_fuse_prepare(req);
+
3180 struct fuse_intr_data d;
+
3181 struct fuse_entry_param e;
+
3182 char *path;
+
3183 int err;
+
3184
+
3185 err = get_path_name(f, parent, name, &path);
+
3186 if (!err) {
+
3187 fuse_prepare_interrupt(f, req, &d);
+
3188 err = fuse_fs_create(f->fs, path, mode, fi);
+
3189 if (!err) {
+
3190 err = lookup_path(f, parent, name, path, &e, fi);
+
3191 if (err)
+
3192 fuse_fs_release(f->fs, path, fi);
+
3193 else if (!S_ISREG(e.attr.st_mode)) {
+
3194 err = -EIO;
+
3195 fuse_fs_release(f->fs, path, fi);
+
3196 forget_node(f, e.ino, 1);
+
3197 } else {
+
3198 if (f->conf.direct_io)
+
3199 fi->direct_io = 1;
+
3200 if (f->conf.kernel_cache)
+
3201 fi->keep_cache = 1;
+
3202 if (fi->direct_io &&
+
3203 f->conf.parallel_direct_writes)
+
3204 fi->parallel_direct_writes = 1;
+
3205 }
+
3206 }
+
3207 fuse_finish_interrupt(f, req, &d);
+
3208 }
+
3209 if (!err) {
+
3210 pthread_mutex_lock(&f->lock);
+
3211 get_node(f, e.ino)->open_count++;
+
3212 pthread_mutex_unlock(&f->lock);
+
3213 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3214 /* The open syscall was interrupted, so it
+
3215 must be cancelled */
+
3216 fuse_do_release(f, e.ino, path, fi);
+
3217 forget_node(f, e.ino, 1);
+
3218 }
+
3219 } else {
+
3220 reply_err(req, err);
+
3221 }
+
3222
+
3223 free_path(f, parent, path);
+
3224}
+
3225
+
3226static double diff_timespec(const struct timespec *t1,
+
3227 const struct timespec *t2)
+
3228{
+
3229 return (t1->tv_sec - t2->tv_sec) +
+
3230 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3231}
+
3232
+
3233static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3234 struct fuse_file_info *fi)
+
3235{
+
3236 struct node *node;
+
3237
+
3238 pthread_mutex_lock(&f->lock);
+
3239 node = get_node(f, ino);
+
3240 if (node->cache_valid) {
+
3241 struct timespec now;
+
3242
+
3243 curr_time(&now);
+
3244 if (diff_timespec(&now, &node->stat_updated) >
+
3245 f->conf.ac_attr_timeout) {
+
3246 struct stat stbuf;
+
3247 int err;
+
3248 pthread_mutex_unlock(&f->lock);
+
3249 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3250 pthread_mutex_lock(&f->lock);
+
3251 if (!err)
+
3252 update_stat(node, &stbuf);
+
3253 else
+
3254 node->cache_valid = 0;
+
3255 }
+
3256 }
+
3257 if (node->cache_valid)
+
3258 fi->keep_cache = 1;
+
3259
+
3260 node->cache_valid = 1;
+
3261 pthread_mutex_unlock(&f->lock);
+
3262}
+
3263
+
3264static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3265 struct fuse_file_info *fi)
+
3266{
+
3267 struct fuse *f = req_fuse_prepare(req);
+
3268 struct fuse_intr_data d;
+
3269 char *path;
+
3270 int err;
+
3271
+
3272 err = get_path(f, ino, &path);
+
3273 if (!err) {
+
3274 fuse_prepare_interrupt(f, req, &d);
+
3275 err = fuse_fs_open(f->fs, path, fi);
+
3276 if (!err) {
+
3277 if (f->conf.direct_io)
+
3278 fi->direct_io = 1;
+
3279 if (f->conf.kernel_cache)
+
3280 fi->keep_cache = 1;
+
3281
+
3282 if (f->conf.auto_cache)
+
3283 open_auto_cache(f, ino, path, fi);
+
3284
+
3285 if (f->conf.no_rofd_flush &&
+
3286 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3287 fi->noflush = 1;
+
3288
+
3289 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3290 fi->parallel_direct_writes = 1;
+
3291
+
3292 }
+
3293 fuse_finish_interrupt(f, req, &d);
+
3294 }
+
3295 if (!err) {
+
3296 pthread_mutex_lock(&f->lock);
+
3297 get_node(f, ino)->open_count++;
+
3298 pthread_mutex_unlock(&f->lock);
+
3299 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3300 /* The open syscall was interrupted, so it
+
3301 must be cancelled */
+
3302 fuse_do_release(f, ino, path, fi);
+
3303 }
+
3304 } else
+
3305 reply_err(req, err);
+
3306
+
3307 free_path(f, ino, path);
+
3308}
+
3309
+
3310static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3311 off_t off, struct fuse_file_info *fi)
+
3312{
+
3313 struct fuse *f = req_fuse_prepare(req);
+
3314 struct fuse_bufvec *buf = NULL;
+
3315 char *path;
+
3316 int res;
+
3317
+
3318 res = get_path_nullok(f, ino, &path);
+
3319 if (res == 0) {
+
3320 struct fuse_intr_data d;
+
3321
+
3322 fuse_prepare_interrupt(f, req, &d);
+
3323 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3324 fuse_finish_interrupt(f, req, &d);
+
3325 free_path(f, ino, path);
+
3326 }
+
3327
+
3328 if (res == 0)
+ +
3330 else
+
3331 reply_err(req, res);
+
3332
+
3333 fuse_free_buf(buf);
+
3334}
+
3335
+
3336static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3337 struct fuse_bufvec *buf, off_t off,
+
3338 struct fuse_file_info *fi)
+
3339{
+
3340 struct fuse *f = req_fuse_prepare(req);
+
3341 char *path;
+
3342 int res;
+
3343
+
3344 res = get_path_nullok(f, ino, &path);
+
3345 if (res == 0) {
+
3346 struct fuse_intr_data d;
+
3347
+
3348 fuse_prepare_interrupt(f, req, &d);
+
3349 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3350 fuse_finish_interrupt(f, req, &d);
+
3351 free_path(f, ino, path);
+
3352 }
+
3353
+
3354 if (res >= 0)
+
3355 fuse_reply_write(req, res);
+
3356 else
+
3357 reply_err(req, res);
+
3358}
+
3359
+
3360static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3361 struct fuse_file_info *fi)
+
3362{
+
3363 struct fuse *f = req_fuse_prepare(req);
+
3364 char *path;
+
3365 int err;
+
3366
+
3367 err = get_path_nullok(f, ino, &path);
+
3368 if (!err) {
+
3369 struct fuse_intr_data d;
+
3370
+
3371 fuse_prepare_interrupt(f, req, &d);
+
3372 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3373 fuse_finish_interrupt(f, req, &d);
+
3374 free_path(f, ino, path);
+
3375 }
+
3376 reply_err(req, err);
+
3377}
+
3378
+
3379static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3380 struct fuse_file_info *fi)
+
3381{
+
3382 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3383 memset(fi, 0, sizeof(struct fuse_file_info));
+
3384 fi->fh = dh->fh;
+
3385 return dh;
+
3386}
+
3387
+
3388static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3389 struct fuse_file_info *llfi)
+
3390{
+
3391 struct fuse *f = req_fuse_prepare(req);
+
3392 struct fuse_intr_data d;
+
3393 struct fuse_dh *dh;
+
3394 struct fuse_file_info fi;
+
3395 char *path;
+
3396 int err;
+
3397
+
3398 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3399 if (dh == NULL) {
+
3400 reply_err(req, -ENOMEM);
+
3401 return;
+
3402 }
+
3403 memset(dh, 0, sizeof(struct fuse_dh));
+
3404 dh->fuse = f;
+
3405 dh->contents = NULL;
+
3406 dh->first = NULL;
+
3407 dh->len = 0;
+
3408 dh->filled = 0;
+
3409 dh->nodeid = ino;
+
3410 pthread_mutex_init(&dh->lock, NULL);
+
3411
+
3412 llfi->fh = (uintptr_t) dh;
+
3413
+
3414 memset(&fi, 0, sizeof(fi));
+
3415 fi.flags = llfi->flags;
+
3416
+
3417 err = get_path(f, ino, &path);
+
3418 if (!err) {
+
3419 fuse_prepare_interrupt(f, req, &d);
+
3420 err = fuse_fs_opendir(f->fs, path, &fi);
+
3421 fuse_finish_interrupt(f, req, &d);
+
3422 dh->fh = fi.fh;
+
3423 llfi->cache_readdir = fi.cache_readdir;
+
3424 llfi->keep_cache = fi.keep_cache;
+
3425 }
+
3426 if (!err) {
+
3427 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3428 /* The opendir syscall was interrupted, so it
+
3429 must be cancelled */
+
3430 fuse_fs_releasedir(f->fs, path, &fi);
+
3431 pthread_mutex_destroy(&dh->lock);
+
3432 free(dh);
+
3433 }
+
3434 } else {
+
3435 reply_err(req, err);
+
3436 pthread_mutex_destroy(&dh->lock);
+
3437 free(dh);
+
3438 }
+
3439 free_path(f, ino, path);
+
3440}
+
3441
+
3442static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3443{
+
3444 if (minsize > dh->size) {
+
3445 char *newptr;
+
3446 unsigned newsize = dh->size;
+
3447 if (!newsize)
+
3448 newsize = 1024;
+
3449 while (newsize < minsize) {
+
3450 if (newsize >= 0x80000000)
+
3451 newsize = 0xffffffff;
+
3452 else
+
3453 newsize *= 2;
+
3454 }
+
3455
+
3456 newptr = (char *) realloc(dh->contents, newsize);
+
3457 if (!newptr) {
+
3458 dh->error = -ENOMEM;
+
3459 return -1;
+
3460 }
+
3461 dh->contents = newptr;
+
3462 dh->size = newsize;
+
3463 }
+
3464 return 0;
+
3465}
+
3466
+
3467static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3468 struct stat *st, enum fuse_fill_dir_flags flags)
+
3469{
+
3470 struct fuse_direntry *de;
+
3471
+
3472 de = malloc(sizeof(struct fuse_direntry));
+
3473 if (!de) {
+
3474 dh->error = -ENOMEM;
+
3475 return -1;
+
3476 }
+
3477 de->name = strdup(name);
+
3478 if (!de->name) {
+
3479 dh->error = -ENOMEM;
+
3480 free(de);
+
3481 return -1;
+
3482 }
+
3483 de->flags = flags;
+
3484 de->stat = *st;
+
3485 de->next = NULL;
+
3486
+
3487 *dh->last = de;
+
3488 dh->last = &de->next;
+
3489
+
3490 return 0;
+
3491}
+
3492
+
3493static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3494 const char *name)
+
3495{
+
3496 struct node *node;
+
3497 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3498
+
3499 pthread_mutex_lock(&f->lock);
+
3500 node = lookup_node(f, parent, name);
+
3501 if (node)
+
3502 res = node->nodeid;
+
3503 pthread_mutex_unlock(&f->lock);
+
3504
+
3505 return res;
+
3506}
+
3507
+
3508static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3509 off_t off, enum fuse_fill_dir_flags flags)
+
3510{
+
3511 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3512 struct stat stbuf;
+
3513
+
3514 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3515 dh->error = -EIO;
+
3516 return 1;
+
3517 }
+
3518
+
3519 if (statp)
+
3520 stbuf = *statp;
+
3521 else {
+
3522 memset(&stbuf, 0, sizeof(stbuf));
+
3523 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3524 }
+
3525
+
3526 if (!dh->fuse->conf.use_ino) {
+
3527 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3528 if (dh->fuse->conf.readdir_ino) {
+
3529 stbuf.st_ino = (ino_t)
+
3530 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3531 }
+
3532 }
+
3533
+
3534 if (off) {
+
3535 size_t newlen;
+
3536
+
3537 if (dh->filled) {
+
3538 dh->error = -EIO;
+
3539 return 1;
+
3540 }
+
3541
+
3542 if (dh->first) {
+
3543 dh->error = -EIO;
+
3544 return 1;
+
3545 }
+
3546
+
3547 if (extend_contents(dh, dh->needlen) == -1)
+
3548 return 1;
+
3549
+
3550 newlen = dh->len +
+
3551 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3552 dh->needlen - dh->len, name,
+
3553 &stbuf, off);
+
3554 if (newlen > dh->needlen)
+
3555 return 1;
+
3556
+
3557 dh->len = newlen;
+
3558 } else {
+
3559 dh->filled = 1;
+
3560
+
3561 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3562 return 1;
+
3563 }
+
3564 return 0;
+
3565}
+
3566
+
3567static int is_dot_or_dotdot(const char *name)
+
3568{
+
3569 return name[0] == '.' && (name[1] == '\0' ||
+
3570 (name[1] == '.' && name[2] == '\0'));
+
3571}
+
3572
+
3573static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3574 off_t off, enum fuse_fill_dir_flags flags)
+
3575{
+
3576 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3577 struct fuse_entry_param e = {
+
3578 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3579 .ino = 0,
+
3580 };
+
3581 struct fuse *f = dh->fuse;
+
3582 int res;
+
3583
+
3584 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3585 dh->error = -EIO;
+
3586 return 1;
+
3587 }
+
3588
+
3589 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3590 e.attr = *statp;
+
3591 } else {
+
3592 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3593 if (statp) {
+
3594 e.attr.st_mode = statp->st_mode;
+
3595 if (f->conf.use_ino)
+
3596 e.attr.st_ino = statp->st_ino;
+
3597 }
+
3598 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3599 e.attr.st_ino = (ino_t)
+
3600 lookup_nodeid(f, dh->nodeid, name);
+
3601 }
+
3602 }
+
3603
+
3604 if (off) {
+
3605 size_t newlen;
+
3606
+
3607 if (dh->filled) {
+
3608 dh->error = -EIO;
+
3609 return 1;
+
3610 }
+
3611
+
3612 if (dh->first) {
+
3613 dh->error = -EIO;
+
3614 return 1;
+
3615 }
+
3616 if (extend_contents(dh, dh->needlen) == -1)
+
3617 return 1;
+
3618
+
3619 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3620 if (!is_dot_or_dotdot(name)) {
+
3621 res = do_lookup(f, dh->nodeid, name, &e);
+
3622 if (res) {
+
3623 dh->error = res;
+
3624 return 1;
+
3625 }
+
3626 }
+
3627 }
+
3628
+
3629 newlen = dh->len +
+
3630 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3631 dh->needlen - dh->len, name,
+
3632 &e, off);
+
3633 if (newlen > dh->needlen)
+
3634 return 1;
+
3635 dh->len = newlen;
+
3636 } else {
+
3637 dh->filled = 1;
+
3638
+
3639 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3640 return 1;
+
3641 }
+
3642
+
3643 return 0;
+
3644}
+
3645
+
3646static void free_direntries(struct fuse_direntry *de)
+
3647{
+
3648 while (de) {
+
3649 struct fuse_direntry *next = de->next;
+
3650 free(de->name);
+
3651 free(de);
+
3652 de = next;
+
3653 }
+
3654}
+
3655
+
3656static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3657 size_t size, off_t off, struct fuse_dh *dh,
+
3658 struct fuse_file_info *fi,
+
3659 enum fuse_readdir_flags flags)
+
3660{
+
3661 char *path;
+
3662 int err;
+
3663
+
3664 if (f->fs->op.readdir)
+
3665 err = get_path_nullok(f, ino, &path);
+
3666 else
+
3667 err = get_path(f, ino, &path);
+
3668 if (!err) {
+
3669 struct fuse_intr_data d;
+
3670 fuse_fill_dir_t filler = fill_dir;
+
3671
+
3672 if (flags & FUSE_READDIR_PLUS)
+
3673 filler = fill_dir_plus;
+
3674
+
3675 free_direntries(dh->first);
+
3676 dh->first = NULL;
+
3677 dh->last = &dh->first;
+
3678 dh->len = 0;
+
3679 dh->error = 0;
+
3680 dh->needlen = size;
+
3681 dh->filled = 0;
+
3682 dh->req = req;
+
3683 fuse_prepare_interrupt(f, req, &d);
+
3684 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3685 fuse_finish_interrupt(f, req, &d);
+
3686 dh->req = NULL;
+
3687 if (!err)
+
3688 err = dh->error;
+
3689 if (err)
+
3690 dh->filled = 0;
+
3691 free_path(f, ino, path);
+
3692 }
+
3693 return err;
+
3694}
+
3695
+
3696static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3697 off_t off, enum fuse_readdir_flags flags)
+
3698{
+
3699 off_t pos;
+
3700 struct fuse_direntry *de = dh->first;
+
3701 int res;
+
3702
+
3703 dh->len = 0;
+
3704
+
3705 if (extend_contents(dh, dh->needlen) == -1)
+
3706 return dh->error;
+
3707
+
3708 for (pos = 0; pos < off; pos++) {
+
3709 if (!de)
+
3710 break;
+
3711
+
3712 de = de->next;
+
3713 }
+
3714 while (de) {
+
3715 char *p = dh->contents + dh->len;
+
3716 unsigned rem = dh->needlen - dh->len;
+
3717 unsigned thislen;
+
3718 unsigned newlen;
+
3719 pos++;
+
3720
+
3721 if (flags & FUSE_READDIR_PLUS) {
+
3722 struct fuse_entry_param e = {
+
3723 .ino = 0,
+
3724 .attr = de->stat,
+
3725 };
+
3726
+
3727 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3728 !is_dot_or_dotdot(de->name)) {
+
3729 res = do_lookup(dh->fuse, dh->nodeid,
+
3730 de->name, &e);
+
3731 if (res) {
+
3732 dh->error = res;
+
3733 return 1;
+
3734 }
+
3735 }
+
3736
+
3737 thislen = fuse_add_direntry_plus(req, p, rem,
+
3738 de->name, &e, pos);
+
3739 } else {
+
3740 thislen = fuse_add_direntry(req, p, rem,
+
3741 de->name, &de->stat, pos);
+
3742 }
+
3743 newlen = dh->len + thislen;
+
3744 if (newlen > dh->needlen)
+
3745 break;
+
3746 dh->len = newlen;
+
3747 de = de->next;
+
3748 }
+
3749 return 0;
+
3750}
+
3751
+
3752static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3753 off_t off, struct fuse_file_info *llfi,
+
3754 enum fuse_readdir_flags flags)
+
3755{
+
3756 struct fuse *f = req_fuse_prepare(req);
+
3757 struct fuse_file_info fi;
+
3758 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3759 int err;
+
3760
+
3761 pthread_mutex_lock(&dh->lock);
+
3762 /* According to SUS, directory contents need to be refreshed on
+
3763 rewinddir() */
+
3764 if (!off)
+
3765 dh->filled = 0;
+
3766
+
3767 if (!dh->filled) {
+
3768 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3769 if (err) {
+
3770 reply_err(req, err);
+
3771 goto out;
+
3772 }
+
3773 }
+
3774 if (dh->filled) {
+
3775 dh->needlen = size;
+
3776 err = readdir_fill_from_list(req, dh, off, flags);
+
3777 if (err) {
+
3778 reply_err(req, err);
+
3779 goto out;
+
3780 }
+
3781 }
+
3782 fuse_reply_buf(req, dh->contents, dh->len);
+
3783out:
+
3784 pthread_mutex_unlock(&dh->lock);
+
3785}
+
3786
+
3787static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3788 off_t off, struct fuse_file_info *llfi)
+
3789{
+
3790 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3791}
+
3792
+
3793static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3794 off_t off, struct fuse_file_info *llfi)
+
3795{
+
3796 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3797}
+
3798
+
3799static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3800 struct fuse_file_info *llfi)
+
3801{
+
3802 struct fuse *f = req_fuse_prepare(req);
+
3803 struct fuse_intr_data d;
+
3804 struct fuse_file_info fi;
+
3805 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3806 char *path;
+
3807
+
3808 get_path_nullok(f, ino, &path);
+
3809
+
3810 fuse_prepare_interrupt(f, req, &d);
+
3811 fuse_fs_releasedir(f->fs, path, &fi);
+
3812 fuse_finish_interrupt(f, req, &d);
+
3813 free_path(f, ino, path);
+
3814
+
3815 pthread_mutex_lock(&dh->lock);
+
3816 pthread_mutex_unlock(&dh->lock);
+
3817 pthread_mutex_destroy(&dh->lock);
+
3818 free_direntries(dh->first);
+
3819 free(dh->contents);
+
3820 free(dh);
+
3821 reply_err(req, 0);
+
3822}
+
3823
+
3824static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3825 struct fuse_file_info *llfi)
+
3826{
+
3827 struct fuse *f = req_fuse_prepare(req);
+
3828 struct fuse_file_info fi;
+
3829 char *path;
+
3830 int err;
+
3831
+
3832 get_dirhandle(llfi, &fi);
+
3833
+
3834 err = get_path_nullok(f, ino, &path);
+
3835 if (!err) {
+
3836 struct fuse_intr_data d;
+
3837 fuse_prepare_interrupt(f, req, &d);
+
3838 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3839 fuse_finish_interrupt(f, req, &d);
+
3840 free_path(f, ino, path);
+
3841 }
+
3842 reply_err(req, err);
+
3843}
+
3844
+
3845static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3846{
+
3847 struct fuse *f = req_fuse_prepare(req);
+
3848 struct statvfs buf;
+
3849 char *path = NULL;
+
3850 int err = 0;
+
3851
+
3852 memset(&buf, 0, sizeof(buf));
+
3853 if (ino)
+
3854 err = get_path(f, ino, &path);
+
3855
+
3856 if (!err) {
+
3857 struct fuse_intr_data d;
+
3858 fuse_prepare_interrupt(f, req, &d);
+
3859 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3860 fuse_finish_interrupt(f, req, &d);
+
3861 free_path(f, ino, path);
+
3862 }
+
3863
+
3864 if (!err)
+
3865 fuse_reply_statfs(req, &buf);
+
3866 else
+
3867 reply_err(req, err);
+
3868}
+
3869
+
3870static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3871 const char *value, size_t size, int flags)
+
3872{
+
3873 struct fuse *f = req_fuse_prepare(req);
+
3874 char *path;
+
3875 int err;
+
3876
+
3877 err = get_path(f, ino, &path);
+
3878 if (!err) {
+
3879 struct fuse_intr_data d;
+
3880 fuse_prepare_interrupt(f, req, &d);
+
3881 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3882 fuse_finish_interrupt(f, req, &d);
+
3883 free_path(f, ino, path);
+
3884 }
+
3885 reply_err(req, err);
+
3886}
+
3887
+
3888static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3889 const char *name, char *value, size_t size)
+
3890{
+
3891 int err;
+
3892 char *path;
+
3893
+
3894 err = get_path(f, ino, &path);
+
3895 if (!err) {
+
3896 struct fuse_intr_data d;
+
3897 fuse_prepare_interrupt(f, req, &d);
+
3898 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3899 fuse_finish_interrupt(f, req, &d);
+
3900 free_path(f, ino, path);
+
3901 }
+
3902 return err;
+
3903}
+
3904
+
3905static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3906 size_t size)
+
3907{
+
3908 struct fuse *f = req_fuse_prepare(req);
+
3909 int res;
+
3910
+
3911 if (size) {
+
3912 char *value = (char *) malloc(size);
+
3913 if (value == NULL) {
+
3914 reply_err(req, -ENOMEM);
+
3915 return;
+
3916 }
+
3917 res = common_getxattr(f, req, ino, name, value, size);
+
3918 if (res > 0)
+
3919 fuse_reply_buf(req, value, res);
+
3920 else
+
3921 reply_err(req, res);
+
3922 free(value);
+
3923 } else {
+
3924 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3925 if (res >= 0)
+
3926 fuse_reply_xattr(req, res);
+
3927 else
+
3928 reply_err(req, res);
+
3929 }
+
3930}
+
3931
+
3932static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3933 char *list, size_t size)
+
3934{
+
3935 char *path;
+
3936 int err;
+
3937
+
3938 err = get_path(f, ino, &path);
+
3939 if (!err) {
+
3940 struct fuse_intr_data d;
+
3941 fuse_prepare_interrupt(f, req, &d);
+
3942 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3943 fuse_finish_interrupt(f, req, &d);
+
3944 free_path(f, ino, path);
+
3945 }
+
3946 return err;
+
3947}
+
3948
+
3949static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3950{
+
3951 struct fuse *f = req_fuse_prepare(req);
+
3952 int res;
+
3953
+
3954 if (size) {
+
3955 char *list = (char *) malloc(size);
+
3956 if (list == NULL) {
+
3957 reply_err(req, -ENOMEM);
+
3958 return;
+
3959 }
+
3960 res = common_listxattr(f, req, ino, list, size);
+
3961 if (res > 0)
+
3962 fuse_reply_buf(req, list, res);
+
3963 else
+
3964 reply_err(req, res);
+
3965 free(list);
+
3966 } else {
+
3967 res = common_listxattr(f, req, ino, NULL, 0);
+
3968 if (res >= 0)
+
3969 fuse_reply_xattr(req, res);
+
3970 else
+
3971 reply_err(req, res);
+
3972 }
+
3973}
+
3974
+
3975static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3976 const char *name)
+
3977{
+
3978 struct fuse *f = req_fuse_prepare(req);
+
3979 char *path;
+
3980 int err;
+
3981
+
3982 err = get_path(f, ino, &path);
+
3983 if (!err) {
+
3984 struct fuse_intr_data d;
+
3985 fuse_prepare_interrupt(f, req, &d);
+
3986 err = fuse_fs_removexattr(f->fs, path, name);
+
3987 fuse_finish_interrupt(f, req, &d);
+
3988 free_path(f, ino, path);
+
3989 }
+
3990 reply_err(req, err);
+
3991}
+
3992
+
3993static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3994{
+
3995 struct lock *l;
+
3996
+
3997 for (l = node->locks; l; l = l->next)
+
3998 if (l->owner != lock->owner &&
+
3999 lock->start <= l->end && l->start <= lock->end &&
+
4000 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
4001 break;
+
4002
+
4003 return l;
+
4004}
+
4005
+
4006static void delete_lock(struct lock **lockp)
+
4007{
+
4008 struct lock *l = *lockp;
+
4009 *lockp = l->next;
+
4010 free(l);
+
4011}
+
4012
+
4013static void insert_lock(struct lock **pos, struct lock *lock)
+
4014{
+
4015 lock->next = *pos;
+
4016 *pos = lock;
+
4017}
+
4018
+
4019static int locks_insert(struct node *node, struct lock *lock)
+
4020{
+
4021 struct lock **lp;
+
4022 struct lock *newl1 = NULL;
+
4023 struct lock *newl2 = NULL;
+
4024
+
4025 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4026 lock->end != OFFSET_MAX) {
+
4027 newl1 = malloc(sizeof(struct lock));
+
4028 newl2 = malloc(sizeof(struct lock));
+
4029
+
4030 if (!newl1 || !newl2) {
+
4031 free(newl1);
+
4032 free(newl2);
+
4033 return -ENOLCK;
+
4034 }
+
4035 }
+
4036
+
4037 for (lp = &node->locks; *lp;) {
+
4038 struct lock *l = *lp;
+
4039 if (l->owner != lock->owner)
+
4040 goto skip;
+
4041
+
4042 if (lock->type == l->type) {
+
4043 if (l->end < lock->start - 1)
+
4044 goto skip;
+
4045 if (lock->end < l->start - 1)
+
4046 break;
+
4047 if (l->start <= lock->start && lock->end <= l->end)
+
4048 goto out;
+
4049 if (l->start < lock->start)
+
4050 lock->start = l->start;
+
4051 if (lock->end < l->end)
+
4052 lock->end = l->end;
+
4053 goto delete;
+
4054 } else {
+
4055 if (l->end < lock->start)
+
4056 goto skip;
+
4057 if (lock->end < l->start)
+
4058 break;
+
4059 if (lock->start <= l->start && l->end <= lock->end)
+
4060 goto delete;
+
4061 if (l->end <= lock->end) {
+
4062 l->end = lock->start - 1;
+
4063 goto skip;
+
4064 }
+
4065 if (lock->start <= l->start) {
+
4066 l->start = lock->end + 1;
+
4067 break;
+
4068 }
+
4069 *newl2 = *l;
+
4070 newl2->start = lock->end + 1;
+
4071 l->end = lock->start - 1;
+
4072 insert_lock(&l->next, newl2);
+
4073 newl2 = NULL;
+
4074 }
+
4075 skip:
+
4076 lp = &l->next;
+
4077 continue;
+
4078
+
4079 delete:
+
4080 delete_lock(lp);
+
4081 }
+
4082 if (lock->type != F_UNLCK) {
+
4083 *newl1 = *lock;
+
4084 insert_lock(lp, newl1);
+
4085 newl1 = NULL;
+
4086 }
+
4087out:
+
4088 free(newl1);
+
4089 free(newl2);
+
4090 return 0;
+
4091}
+
4092
+
4093static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4094{
+
4095 memset(lock, 0, sizeof(struct lock));
+
4096 lock->type = flock->l_type;
+
4097 lock->start = flock->l_start;
+
4098 lock->end =
+
4099 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4100 lock->pid = flock->l_pid;
+
4101}
+
4102
+
4103static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4104{
+
4105 flock->l_type = lock->type;
+
4106 flock->l_start = lock->start;
+
4107 flock->l_len =
+
4108 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4109 flock->l_pid = lock->pid;
+
4110}
+
4111
+
4112static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4113 const char *path, struct fuse_file_info *fi)
+
4114{
+
4115 struct fuse_intr_data d;
+
4116 struct flock lock;
+
4117 struct lock l;
+
4118 int err;
+
4119 int errlock;
+
4120
+
4121 fuse_prepare_interrupt(f, req, &d);
+
4122 memset(&lock, 0, sizeof(lock));
+
4123 lock.l_type = F_UNLCK;
+
4124 lock.l_whence = SEEK_SET;
+
4125 err = fuse_fs_flush(f->fs, path, fi);
+
4126 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4127 fuse_finish_interrupt(f, req, &d);
+
4128
+
4129 if (errlock != -ENOSYS) {
+
4130 flock_to_lock(&lock, &l);
+
4131 l.owner = fi->lock_owner;
+
4132 pthread_mutex_lock(&f->lock);
+
4133 locks_insert(get_node(f, ino), &l);
+
4134 pthread_mutex_unlock(&f->lock);
+
4135
+
4136 /* if op.lock() is defined FLUSH is needed regardless
+
4137 of op.flush() */
+
4138 if (err == -ENOSYS)
+
4139 err = 0;
+
4140 }
+
4141 return err;
+
4142}
+
4143
+
4144static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4145 struct fuse_file_info *fi)
+
4146{
+
4147 struct fuse *f = req_fuse_prepare(req);
+
4148 struct fuse_intr_data d;
+
4149 char *path;
+
4150 int err = 0;
+
4151
+
4152 get_path_nullok(f, ino, &path);
+
4153 if (fi->flush) {
+
4154 err = fuse_flush_common(f, req, ino, path, fi);
+
4155 if (err == -ENOSYS)
+
4156 err = 0;
+
4157 }
+
4158
+
4159 fuse_prepare_interrupt(f, req, &d);
+
4160 fuse_do_release(f, ino, path, fi);
+
4161 fuse_finish_interrupt(f, req, &d);
+
4162 free_path(f, ino, path);
+
4163
+
4164 reply_err(req, err);
+
4165}
+
4166
+
4167static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4168 struct fuse_file_info *fi)
+
4169{
+
4170 struct fuse *f = req_fuse_prepare(req);
+
4171 char *path;
+
4172 int err;
+
4173
+
4174 get_path_nullok(f, ino, &path);
+
4175 err = fuse_flush_common(f, req, ino, path, fi);
+
4176 free_path(f, ino, path);
+
4177
+
4178 reply_err(req, err);
+
4179}
+
4180
+
4181static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4182 struct fuse_file_info *fi, struct flock *lock,
+
4183 int cmd)
+
4184{
+
4185 struct fuse *f = req_fuse_prepare(req);
+
4186 char *path;
+
4187 int err;
+
4188
+
4189 err = get_path_nullok(f, ino, &path);
+
4190 if (!err) {
+
4191 struct fuse_intr_data d;
+
4192 fuse_prepare_interrupt(f, req, &d);
+
4193 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4194 fuse_finish_interrupt(f, req, &d);
+
4195 free_path(f, ino, path);
+
4196 }
+
4197 return err;
+
4198}
+
4199
+
4200static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4201 struct fuse_file_info *fi, struct flock *lock)
+
4202{
+
4203 int err;
+
4204 struct lock l;
+
4205 struct lock *conflict;
+
4206 struct fuse *f = req_fuse(req);
+
4207
+
4208 flock_to_lock(lock, &l);
+
4209 l.owner = fi->lock_owner;
+
4210 pthread_mutex_lock(&f->lock);
+
4211 conflict = locks_conflict(get_node(f, ino), &l);
+
4212 if (conflict)
+
4213 lock_to_flock(conflict, lock);
+
4214 pthread_mutex_unlock(&f->lock);
+
4215 if (!conflict)
+
4216 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4217 else
+
4218 err = 0;
+
4219
+
4220 if (!err)
+
4221 fuse_reply_lock(req, lock);
+
4222 else
+
4223 reply_err(req, err);
+
4224}
+
4225
+
4226static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4227 struct fuse_file_info *fi, struct flock *lock,
+
4228 int sleep)
+
4229{
+
4230 int err = fuse_lock_common(req, ino, fi, lock,
+
4231 sleep ? F_SETLKW : F_SETLK);
+
4232 if (!err) {
+
4233 struct fuse *f = req_fuse(req);
+
4234 struct lock l;
+
4235 flock_to_lock(lock, &l);
+
4236 l.owner = fi->lock_owner;
+
4237 pthread_mutex_lock(&f->lock);
+
4238 locks_insert(get_node(f, ino), &l);
+
4239 pthread_mutex_unlock(&f->lock);
+
4240 }
+
4241 reply_err(req, err);
+
4242}
+
4243
+
4244static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4245 struct fuse_file_info *fi, int op)
+
4246{
+
4247 struct fuse *f = req_fuse_prepare(req);
+
4248 char *path;
+
4249 int err;
+
4250
+
4251 err = get_path_nullok(f, ino, &path);
+
4252 if (err == 0) {
+
4253 struct fuse_intr_data d;
+
4254 fuse_prepare_interrupt(f, req, &d);
+
4255 err = fuse_fs_flock(f->fs, path, fi, op);
+
4256 fuse_finish_interrupt(f, req, &d);
+
4257 free_path(f, ino, path);
+
4258 }
+
4259 reply_err(req, err);
+
4260}
+
4261
+
4262static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4263 uint64_t idx)
+
4264{
+
4265 struct fuse *f = req_fuse_prepare(req);
+
4266 struct fuse_intr_data d;
+
4267 char *path;
+
4268 int err;
+
4269
+
4270 err = get_path(f, ino, &path);
+
4271 if (!err) {
+
4272 fuse_prepare_interrupt(f, req, &d);
+
4273 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4274 fuse_finish_interrupt(f, req, &d);
+
4275 free_path(f, ino, path);
+
4276 }
+
4277 if (!err)
+
4278 fuse_reply_bmap(req, idx);
+
4279 else
+
4280 reply_err(req, err);
+
4281}
+
4282
+
4283static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4284 void *arg, struct fuse_file_info *llfi,
+
4285 unsigned int flags, const void *in_buf,
+
4286 size_t in_bufsz, size_t out_bufsz)
+
4287{
+
4288 struct fuse *f = req_fuse_prepare(req);
+
4289 struct fuse_intr_data d;
+
4290 struct fuse_file_info fi;
+
4291 char *path, *out_buf = NULL;
+
4292 int err;
+
4293
+
4294 err = -EPERM;
+
4295 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4296 goto err;
+
4297
+
4298 if (flags & FUSE_IOCTL_DIR)
+
4299 get_dirhandle(llfi, &fi);
+
4300 else
+
4301 fi = *llfi;
+
4302
+
4303 if (out_bufsz) {
+
4304 err = -ENOMEM;
+
4305 out_buf = malloc(out_bufsz);
+
4306 if (!out_buf)
+
4307 goto err;
+
4308 }
+
4309
+
4310 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4311 if (out_buf && in_bufsz)
+
4312 memcpy(out_buf, in_buf, in_bufsz);
+
4313
+
4314 err = get_path_nullok(f, ino, &path);
+
4315 if (err)
+
4316 goto err;
+
4317
+
4318 fuse_prepare_interrupt(f, req, &d);
+
4319
+
4320 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4321 out_buf ? out_buf : (void *)in_buf);
+
4322
+
4323 fuse_finish_interrupt(f, req, &d);
+
4324 free_path(f, ino, path);
+
4325
+
4326 if (err < 0)
+
4327 goto err;
+
4328 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4329 goto out;
+
4330err:
+
4331 reply_err(req, err);
+
4332out:
+
4333 free(out_buf);
+
4334}
+
4335
+
4336static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4337 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4338{
+
4339 struct fuse *f = req_fuse_prepare(req);
+
4340 struct fuse_intr_data d;
+
4341 char *path;
+
4342 int err;
+
4343 unsigned revents = 0;
+
4344
+
4345 err = get_path_nullok(f, ino, &path);
+
4346 if (!err) {
+
4347 fuse_prepare_interrupt(f, req, &d);
+
4348 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4349 fuse_finish_interrupt(f, req, &d);
+
4350 free_path(f, ino, path);
+
4351 }
+
4352 if (!err)
+
4353 fuse_reply_poll(req, revents);
+
4354 else
+
4355 reply_err(req, err);
+
4356}
+
4357
+
4358static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4359 off_t offset, off_t length, struct fuse_file_info *fi)
+
4360{
+
4361 struct fuse *f = req_fuse_prepare(req);
+
4362 struct fuse_intr_data d;
+
4363 char *path;
+
4364 int err;
+
4365
+
4366 err = get_path_nullok(f, ino, &path);
+
4367 if (!err) {
+
4368 fuse_prepare_interrupt(f, req, &d);
+
4369 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4370 fuse_finish_interrupt(f, req, &d);
+
4371 free_path(f, ino, path);
+
4372 }
+
4373 reply_err(req, err);
+
4374}
+
4375
+
4376static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4377 off_t off_in, struct fuse_file_info *fi_in,
+
4378 fuse_ino_t nodeid_out, off_t off_out,
+
4379 struct fuse_file_info *fi_out, size_t len,
+
4380 int flags)
+
4381{
+
4382 struct fuse *f = req_fuse_prepare(req);
+
4383 struct fuse_intr_data d;
+
4384 char *path_in, *path_out;
+
4385 int err;
+
4386 ssize_t res;
+
4387
+
4388 err = get_path_nullok(f, nodeid_in, &path_in);
+
4389 if (err) {
+
4390 reply_err(req, err);
+
4391 return;
+
4392 }
+
4393
+
4394 err = get_path_nullok(f, nodeid_out, &path_out);
+
4395 if (err) {
+
4396 free_path(f, nodeid_in, path_in);
+
4397 reply_err(req, err);
+
4398 return;
+
4399 }
+
4400
+
4401 fuse_prepare_interrupt(f, req, &d);
+
4402 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4403 fi_out, off_out, len, flags);
+
4404 fuse_finish_interrupt(f, req, &d);
+
4405
+
4406 if (res >= 0)
+
4407 fuse_reply_write(req, res);
+
4408 else
+
4409 reply_err(req, res);
+
4410
+
4411 free_path(f, nodeid_in, path_in);
+
4412 free_path(f, nodeid_out, path_out);
+
4413}
+
4414
+
4415static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4416 struct fuse_file_info *fi)
+
4417{
+
4418 struct fuse *f = req_fuse_prepare(req);
+
4419 struct fuse_intr_data d;
+
4420 char *path;
+
4421 int err;
+
4422 off_t res;
+
4423
+
4424 err = get_path(f, ino, &path);
+
4425 if (err) {
+
4426 reply_err(req, err);
+
4427 return;
+
4428 }
+
4429
+
4430 fuse_prepare_interrupt(f, req, &d);
+
4431 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4432 fuse_finish_interrupt(f, req, &d);
+
4433 free_path(f, ino, path);
+
4434 if (res >= 0)
+
4435 fuse_reply_lseek(req, res);
+
4436 else
+
4437 reply_err(req, res);
+
4438}
+
4439
+
4440static int clean_delay(struct fuse *f)
+
4441{
+
4442 /*
+
4443 * This is calculating the delay between clean runs. To
+
4444 * reduce the number of cleans we are doing them 10 times
+
4445 * within the remember window.
+
4446 */
+
4447 int min_sleep = 60;
+
4448 int max_sleep = 3600;
+
4449 int sleep_time = f->conf.remember / 10;
+
4450
+
4451 if (sleep_time > max_sleep)
+
4452 return max_sleep;
+
4453 if (sleep_time < min_sleep)
+
4454 return min_sleep;
+
4455 return sleep_time;
+
4456}
+
4457
+
+
4458int fuse_clean_cache(struct fuse *f)
+
4459{
+
4460 struct node_lru *lnode;
+
4461 struct list_head *curr, *next;
+
4462 struct node *node;
+
4463 struct timespec now;
+
4464
+
4465 pthread_mutex_lock(&f->lock);
+
4466
+
4467 curr_time(&now);
+
4468
+
4469 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4470 double age;
+
4471
+
4472 next = curr->next;
+
4473 lnode = list_entry(curr, struct node_lru, lru);
+
4474 node = &lnode->node;
+
4475
+
4476 age = diff_timespec(&now, &lnode->forget_time);
+
4477 if (age <= f->conf.remember)
+
4478 break;
+
4479
+
4480 assert(node->nlookup == 1);
+
4481
+
4482 /* Don't forget active directories */
+
4483 if (node->refctr > 1)
+
4484 continue;
+
4485
+
4486 node->nlookup = 0;
+
4487 unhash_name(f, node);
+
4488 unref_node(f, node);
+
4489 }
+
4490 pthread_mutex_unlock(&f->lock);
+
4491
+
4492 return clean_delay(f);
+
4493}
+
+
4494
+
4495static struct fuse_lowlevel_ops fuse_path_ops = {
+
4496 .init = fuse_lib_init,
+
4497 .destroy = fuse_lib_destroy,
+
4498 .lookup = fuse_lib_lookup,
+
4499 .forget = fuse_lib_forget,
+
4500 .forget_multi = fuse_lib_forget_multi,
+
4501 .getattr = fuse_lib_getattr,
+
4502 .setattr = fuse_lib_setattr,
+
4503 .access = fuse_lib_access,
+
4504 .readlink = fuse_lib_readlink,
+
4505 .mknod = fuse_lib_mknod,
+
4506 .mkdir = fuse_lib_mkdir,
+
4507 .unlink = fuse_lib_unlink,
+
4508 .rmdir = fuse_lib_rmdir,
+
4509 .symlink = fuse_lib_symlink,
+
4510 .rename = fuse_lib_rename,
+
4511 .link = fuse_lib_link,
+
4512 .create = fuse_lib_create,
+
4513 .open = fuse_lib_open,
+
4514 .read = fuse_lib_read,
+
4515 .write_buf = fuse_lib_write_buf,
+
4516 .flush = fuse_lib_flush,
+
4517 .release = fuse_lib_release,
+
4518 .fsync = fuse_lib_fsync,
+
4519 .opendir = fuse_lib_opendir,
+
4520 .readdir = fuse_lib_readdir,
+
4521 .readdirplus = fuse_lib_readdirplus,
+
4522 .releasedir = fuse_lib_releasedir,
+
4523 .fsyncdir = fuse_lib_fsyncdir,
+
4524 .statfs = fuse_lib_statfs,
+
4525 .setxattr = fuse_lib_setxattr,
+
4526 .getxattr = fuse_lib_getxattr,
+
4527 .listxattr = fuse_lib_listxattr,
+
4528 .removexattr = fuse_lib_removexattr,
+
4529 .getlk = fuse_lib_getlk,
+
4530 .setlk = fuse_lib_setlk,
+
4531 .flock = fuse_lib_flock,
+
4532 .bmap = fuse_lib_bmap,
+
4533 .ioctl = fuse_lib_ioctl,
+
4534 .poll = fuse_lib_poll,
+
4535 .fallocate = fuse_lib_fallocate,
+
4536 .copy_file_range = fuse_lib_copy_file_range,
+
4537 .lseek = fuse_lib_lseek,
+
4538};
+
4539
+
4540int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4541{
+
4542 return fuse_lowlevel_notify_poll(ph);
+
4543}
+
4544
+
+
4545struct fuse_session *fuse_get_session(struct fuse *f)
+
4546{
+
4547 return f->se;
+
4548}
+
+
4549
+
4550static int fuse_session_loop_remember(struct fuse *f)
+
4551{
+
4552 struct fuse_session *se = f->se;
+
4553 int res = 0;
+
4554 struct timespec now;
+
4555 time_t next_clean;
+
4556 struct pollfd fds = {
+
4557 .fd = se->fd,
+
4558 .events = POLLIN
+
4559 };
+
4560 struct fuse_buf fbuf = {
+
4561 .mem = NULL,
+
4562 };
+
4563
+
4564 curr_time(&now);
+
4565 next_clean = now.tv_sec;
+
4566 while (!fuse_session_exited(se)) {
+
4567 unsigned timeout;
+
4568
+
4569 curr_time(&now);
+
4570 if (now.tv_sec < next_clean)
+
4571 timeout = next_clean - now.tv_sec;
+
4572 else
+
4573 timeout = 0;
+
4574
+
4575 res = poll(&fds, 1, timeout * 1000);
+
4576 if (res == -1) {
+
4577 if (errno == EINTR)
+
4578 continue;
+
4579 else
+
4580 break;
+
4581 } else if (res > 0) {
+
4582 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4583 NULL);
+
4584 if (res == -EINTR)
+
4585 continue;
+
4586 if (res <= 0)
+
4587 break;
+
4588
+
4589 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4590 } else {
+
4591 timeout = fuse_clean_cache(f);
+
4592 curr_time(&now);
+
4593 next_clean = now.tv_sec + timeout;
+
4594 }
+
4595 }
+
4596
+
4597 free(fbuf.mem);
+ +
4599 return res < 0 ? -1 : 0;
+
4600}
+
4601
+
+
4602int fuse_loop(struct fuse *f)
+
4603{
+
4604 if (!f)
+
4605 return -1;
+
4606
+
4607 if (lru_enabled(f))
+
4608 return fuse_session_loop_remember(f);
+
4609
+
4610 return fuse_session_loop(f->se);
+
4611}
+
+
4612
+
4613FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4614int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4615{
+
4616 if (f == NULL)
+
4617 return -1;
+
4618
+
4619 int res = fuse_start_cleanup_thread(f);
+
4620 if (res)
+
4621 return -1;
+
4622
+
4623 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4625 return res;
+
4626}
+
4627
+
4628int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4629FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4630int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4631{
+
4632 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4633 if (config == NULL)
+
4634 return ENOMEM;
+
4635
+
4636 fuse_loop_cfg_convert(config, config_v1);
+
4637
+
4638 int res = fuse_loop_mt_312(f, config);
+
4639
+
4640 fuse_loop_cfg_destroy(config);
+
4641
+
4642 return res;
+
4643}
+
4644
+
4645int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4646FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4647int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4648{
+
4649 int err;
+
4650 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4651
+
4652 if (config == NULL)
+
4653 return ENOMEM;
+
4654
+
4655 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4656
+
4657 err = fuse_loop_mt_312(f, config);
+
4658
+
4659 fuse_loop_cfg_destroy(config);
+
4660
+
4661 return err;
+
4662}
+
4663
+
+
4664void fuse_exit(struct fuse *f)
+
4665{
+
4666 fuse_session_exit(f->se);
+
4667}
+
+
4668
+
+ +
4670{
+
4671 struct fuse_context_i *c = fuse_get_context_internal();
+
4672
+
4673 if (c)
+
4674 return &c->ctx;
+
4675 else
+
4676 return NULL;
+
4677}
+
+
4678
+
+
4679int fuse_getgroups(int size, gid_t list[])
+
4680{
+
4681 struct fuse_context_i *c = fuse_get_context_internal();
+
4682 if (!c)
+
4683 return -EINVAL;
+
4684
+
4685 return fuse_req_getgroups(c->req, size, list);
+
4686}
+
+
4687
+
+ +
4689{
+
4690 struct fuse_context_i *c = fuse_get_context_internal();
+
4691
+
4692 if (c)
+
4693 return fuse_req_interrupted(c->req);
+
4694 else
+
4695 return 0;
+
4696}
+
+
4697
+
+
4698int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4699 fuse_ino_t ino;
+
4700 int err = lookup_path_in_cache(f, path, &ino);
+
4701 if (err) {
+
4702 return err;
+
4703 }
+
4704
+
4705 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4706}
+
+
4707
+
4708#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4709
+
4710static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4713 FUSE_LIB_OPT("debug", debug, 1),
+
4714 FUSE_LIB_OPT("-d", debug, 1),
+
4715 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4716 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4717 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4718 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4719 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4720 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4721 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4722 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4723 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4724 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4725 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4726 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4727 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4728 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4729 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4730 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4731 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4732 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4733 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4734 FUSE_LIB_OPT("noforget", remember, -1),
+
4735 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4736 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4737 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4739};
+
4740
+
4741static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4742 struct fuse_args *outargs)
+
4743{
+
4744 (void) arg; (void) outargs; (void) data; (void) key;
+
4745
+
4746 /* Pass through unknown options */
+
4747 return 1;
+
4748}
+
4749
+
4750
+
4751static const struct fuse_opt fuse_help_opts[] = {
+
4752 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4753 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4755};
+
4756
+
4757static void print_module_help(const char *name,
+ +
4759{
+
4760 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4761 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4762 fuse_opt_add_arg(&a, "-h") == -1)
+
4763 return;
+
4764 printf("\nOptions for %s module:\n", name);
+
4765 (*fac)(&a, NULL);
+ +
4767}
+
4768
+
+
4769void fuse_lib_help(struct fuse_args *args)
+
4770{
+
4771 /* These are not all options, but only the ones that
+
4772 may be of interest to an end-user */
+
4773 printf(
+
4774" -o kernel_cache cache files in kernel\n"
+
4775" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4776" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4777" -o umask=M set file permissions (octal)\n"
+
4778" -o fmask=M set file permissions (octal)\n"
+
4779" -o dmask=M set dir permissions (octal)\n"
+
4780" -o uid=N set file owner\n"
+
4781" -o gid=N set file group\n"
+
4782" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4783" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4784" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4785" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4786" -o noforget never forget cached inodes\n"
+
4787" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4788" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4789
+
4790
+
4791 /* Print low-level help */
+ +
4793
+
4794 /* Print help for builtin modules */
+
4795 print_module_help("subdir", &fuse_module_subdir_factory);
+
4796#ifdef HAVE_ICONV
+
4797 print_module_help("iconv", &fuse_module_iconv_factory);
+
4798#endif
+
4799
+
4800 /* Parse command line options in case we need to
+
4801 activate more modules */
+
4802 struct fuse_config conf = { .modules = NULL };
+
4803 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4804 fuse_lib_opt_proc) == -1
+
4805 || !conf.modules)
+
4806 return;
+
4807
+
4808 char *module;
+
4809 char *next;
+
4810 struct fuse_module *m;
+
4811
+
4812 // Iterate over all modules
+
4813 for (module = conf.modules; module; module = next) {
+
4814 char *p;
+
4815 for (p = module; *p && *p != ':'; p++);
+
4816 next = *p ? p + 1 : NULL;
+
4817 *p = '\0';
+
4818
+
4819 m = fuse_get_module(module);
+
4820 if (m)
+
4821 print_module_help(module, &m->factory);
+
4822 }
+
4823}
+
+
4824
+
4825static int fuse_init_intr_signal(int signum, int *installed)
+
4826{
+
4827 struct sigaction old_sa;
+
4828
+
4829 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4830 perror("fuse: cannot get old signal handler");
+
4831 return -1;
+
4832 }
+
4833
+
4834 if (old_sa.sa_handler == SIG_DFL) {
+
4835 struct sigaction sa;
+
4836
+
4837 memset(&sa, 0, sizeof(struct sigaction));
+
4838 sa.sa_handler = fuse_intr_sighandler;
+
4839 sigemptyset(&sa.sa_mask);
+
4840
+
4841 if (sigaction(signum, &sa, NULL) == -1) {
+
4842 perror("fuse: cannot set interrupt signal handler");
+
4843 return -1;
+
4844 }
+
4845 *installed = 1;
+
4846 }
+
4847 return 0;
+
4848}
+
4849
+
4850static void fuse_restore_intr_signal(int signum)
+
4851{
+
4852 struct sigaction sa;
+
4853
+
4854 memset(&sa, 0, sizeof(struct sigaction));
+
4855 sa.sa_handler = SIG_DFL;
+
4856 sigaction(signum, &sa, NULL);
+
4857}
+
4858
+
4859
+
4860static int fuse_push_module(struct fuse *f, const char *module,
+
4861 struct fuse_args *args)
+
4862{
+
4863 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4864 struct fuse_fs *newfs;
+
4865 struct fuse_module *m = fuse_get_module(module);
+
4866
+
4867 if (!m)
+
4868 return -1;
+
4869
+
4870 newfs = m->factory(args, fs);
+
4871 if (!newfs) {
+
4872 fuse_put_module(m);
+
4873 return -1;
+
4874 }
+
4875 f->fs = newfs;
+
4876 return 0;
+
4877}
+
4878
+
+
4879struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4880 void *user_data)
+
4881{
+
4882 struct fuse_fs *fs;
+
4883
+
4884 if (sizeof(struct fuse_operations) < op_size) {
+
4885 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4886 op_size = sizeof(struct fuse_operations);
+
4887 }
+
4888
+
4889 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4890 if (!fs) {
+
4891 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4892 return NULL;
+
4893 }
+
4894
+
4895 fs->user_data = user_data;
+
4896 if (op)
+
4897 memcpy(&fs->op, op, op_size);
+
4898 return fs;
+
4899}
+
+
4900
+
4901static int node_table_init(struct node_table *t)
+
4902{
+
4903 t->size = NODE_TABLE_MIN_SIZE;
+
4904 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4905 if (t->array == NULL) {
+
4906 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4907 return -1;
+
4908 }
+
4909 t->use = 0;
+
4910 t->split = 0;
+
4911
+
4912 return 0;
+
4913}
+
4914
+
4915static void *fuse_prune_nodes(void *fuse)
+
4916{
+
4917 struct fuse *f = fuse;
+
4918 int sleep_time;
+
4919
+
4920 pthread_setname_np(pthread_self(), "fuse_prune_nodes");
+
4921
+
4922 while(1) {
+
4923 sleep_time = fuse_clean_cache(f);
+
4924 sleep(sleep_time);
+
4925 }
+
4926 return NULL;
+
4927}
+
4928
+
+
4929int fuse_start_cleanup_thread(struct fuse *f)
+
4930{
+
4931 if (lru_enabled(f))
+
4932 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4933
+
4934 return 0;
+
4935}
+
+
4936
+
+
4937void fuse_stop_cleanup_thread(struct fuse *f)
+
4938{
+
4939 if (lru_enabled(f)) {
+
4940 pthread_mutex_lock(&f->lock);
+
4941 pthread_cancel(f->prune_thread);
+
4942 pthread_mutex_unlock(&f->lock);
+
4943 pthread_join(f->prune_thread, NULL);
+
4944 }
+
4945}
+
+
4946
+
4947/*
+
4948 * Not supposed to be called directly, but supposed to be called
+
4949 * through the fuse_new macro
+
4950 */
+
4951struct fuse *_fuse_new_31(struct fuse_args *args,
+
4952 const struct fuse_operations *op, size_t op_size,
+
4953 struct libfuse_version *version, void *user_data)
+
4954{
+
4955 struct fuse *f;
+
4956 struct node *root;
+
4957 struct fuse_fs *fs;
+
4958 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4959
+
4960 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4961 if (f == NULL) {
+
4962 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4963 goto out;
+
4964 }
+
4965
+
4966 f->conf.entry_timeout = 1.0;
+
4967 f->conf.attr_timeout = 1.0;
+
4968 f->conf.negative_timeout = 0.0;
+
4969 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
4970
+
4971 /* Parse options */
+
4972 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
4973 fuse_lib_opt_proc) == -1)
+
4974 goto out_free;
+
4975
+
4976 pthread_mutex_lock(&fuse_context_lock);
+
4977 static int builtin_modules_registered = 0;
+
4978 /* Have the builtin modules already been registered? */
+
4979 if (builtin_modules_registered == 0) {
+
4980 /* If not, register them. */
+
4981 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
4982#ifdef HAVE_ICONV
+
4983 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
4984#endif
+
4985 builtin_modules_registered= 1;
+
4986 }
+
4987 pthread_mutex_unlock(&fuse_context_lock);
+
4988
+
4989 if (fuse_create_context_key() == -1)
+
4990 goto out_free;
+
4991
+
4992 fs = fuse_fs_new(op, op_size, user_data);
+
4993 if (!fs)
+
4994 goto out_delete_context_key;
+
4995
+
4996 f->fs = fs;
+
4997
+
4998 /* Oh f**k, this is ugly! */
+
4999 if (!fs->op.lock) {
+
5000 llop.getlk = NULL;
+
5001 llop.setlk = NULL;
+
5002 }
+
5003
+
5004 f->pagesize = getpagesize();
+
5005 init_list_head(&f->partial_slabs);
+
5006 init_list_head(&f->full_slabs);
+
5007 init_list_head(&f->lru_table);
+
5008
+
5009 if (f->conf.modules) {
+
5010 char *module;
+
5011 char *next;
+
5012
+
5013 for (module = f->conf.modules; module; module = next) {
+
5014 char *p;
+
5015 for (p = module; *p && *p != ':'; p++);
+
5016 next = *p ? p + 1 : NULL;
+
5017 *p = '\0';
+
5018 if (module[0] &&
+
5019 fuse_push_module(f, module, args) == -1)
+
5020 goto out_free_fs;
+
5021 }
+
5022 }
+
5023
+
5024 if (!f->conf.ac_attr_timeout_set)
+
5025 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5026
+
5027#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5028 /*
+
5029 * In FreeBSD, we always use these settings as inode numbers
+
5030 * are needed to make getcwd(3) work.
+
5031 */
+
5032 f->conf.readdir_ino = 1;
+
5033#endif
+
5034
+
5035 /* not declared globally, to restrict usage of this function */
+
5036 struct fuse_session *fuse_session_new_versioned(
+
5037 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5038 size_t op_size, struct libfuse_version *version,
+
5039 void *userdata);
+
5040 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5041 f);
+
5042 if (f->se == NULL)
+
5043 goto out_free_fs;
+
5044
+
5045 if (f->conf.debug) {
+
5046 fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok);
+
5047 }
+
5048
+
5049 /* Trace topmost layer by default */
+
5050 f->fs->debug = f->conf.debug;
+
5051 f->ctr = 0;
+
5052 f->generation = 0;
+
5053 if (node_table_init(&f->name_table) == -1)
+
5054 goto out_free_session;
+
5055
+
5056 if (node_table_init(&f->id_table) == -1)
+
5057 goto out_free_name_table;
+
5058
+
5059 pthread_mutex_init(&f->lock, NULL);
+
5060
+
5061 root = alloc_node(f);
+
5062 if (root == NULL) {
+
5063 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5064 goto out_free_id_table;
+
5065 }
+
5066 if (lru_enabled(f)) {
+
5067 struct node_lru *lnode = node_lru(root);
+
5068 init_list_head(&lnode->lru);
+
5069 }
+
5070
+
5071 strcpy(root->inline_name, "/");
+
5072 root->name = root->inline_name;
+
5073 root->parent = NULL;
+
5074 root->nodeid = FUSE_ROOT_ID;
+
5075 inc_nlookup(root);
+
5076 hash_id(f, root);
+
5077
+
5078 return f;
+
5079
+
5080out_free_id_table:
+
5081 free(f->id_table.array);
+
5082out_free_name_table:
+
5083 free(f->name_table.array);
+
5084out_free_session:
+
5085 fuse_session_destroy(f->se);
+
5086out_free_fs:
+
5087 free(f->fs);
+
5088 free(f->conf.modules);
+
5089out_delete_context_key:
+
5090 fuse_delete_context_key();
+
5091out_free:
+
5092 free(f);
+
5093out:
+
5094 return NULL;
+
5095}
+
5096
+
5097/* Emulates 3.0-style fuse_new(), which processes --help */
+
5098FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5099struct fuse *_fuse_new_30(struct fuse_args *args,
+
5100 const struct fuse_operations *op,
+
5101 size_t op_size,
+
5102 struct libfuse_version *version,
+
5103 void *user_data)
+
5104{
+
5105 struct fuse_config conf = {0};
+
5106
+
5107 const struct fuse_opt opts[] = {
+
5108 FUSE_LIB_OPT("-h", show_help, 1),
+
5109 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5111 };
+
5112
+
5113 if (fuse_opt_parse(args, &conf, opts,
+
5114 fuse_lib_opt_proc) == -1)
+
5115 return NULL;
+
5116
+
5117 if (conf.show_help) {
+
5118 fuse_lib_help(args);
+
5119 return NULL;
+
5120 } else
+
5121 return _fuse_new_31(args, op, op_size, version, user_data);
+
5122}
+
5123
+
5124/* ABI compat version */
+
5125struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5126 size_t op_size, void *user_data);
+
5127FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5128struct fuse *fuse_new_31(struct fuse_args *args,
+
5129 const struct fuse_operations *op,
+
5130 size_t op_size, void *user_data)
+
5131{
+
5132 /* unknown version */
+
5133 struct libfuse_version version = { 0 };
+
5134
+
5135 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5136}
+
5137
+
5138/*
+
5139 * ABI compat version
+
5140 * Emulates 3.0-style fuse_new(), which processes --help
+
5141 */
+
5142struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5143 size_t op_size, void *user_data);
+
5144FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5145struct fuse *fuse_new_30(struct fuse_args *args,
+
5146 const struct fuse_operations *op,
+
5147 size_t op_size, void *user_data)
+
5148{
+
5149 struct fuse_config conf = {0};
+
5150
+
5151 const struct fuse_opt opts[] = {
+
5152 FUSE_LIB_OPT("-h", show_help, 1),
+
5153 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5155 };
+
5156
+
5157 if (fuse_opt_parse(args, &conf, opts,
+
5158 fuse_lib_opt_proc) == -1)
+
5159 return NULL;
+
5160
+
5161 if (conf.show_help) {
+
5162 fuse_lib_help(args);
+
5163 return NULL;
+
5164 } else
+
5165 return fuse_new_31(args, op, op_size, user_data);
+
5166}
+
5167
+
5168
+
+
5169void fuse_destroy(struct fuse *f)
+
5170{
+
5171 size_t i;
+
5172
+
5173 if (f->conf.intr && f->intr_installed)
+
5174 fuse_restore_intr_signal(f->conf.intr_signal);
+
5175
+
5176 if (f->fs) {
+
5177 fuse_create_context(f);
+
5178
+
5179 for (i = 0; i < f->id_table.size; i++) {
+
5180 struct node *node;
+
5181
+
5182 for (node = f->id_table.array[i]; node != NULL;
+
5183 node = node->id_next) {
+
5184 if (node->is_hidden) {
+
5185 char *path;
+
5186 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5187 fuse_fs_unlink(f->fs, path);
+
5188 free(path);
+
5189 }
+
5190 }
+
5191 }
+
5192 }
+
5193 }
+
5194 for (i = 0; i < f->id_table.size; i++) {
+
5195 struct node *node;
+
5196 struct node *next;
+
5197
+
5198 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5199 next = node->id_next;
+
5200 free_node(f, node);
+
5201 f->id_table.use--;
+
5202 }
+
5203 }
+
5204 assert(list_empty(&f->partial_slabs));
+
5205 assert(list_empty(&f->full_slabs));
+
5206
+
5207 while (fuse_modules) {
+
5208 fuse_put_module(fuse_modules);
+
5209 }
+
5210 free(f->id_table.array);
+
5211 free(f->name_table.array);
+
5212 pthread_mutex_destroy(&f->lock);
+
5213 fuse_session_destroy(f->se);
+
5214 free(f->fs);
+
5215 free(f->conf.modules);
+
5216 free(f);
+
5217 fuse_delete_context_key();
+
5218}
+
+
5219
+
+
5220int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5221 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5222}
+
+
5223
+
5224
+
+
5225void fuse_unmount(struct fuse *f) {
+ +
5227}
+
+
5228
+
+ +
5230{
+
5231 return FUSE_VERSION;
+
5232}
+
+
5233
+
+
5234const char *fuse_pkgversion(void)
+
5235{
+
5236 return PACKAGE_VERSION;
+
5237}
+
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4679
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
int fuse_interrupted(void)
Definition fuse.c:4688
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4929
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1386
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4664
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4458
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4937
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
int fuse_version(void)
Definition fuse.c:5229
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ + +
uint32_t no_interrupt
+
uint64_t want_ext
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__i_8h_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..689f8ee --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__i_8h_source.html @@ -0,0 +1,285 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include "fuse.h"
+
10#include "fuse_lowlevel.h"
+
11#include "util.h"
+
12
+
13#include <stdint.h>
+
14#include <stdbool.h>
+
15#include <errno.h>
+
16
+
17#define MIN(a, b) \
+
18({ \
+
19 typeof(a) _a = (a); \
+
20 typeof(b) _b = (b); \
+
21 _a < _b ? _a : _b; \
+
22})
+
23
+
24struct mount_opts;
+
25
+
26struct fuse_req {
+
27 struct fuse_session *se;
+
28 uint64_t unique;
+
29 _Atomic int ref_cnt;
+
30 pthread_mutex_t lock;
+
31 struct fuse_ctx ctx;
+
32 struct fuse_chan *ch;
+
33 int interrupted;
+
34 unsigned int ioctl_64bit : 1;
+
35 union {
+
36 struct {
+
37 uint64_t unique;
+
38 } i;
+
39 struct {
+ +
41 void *data;
+
42 } ni;
+
43 } u;
+
44 struct fuse_req *next;
+
45 struct fuse_req *prev;
+
46};
+
47
+
48struct fuse_notify_req {
+
49 uint64_t unique;
+
50 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
51 const void *, const struct fuse_buf *);
+
52 struct fuse_notify_req *next;
+
53 struct fuse_notify_req *prev;
+
54};
+
55
+
56struct fuse_session {
+
57 char *mountpoint;
+
58 volatile int exited;
+
59 int fd;
+
60 struct fuse_custom_io *io;
+
61 struct mount_opts *mo;
+
62 int debug;
+
63 int deny_others;
+
64 struct fuse_lowlevel_ops op;
+
65 int got_init;
+
66 struct cuse_data *cuse_data;
+
67 void *userdata;
+
68 uid_t owner;
+
69 struct fuse_conn_info conn;
+
70 struct fuse_req list;
+
71 struct fuse_req interrupts;
+
72 pthread_mutex_t lock;
+
73 int got_destroy;
+
74 pthread_key_t pipe_key;
+
75 int broken_splice_nonblock;
+
76 uint64_t notify_ctr;
+
77 struct fuse_notify_req notify_list;
+
78 size_t bufsize;
+
79 int error;
+
80
+
81 /* This is useful if any kind of ABI incompatibility is found at
+
82 * a later version, to 'fix' it at run time.
+
83 */
+
84 struct libfuse_version version;
+
85 bool buf_reallocable;
+
86};
+
87
+
88struct fuse_chan {
+
89 pthread_mutex_t lock;
+
90 int ctr;
+
91 int fd;
+
92};
+
93
+
+ +
102 char *name;
+
103 fuse_module_factory_t factory;
+
104 struct fuse_module *next;
+
105 struct fusemod_so *so;
+
106 int ctr;
+
107};
+
+
108
+
117#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
118struct fuse_loop_config
+
119{
+
120 /* verififier that a correct struct was was passed. This is especially
+
121 * needed, as versions below (3, 12) were using a public struct
+
122 * (now called fuse_loop_config_v1), which was hard to extend with
+
123 * additional parameters, without risking that file system implementations
+
124 * would not have noticed and might either pass uninitialized members
+
125 * or even too small structs.
+
126 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
127 * or 1. v2 or even higher version just need to set a value here
+
128 * which not conflicting and very unlikely as having been set by
+
129 * file system implementation.
+
130 */
+
131 int version_id;
+
132
+
137 int clone_fd;
+ +
150
+
156 unsigned int max_threads;
+
157};
+
158#endif
+
159
+
160/* ----------------------------------------------------------- *
+
161 * Channel interface (when using -o clone_fd) *
+
162 * ----------------------------------------------------------- */
+
163
+
170struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
171
+
177void fuse_chan_put(struct fuse_chan *ch);
+
178
+
179struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
180void destroy_mount_opts(struct mount_opts *mo);
+
181void fuse_mount_version(void);
+
182unsigned get_max_read(struct mount_opts *o);
+
183void fuse_kern_unmount(const char *mountpoint, int fd);
+
184int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
185
+
186int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
187 int count);
+
188void fuse_free_req(fuse_req_t req);
+
189
+
190void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
191
+
192int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
193
+
194void fuse_buf_free(struct fuse_buf *buf);
+
195
+
196int fuse_session_receive_buf_internal(struct fuse_session *se,
+
197 struct fuse_buf *buf,
+
198 struct fuse_chan *ch);
+
199void fuse_session_process_buf_internal(struct fuse_session *se,
+
200 const struct fuse_buf *buf,
+
201 struct fuse_chan *ch);
+
202
+
203struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
204 size_t op_size, void *private_data);
+
205int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
206int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
207
+
213int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
214
+
215
+
216/*
+
217 * This can be changed dynamically on recent kernels through the
+
218 * /proc/sys/fs/fuse/max_pages_limit interface.
+
219 *
+
220 * Older kernels will always use the default value.
+
221 */
+
222#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
223#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
224
+
225/* room needed in buffer to accommodate header */
+
226#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
227
+
231static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn,
+
232 uint64_t want_ext_default,
+
233 uint32_t want_default)
+
234{
+
235 /*
+
236 * Convert want to want_ext if necessary.
+
237 * For the high level interface this function might be called
+
238 * twice, once from the high level interface and once from the
+
239 * low level interface. Both, with different want_ext_default and
+
240 * want_default values. In order to suppress a failure for the
+
241 * second call, we check if the lower 32 bits of want_ext are
+
242 * already set to the value of want.
+
243 */
+
244 if (conn->want != want_default &&
+
245 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
246 if (conn->want_ext != want_ext_default) {
+
247 fuse_log(FUSE_LOG_ERR,
+
248 "fuse: both 'want' and 'want_ext' are set\n");
+
249 return -EINVAL;
+
250 }
+
251
+
252 /* high bits from want_ext, low bits from want */
+
253 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
254 conn->want;
+
255 }
+
256
+
257 return 0;
+
258}
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1386
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + +
uint64_t want_ext
+ + + +
unsigned int max_threads
Definition fuse_i.h:156
+
int max_idle_threads
Definition fuse_i.h:149
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__log_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..7bed673 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__log_8c_source.html @@ -0,0 +1,170 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdarg.h>
+
14#include <stdio.h>
+
15#include <stdbool.h>
+
16#include <syslog.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(__attribute__((unused)) enum fuse_log_level level,
+
23 const char *fmt, va_list ap)
+
24{
+
25 if (to_syslog) {
+
26 int sys_log_level = LOG_ERR;
+
27
+
28 /*
+
29 * with glibc fuse_log_level has identical values as
+
30 * syslog levels, but we also support BSD - better we convert to
+
31 * be sure.
+
32 */
+
33 switch (level) {
+
34 case FUSE_LOG_DEBUG:
+
35 sys_log_level = LOG_DEBUG;
+
36 break;
+
37 case FUSE_LOG_INFO:
+
38 sys_log_level = LOG_INFO;
+
39 break;
+
40 case FUSE_LOG_NOTICE:
+
41 sys_log_level = LOG_NOTICE;
+
42 break;
+
43 case FUSE_LOG_WARNING:
+
44 sys_log_level = LOG_WARNING;
+
45 break;
+
46 case FUSE_LOG_ERR:
+
47 sys_log_level = LOG_ERR;
+
48 break;
+
49 case FUSE_LOG_CRIT:
+
50 sys_log_level = LOG_CRIT;
+
51 break;
+
52 case FUSE_LOG_ALERT:
+
53 sys_log_level = LOG_ALERT;
+
54 break;
+
55 case FUSE_LOG_EMERG:
+
56 sys_log_level = LOG_EMERG;
+
57 }
+
58
+
59 char log[MAX_SYSLOG_LINE_LEN];
+
60 vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap);
+
61 syslog(sys_log_level, "%s", log);
+
62 } else {
+
63 vfprintf(stderr, fmt, ap);
+
64 }
+
65}
+
66
+
67static fuse_log_func_t log_func = default_log_func;
+
68
+
+ +
70{
+
71 if (!func)
+
72 func = default_log_func;
+
73
+
74 log_func = func;
+
75}
+
+
76
+
+
77void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
78{
+
79 va_list ap;
+
80
+
81 va_start(ap, fmt);
+
82 log_func(level, fmt, ap);
+
83 va_end(ap);
+
84}
+
+
85
+
+
86void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
87{
+
88 to_syslog = true;
+
89
+
90 openlog(ident, option, facility);
+
91}
+
+
92
+
+ +
94{
+
95 closelog();
+
96}
+
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..a489053 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop_8c_source.html @@ -0,0 +1,114 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+ +
45 return res;
+
46}
+
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_reset(struct fuse_session *se)
+ +
void * mem
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop__mt_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..f6a2dd0 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,599 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <string.h>
+
23#include <unistd.h>
+
24#include <signal.h>
+
25#include <semaphore.h>
+
26#include <errno.h>
+
27#include <sys/time.h>
+
28#include <sys/ioctl.h>
+
29#include <assert.h>
+
30#include <limits.h>
+
31
+
32/* Environment var controlling the thread stack size */
+
33#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
34
+
35#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
36#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
37#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
38#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
39 * by default */
+
40
+
41/* an arbitrary large value that cannot be valid */
+
42#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
43
+
44struct fuse_worker {
+
45 struct fuse_worker *prev;
+
46 struct fuse_worker *next;
+
47 pthread_t thread_id;
+
48
+
49 // We need to include fuse_buf so that we can properly free
+
50 // it when a thread is terminated by pthread_cancel().
+
51 struct fuse_buf fbuf;
+
52 struct fuse_chan *ch;
+
53 struct fuse_mt *mt;
+
54};
+
55
+
56struct fuse_mt {
+
57 pthread_mutex_t lock;
+
58 int numworker;
+
59 int numavail;
+
60 struct fuse_session *se;
+
61 struct fuse_worker main;
+
62 sem_t finish;
+
63 int exit;
+
64 int error;
+
65 int clone_fd;
+
66 int max_idle;
+
67 int max_threads;
+
68};
+
69
+
70static struct fuse_chan *fuse_chan_new(int fd)
+
71{
+
72 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
73 if (ch == NULL) {
+
74 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
75 return NULL;
+
76 }
+
77
+
78 memset(ch, 0, sizeof(*ch));
+
79 ch->fd = fd;
+
80 ch->ctr = 1;
+
81 pthread_mutex_init(&ch->lock, NULL);
+
82
+
83 return ch;
+
84}
+
85
+
86struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
87{
+
88 assert(ch->ctr > 0);
+
89 pthread_mutex_lock(&ch->lock);
+
90 ch->ctr++;
+
91 pthread_mutex_unlock(&ch->lock);
+
92
+
93 return ch;
+
94}
+
95
+
96void fuse_chan_put(struct fuse_chan *ch)
+
97{
+
98 if (ch == NULL)
+
99 return;
+
100 pthread_mutex_lock(&ch->lock);
+
101 ch->ctr--;
+
102 if (!ch->ctr) {
+
103 pthread_mutex_unlock(&ch->lock);
+
104 close(ch->fd);
+
105 pthread_mutex_destroy(&ch->lock);
+
106 free(ch);
+
107 } else
+
108 pthread_mutex_unlock(&ch->lock);
+
109}
+
110
+
111static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
112{
+
113 struct fuse_worker *prev = next->prev;
+
114 w->next = next;
+
115 w->prev = prev;
+
116 prev->next = w;
+
117 next->prev = w;
+
118}
+
119
+
120static void list_del_worker(struct fuse_worker *w)
+
121{
+
122 struct fuse_worker *prev = w->prev;
+
123 struct fuse_worker *next = w->next;
+
124 prev->next = next;
+
125 next->prev = prev;
+
126}
+
127
+
128static int fuse_loop_start_thread(struct fuse_mt *mt);
+
129
+
130static void *fuse_do_work(void *data)
+
131{
+
132 struct fuse_worker *w = (struct fuse_worker *) data;
+
133 struct fuse_mt *mt = w->mt;
+
134
+
135 pthread_setname_np(pthread_self(), "fuse_worker");
+
136
+
137 while (!fuse_session_exited(mt->se)) {
+
138 int isforget = 0;
+
139 int res;
+
140
+
141 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
142 res = fuse_session_receive_buf_internal(mt->se, &w->fbuf,
+
143 w->ch);
+
144 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
145 if (res == -EINTR)
+
146 continue;
+
147 if (res <= 0) {
+
148 if (res < 0) {
+
149 fuse_session_exit(mt->se);
+
150 mt->error = res;
+
151 }
+
152 break;
+
153 }
+
154
+
155 pthread_mutex_lock(&mt->lock);
+
156 if (mt->exit) {
+
157 pthread_mutex_unlock(&mt->lock);
+
158 return NULL;
+
159 }
+
160
+
161 /*
+
162 * This disgusting hack is needed so that zillions of threads
+
163 * are not created on a burst of FORGET messages
+
164 */
+
165 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
166 struct fuse_in_header *in = w->fbuf.mem;
+
167
+
168 if (in->opcode == FUSE_FORGET ||
+
169 in->opcode == FUSE_BATCH_FORGET)
+
170 isforget = 1;
+
171 }
+
172
+
173 if (!isforget)
+
174 mt->numavail--;
+
175 if (mt->numavail == 0 && mt->numworker < mt->max_threads)
+
176 fuse_loop_start_thread(mt);
+
177 pthread_mutex_unlock(&mt->lock);
+
178
+
179 fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch);
+
180
+
181 pthread_mutex_lock(&mt->lock);
+
182 if (!isforget)
+
183 mt->numavail++;
+
184
+
185 /* creating and destroying threads is rather expensive - and there is
+
186 * not much gain from destroying existing threads. It is therefore
+
187 * discouraged to set max_idle to anything else than -1. If there
+
188 * is indeed a good reason to destruct threads it should be done
+
189 * delayed, a moving average might be useful for that.
+
190 */
+
191 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
192 if (mt->exit) {
+
193 pthread_mutex_unlock(&mt->lock);
+
194 return NULL;
+
195 }
+
196 list_del_worker(w);
+
197 mt->numavail--;
+
198 mt->numworker--;
+
199 pthread_mutex_unlock(&mt->lock);
+
200
+
201 pthread_detach(w->thread_id);
+
202 fuse_buf_free(&w->fbuf);
+
203 fuse_chan_put(w->ch);
+
204 free(w);
+
205 return NULL;
+
206 }
+
207 pthread_mutex_unlock(&mt->lock);
+
208 }
+
209
+
210 sem_post(&mt->finish);
+
211
+
212 return NULL;
+
213}
+
214
+
215int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
216{
+
217 sigset_t oldset;
+
218 sigset_t newset;
+
219 int res;
+
220 pthread_attr_t attr;
+
221 char *stack_size;
+
222
+
223 /* Override default stack size
+
224 * XXX: This should ideally be a parameter option. It is rather
+
225 * well hidden here.
+
226 */
+
227 pthread_attr_init(&attr);
+
228 stack_size = getenv(ENVNAME_THREAD_STACK);
+
229 if (stack_size) {
+
230 long size;
+
231
+
232 res = libfuse_strtol(stack_size, &size);
+
233 if (res)
+
234 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
235 stack_size);
+
236 else if (pthread_attr_setstacksize(&attr, size))
+
237 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
238 size);
+
239 }
+
240
+
241 /* Disallow signal reception in worker threads */
+
242 sigemptyset(&newset);
+
243 sigaddset(&newset, SIGTERM);
+
244 sigaddset(&newset, SIGINT);
+
245 sigaddset(&newset, SIGHUP);
+
246 sigaddset(&newset, SIGQUIT);
+
247 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
248 res = pthread_create(thread_id, &attr, func, arg);
+
249 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
250 pthread_attr_destroy(&attr);
+
251 if (res != 0) {
+
252 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
253 strerror(res));
+
254 return -1;
+
255 }
+
256
+
257 return 0;
+
258}
+
259
+
260static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
261{
+
262 int res;
+
263 int clonefd;
+
264 uint32_t masterfd;
+
265 const char *devname = "/dev/fuse";
+
266
+
267#ifndef O_CLOEXEC
+
268#define O_CLOEXEC 0
+
269#endif
+
270 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
271 if (clonefd == -1) {
+
272 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
273 strerror(errno));
+
274 return -1;
+
275 }
+
276#ifndef O_CLOEXEC
+
277 fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
278#endif
+
279
+
280 masterfd = se->fd;
+
281 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
282 if (res == -1) {
+
283 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
284 strerror(errno));
+
285 close(clonefd);
+
286 return -1;
+
287 }
+
288 return clonefd;
+
289}
+
290
+
291static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
292{
+
293 int clonefd;
+
294 struct fuse_session *se = mt->se;
+
295 struct fuse_chan *newch;
+
296
+
297 if (se->io != NULL) {
+
298 if (se->io->clone_fd != NULL)
+
299 clonefd = se->io->clone_fd(se->fd);
+
300 else
+
301 return NULL;
+
302 } else {
+
303 clonefd = fuse_clone_chan_fd_default(se);
+
304 }
+
305 if (clonefd < 0)
+
306 return NULL;
+
307
+
308 newch = fuse_chan_new(clonefd);
+
309 if (newch == NULL)
+
310 close(clonefd);
+
311
+
312 return newch;
+
313}
+
314
+
315static int fuse_loop_start_thread(struct fuse_mt *mt)
+
316{
+
317 int res;
+
318
+
319 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
320 if (!w) {
+
321 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
322 return -1;
+
323 }
+
324 memset(w, 0, sizeof(struct fuse_worker));
+
325 w->fbuf.mem = NULL;
+
326 w->mt = mt;
+
327
+
328 w->ch = NULL;
+
329 if (mt->clone_fd) {
+
330 w->ch = fuse_clone_chan(mt);
+
331 if(!w->ch) {
+
332 /* Don't attempt this again */
+
333 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
334 "without -o clone_fd.\n");
+
335 mt->clone_fd = 0;
+
336 }
+
337 }
+
338
+
339 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
340 if (res == -1) {
+
341 fuse_chan_put(w->ch);
+
342 free(w);
+
343 return -1;
+
344 }
+
345 list_add_worker(w, &mt->main);
+
346 mt->numavail ++;
+
347 mt->numworker ++;
+
348
+
349 return 0;
+
350}
+
351
+
352static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
353{
+
354 pthread_join(w->thread_id, NULL);
+
355 pthread_mutex_lock(&mt->lock);
+
356 list_del_worker(w);
+
357 pthread_mutex_unlock(&mt->lock);
+
358 fuse_buf_free(&w->fbuf);
+
359 fuse_chan_put(w->ch);
+
360 free(w);
+
361}
+
362
+
363int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
364FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
365int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
366{
+
367int err;
+
368 struct fuse_mt mt;
+
369 struct fuse_worker *w;
+
370 int created_config = 0;
+
371
+
372 if (config) {
+
373 err = fuse_loop_cfg_verify(config);
+
374 if (err)
+
375 return err;
+
376 } else {
+
377 /* The caller does not care about parameters - use the default */
+
378 config = fuse_loop_cfg_create();
+
379 created_config = 1;
+
380 }
+
381
+
382
+
383 memset(&mt, 0, sizeof(struct fuse_mt));
+
384 mt.se = se;
+
385 mt.clone_fd = config->clone_fd;
+
386 mt.error = 0;
+
387 mt.numworker = 0;
+
388 mt.numavail = 0;
+
389 mt.max_idle = config->max_idle_threads;
+
390 mt.max_threads = config->max_threads;
+
391 mt.main.thread_id = pthread_self();
+
392 mt.main.prev = mt.main.next = &mt.main;
+
393 sem_init(&mt.finish, 0, 0);
+
394 pthread_mutex_init(&mt.lock, NULL);
+
395
+
396 pthread_mutex_lock(&mt.lock);
+
397 err = fuse_loop_start_thread(&mt);
+
398 pthread_mutex_unlock(&mt.lock);
+
399 if (!err) {
+
400 /* sem_wait() is interruptible */
+
401 while (!fuse_session_exited(se))
+
402 sem_wait(&mt.finish);
+
403
+
404 pthread_mutex_lock(&mt.lock);
+
405 for (w = mt.main.next; w != &mt.main; w = w->next)
+
406 pthread_cancel(w->thread_id);
+
407 mt.exit = 1;
+
408 pthread_mutex_unlock(&mt.lock);
+
409
+
410 while (mt.main.next != &mt.main)
+
411 fuse_join_worker(&mt, mt.main.next);
+
412
+
413 err = mt.error;
+
414 }
+
415
+
416 pthread_mutex_destroy(&mt.lock);
+
417 sem_destroy(&mt.finish);
+
418 if(se->error != 0)
+
419 err = se->error;
+ +
421
+
422 if (created_config) {
+
423 fuse_loop_cfg_destroy(config);
+
424 config = NULL;
+
425 }
+
426
+
427 return err;
+
428}
+
429
+
430int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
431FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
432int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
433{
+
434 int err;
+
435 struct fuse_loop_config *config = NULL;
+
436
+
437 if (config_v1 != NULL) {
+
438 /* convert the given v1 config */
+
439 config = fuse_loop_cfg_create();
+
440 if (config == NULL)
+
441 return ENOMEM;
+
442
+
443 fuse_loop_cfg_convert(config, config_v1);
+
444 }
+
445
+
446 err = fuse_session_loop_mt_312(se, config);
+
447
+
448 fuse_loop_cfg_destroy(config);
+
449
+
450 return err;
+
451}
+
452
+
453
+
454int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
455FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
456int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
457{
+
458 int err;
+
459 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
460 if (clone_fd > 0)
+
461 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
462 err = fuse_session_loop_mt_312(se, config);
+
463
+
464 fuse_loop_cfg_destroy(config);
+
465
+
466 return err;
+
467}
+
468
+
469struct fuse_loop_config *fuse_loop_cfg_create(void)
+
470{
+
471 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
472 if (config == NULL)
+
473 return NULL;
+
474
+
475 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
476 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
477 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
478 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
479
+
480 return config;
+
481}
+
482
+
483void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
484{
+
485 free(config);
+
486}
+
487
+
488int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
489{
+
490 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
491 return -EINVAL;
+
492
+
493 return 0;
+
494}
+
495
+
496void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
497 struct fuse_loop_config_v1 *v1_conf)
+
498{
+
499 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
500
+
501 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
502}
+
503
+
504void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
505 unsigned int value)
+
506{
+
507 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
508 if (value != UINT_MAX)
+
509 fuse_log(FUSE_LOG_ERR,
+
510 "Ignoring invalid max threads value "
+
511 "%u > max (%u).\n", value,
+
512 FUSE_LOOP_MT_MAX_THREADS);
+
513 return;
+
514 }
+
515 config->max_idle_threads = value;
+
516}
+
517
+
518void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
519 unsigned int value)
+
520{
+
521 config->max_threads = value;
+
522}
+
523
+
524void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
525 unsigned int value)
+
526{
+
527 config->clone_fd = value;
+
528}
+
529
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_reset(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:156
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__lowlevel_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..a5d63eb --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,3821 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21
+
22#include <stdint.h>
+
23#include <stdbool.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <stddef.h>
+
27#include <stdalign.h>
+
28#include <string.h>
+
29#include <unistd.h>
+
30#include <limits.h>
+
31#include <errno.h>
+
32#include <assert.h>
+
33#include <sys/file.h>
+
34#include <sys/ioctl.h>
+
35
+
36#ifndef F_LINUX_SPECIFIC_BASE
+
37#define F_LINUX_SPECIFIC_BASE 1024
+
38#endif
+
39#ifndef F_SETPIPE_SZ
+
40#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
41#endif
+
42
+
43
+
44#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
45#define OFFSET_MAX 0x7fffffffffffffffLL
+
46
+
47#define container_of(ptr, type, member) ({ \
+
48 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
49 (type *)( (char *)__mptr - offsetof(type,member) );})
+
50
+
51struct fuse_pollhandle {
+
52 uint64_t kh;
+
53 struct fuse_session *se;
+
54};
+
55
+
56static size_t pagesize;
+
57
+
58static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
59{
+
60 pagesize = getpagesize();
+
61}
+
62
+
63static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
64{
+
65 attr->ino = stbuf->st_ino;
+
66 attr->mode = stbuf->st_mode;
+
67 attr->nlink = stbuf->st_nlink;
+
68 attr->uid = stbuf->st_uid;
+
69 attr->gid = stbuf->st_gid;
+
70 attr->rdev = stbuf->st_rdev;
+
71 attr->size = stbuf->st_size;
+
72 attr->blksize = stbuf->st_blksize;
+
73 attr->blocks = stbuf->st_blocks;
+
74 attr->atime = stbuf->st_atime;
+
75 attr->mtime = stbuf->st_mtime;
+
76 attr->ctime = stbuf->st_ctime;
+
77 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
78 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
79 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
80}
+
81
+
82static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
83{
+
84 stbuf->st_mode = attr->mode;
+
85 stbuf->st_uid = attr->uid;
+
86 stbuf->st_gid = attr->gid;
+
87 stbuf->st_size = attr->size;
+
88 stbuf->st_atime = attr->atime;
+
89 stbuf->st_mtime = attr->mtime;
+
90 stbuf->st_ctime = attr->ctime;
+
91 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
92 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
93 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
94}
+
95
+
96static size_t iov_length(const struct iovec *iov, size_t count)
+
97{
+
98 size_t seg;
+
99 size_t ret = 0;
+
100
+
101 for (seg = 0; seg < count; seg++)
+
102 ret += iov[seg].iov_len;
+
103 return ret;
+
104}
+
105
+
106static void list_init_req(struct fuse_req *req)
+
107{
+
108 req->next = req;
+
109 req->prev = req;
+
110}
+
111
+
112static void list_del_req(struct fuse_req *req)
+
113{
+
114 struct fuse_req *prev = req->prev;
+
115 struct fuse_req *next = req->next;
+
116 prev->next = next;
+
117 next->prev = prev;
+
118}
+
119
+
120static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
121{
+
122 struct fuse_req *prev = next->prev;
+
123 req->next = next;
+
124 req->prev = prev;
+
125 prev->next = req;
+
126 next->prev = req;
+
127}
+
128
+
129static void destroy_req(fuse_req_t req)
+
130{
+
131 assert(req->ch == NULL);
+
132 pthread_mutex_destroy(&req->lock);
+
133 free(req);
+
134}
+
135
+
136void fuse_free_req(fuse_req_t req)
+
137{
+
138 int ctr;
+
139 struct fuse_session *se = req->se;
+
140
+
141 if (se->conn.no_interrupt) {
+
142 ctr = --req->ref_cnt;
+
143 fuse_chan_put(req->ch);
+
144 req->ch = NULL;
+
145 } else {
+
146 pthread_mutex_lock(&se->lock);
+
147 req->u.ni.func = NULL;
+
148 req->u.ni.data = NULL;
+
149 list_del_req(req);
+
150 ctr = --req->ref_cnt;
+
151 fuse_chan_put(req->ch);
+
152 req->ch = NULL;
+
153 pthread_mutex_unlock(&se->lock);
+
154 }
+
155 if (!ctr)
+
156 destroy_req(req);
+
157}
+
158
+
159static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
160{
+
161 struct fuse_req *req;
+
162
+
163 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
164 if (req == NULL) {
+
165 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
166 } else {
+
167 req->se = se;
+
168 req->ref_cnt = 1;
+
169 list_init_req(req);
+
170 pthread_mutex_init(&req->lock, NULL);
+
171 }
+
172
+
173 return req;
+
174}
+
175
+
176/* Send data. If *ch* is NULL, send via session master fd */
+
177static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
178 struct iovec *iov, int count)
+
179{
+
180 struct fuse_out_header *out = iov[0].iov_base;
+
181
+
182 assert(se != NULL);
+
183 out->len = iov_length(iov, count);
+
184 if (se->debug) {
+
185 if (out->unique == 0) {
+
186 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
187 out->error, out->len);
+
188 } else if (out->error) {
+
189 fuse_log(FUSE_LOG_DEBUG,
+
190 " unique: %llu, error: %i (%s), outsize: %i\n",
+
191 (unsigned long long) out->unique, out->error,
+
192 strerror(-out->error), out->len);
+
193 } else {
+
194 fuse_log(FUSE_LOG_DEBUG,
+
195 " unique: %llu, success, outsize: %i\n",
+
196 (unsigned long long) out->unique, out->len);
+
197 }
+
198 }
+
199
+
200 ssize_t res;
+
201 if (se->io != NULL)
+
202 /* se->io->writev is never NULL if se->io is not NULL as
+
203 specified by fuse_session_custom_io()*/
+
204 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
205 se->userdata);
+
206 else
+
207 res = writev(ch ? ch->fd : se->fd, iov, count);
+
208
+
209 int err = errno;
+
210
+
211 if (res == -1) {
+
212 /* ENOENT means the operation was interrupted */
+
213 if (!fuse_session_exited(se) && err != ENOENT)
+
214 perror("fuse: writing device");
+
215 return -err;
+
216 }
+
217
+
218 return 0;
+
219}
+
220
+
221
+
222int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
223 int count)
+
224{
+
225 struct fuse_out_header out;
+
226
+
227#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
228 const char *str = strerrordesc_np(error * -1);
+
229 if ((str == NULL && error != 0) || error > 0) {
+
230#else
+
231 if (error <= -1000 || error > 0) {
+
232#endif
+
233 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
234 error = -ERANGE;
+
235 }
+
236
+
237 out.unique = req->unique;
+
238 out.error = error;
+
239
+
240 iov[0].iov_base = &out;
+
241 iov[0].iov_len = sizeof(struct fuse_out_header);
+
242
+
243 return fuse_send_msg(req->se, req->ch, iov, count);
+
244}
+
245
+
246static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
247 int count)
+
248{
+
249 int res;
+
250
+
251 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
252 fuse_free_req(req);
+
253 return res;
+
254}
+
255
+
256static int send_reply(fuse_req_t req, int error, const void *arg,
+
257 size_t argsize)
+
258{
+
259 struct iovec iov[2];
+
260 int count = 1;
+
261 if (argsize) {
+
262 iov[1].iov_base = (void *) arg;
+
263 iov[1].iov_len = argsize;
+
264 count++;
+
265 }
+
266 return send_reply_iov(req, error, iov, count);
+
267}
+
268
+
+
269int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
270{
+
271 int res;
+
272 struct iovec *padded_iov;
+
273
+
274 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
275 if (padded_iov == NULL)
+
276 return fuse_reply_err(req, ENOMEM);
+
277
+
278 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
279 count++;
+
280
+
281 res = send_reply_iov(req, 0, padded_iov, count);
+
282 free(padded_iov);
+
283
+
284 return res;
+
285}
+
+
286
+
287
+
288/* `buf` is allowed to be empty so that the proper size may be
+
289 allocated by the caller */
+
+
290size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
291 const char *name, const struct stat *stbuf, off_t off)
+
292{
+
293 (void)req;
+
294 size_t namelen;
+
295 size_t entlen;
+
296 size_t entlen_padded;
+
297 struct fuse_dirent *dirent;
+
298
+
299 namelen = strlen(name);
+
300 entlen = FUSE_NAME_OFFSET + namelen;
+
301 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
302
+
303 if ((buf == NULL) || (entlen_padded > bufsize))
+
304 return entlen_padded;
+
305
+
306 dirent = (struct fuse_dirent*) buf;
+
307 dirent->ino = stbuf->st_ino;
+
308 dirent->off = off;
+
309 dirent->namelen = namelen;
+
310 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
311 memcpy(dirent->name, name, namelen);
+
312 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
313
+
314 return entlen_padded;
+
315}
+
+
316
+
317static void convert_statfs(const struct statvfs *stbuf,
+
318 struct fuse_kstatfs *kstatfs)
+
319{
+
320 kstatfs->bsize = stbuf->f_bsize;
+
321 kstatfs->frsize = stbuf->f_frsize;
+
322 kstatfs->blocks = stbuf->f_blocks;
+
323 kstatfs->bfree = stbuf->f_bfree;
+
324 kstatfs->bavail = stbuf->f_bavail;
+
325 kstatfs->files = stbuf->f_files;
+
326 kstatfs->ffree = stbuf->f_ffree;
+
327 kstatfs->namelen = stbuf->f_namemax;
+
328}
+
329
+
330static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
331{
+
332 return send_reply(req, 0, arg, argsize);
+
333}
+
334
+
+
335int fuse_reply_err(fuse_req_t req, int err)
+
336{
+
337 return send_reply(req, -err, NULL, 0);
+
338}
+
+
339
+
+ +
341{
+
342 fuse_free_req(req);
+
343}
+
+
344
+
345static unsigned long calc_timeout_sec(double t)
+
346{
+
347 if (t > (double) ULONG_MAX)
+
348 return ULONG_MAX;
+
349 else if (t < 0.0)
+
350 return 0;
+
351 else
+
352 return (unsigned long) t;
+
353}
+
354
+
355static unsigned int calc_timeout_nsec(double t)
+
356{
+
357 double f = t - (double) calc_timeout_sec(t);
+
358 if (f < 0.0)
+
359 return 0;
+
360 else if (f >= 0.999999999)
+
361 return 999999999;
+
362 else
+
363 return (unsigned int) (f * 1.0e9);
+
364}
+
365
+
366static void fill_entry(struct fuse_entry_out *arg,
+
367 const struct fuse_entry_param *e)
+
368{
+
369 arg->nodeid = e->ino;
+
370 arg->generation = e->generation;
+
371 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
372 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
373 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
374 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
375 convert_stat(&e->attr, &arg->attr);
+
376}
+
377
+
378/* `buf` is allowed to be empty so that the proper size may be
+
379 allocated by the caller */
+
+
380size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
381 const char *name,
+
382 const struct fuse_entry_param *e, off_t off)
+
383{
+
384 (void)req;
+
385 size_t namelen;
+
386 size_t entlen;
+
387 size_t entlen_padded;
+
388
+
389 namelen = strlen(name);
+
390 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
391 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
392 if ((buf == NULL) || (entlen_padded > bufsize))
+
393 return entlen_padded;
+
394
+
395 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
396 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
397 fill_entry(&dp->entry_out, e);
+
398
+
399 struct fuse_dirent *dirent = &dp->dirent;
+
400 dirent->ino = e->attr.st_ino;
+
401 dirent->off = off;
+
402 dirent->namelen = namelen;
+
403 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
404 memcpy(dirent->name, name, namelen);
+
405 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
406
+
407 return entlen_padded;
+
408}
+
+
409
+
410static void fill_open(struct fuse_open_out *arg,
+
411 const struct fuse_file_info *f)
+
412{
+
413 arg->fh = f->fh;
+
414 if (f->backing_id > 0) {
+
415 arg->backing_id = f->backing_id;
+
416 arg->open_flags |= FOPEN_PASSTHROUGH;
+
417 }
+
418 if (f->direct_io)
+
419 arg->open_flags |= FOPEN_DIRECT_IO;
+
420 if (f->keep_cache)
+
421 arg->open_flags |= FOPEN_KEEP_CACHE;
+
422 if (f->cache_readdir)
+
423 arg->open_flags |= FOPEN_CACHE_DIR;
+
424 if (f->nonseekable)
+
425 arg->open_flags |= FOPEN_NONSEEKABLE;
+
426 if (f->noflush)
+
427 arg->open_flags |= FOPEN_NOFLUSH;
+ +
429 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
430}
+
431
+
+ +
433{
+
434 struct fuse_entry_out arg;
+
435 size_t size = req->se->conn.proto_minor < 9 ?
+
436 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
437
+
438 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
439 negative entry */
+
440 if (!e->ino && req->se->conn.proto_minor < 4)
+
441 return fuse_reply_err(req, ENOENT);
+
442
+
443 memset(&arg, 0, sizeof(arg));
+
444 fill_entry(&arg, e);
+
445 return send_reply_ok(req, &arg, size);
+
446}
+
+
447
+
+ +
449 const struct fuse_file_info *f)
+
450{
+
451 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
452 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
453 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
454 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
455 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
456
+
457 memset(buf, 0, sizeof(buf));
+
458 fill_entry(earg, e);
+
459 fill_open(oarg, f);
+
460 return send_reply_ok(req, buf,
+
461 entrysize + sizeof(struct fuse_open_out));
+
462}
+
+
463
+
+
464int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
465 double attr_timeout)
+
466{
+
467 struct fuse_attr_out arg;
+
468 size_t size = req->se->conn.proto_minor < 9 ?
+
469 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
470
+
471 memset(&arg, 0, sizeof(arg));
+
472 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
473 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
474 convert_stat(attr, &arg.attr);
+
475
+
476 return send_reply_ok(req, &arg, size);
+
477}
+
+
478
+
+
479int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
480{
+
481 return send_reply_ok(req, linkname, strlen(linkname));
+
482}
+
+
483
+
+ +
485{
+
486 struct fuse_backing_map map = { .fd = fd };
+
487 int ret;
+
488
+
489 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
490 if (ret <= 0) {
+
491 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
492 return 0;
+
493 }
+
494
+
495 return ret;
+
496}
+
+
497
+
498int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
499{
+
500 int ret;
+
501
+
502 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
503 if (ret < 0)
+
504 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
505
+
506 return ret;
+
507}
+
508
+
+ +
510{
+
511 struct fuse_open_out arg;
+
512
+
513 memset(&arg, 0, sizeof(arg));
+
514 fill_open(&arg, f);
+
515 return send_reply_ok(req, &arg, sizeof(arg));
+
516}
+
+
517
+
+
518int fuse_reply_write(fuse_req_t req, size_t count)
+
519{
+
520 struct fuse_write_out arg;
+
521
+
522 memset(&arg, 0, sizeof(arg));
+
523 arg.size = count;
+
524
+
525 return send_reply_ok(req, &arg, sizeof(arg));
+
526}
+
+
527
+
+
528int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
529{
+
530 return send_reply_ok(req, buf, size);
+
531}
+
+
532
+
533static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
534 struct fuse_chan *ch,
+
535 struct iovec *iov, int iov_count,
+
536 struct fuse_bufvec *buf,
+
537 size_t len)
+
538{
+
539 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
540 void *mbuf;
+
541 int res;
+
542
+
543 /* Optimize common case */
+
544 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
545 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
546 /* FIXME: also avoid memory copy if there are multiple buffers
+
547 but none of them contain an fd */
+
548
+
549 iov[iov_count].iov_base = buf->buf[0].mem;
+
550 iov[iov_count].iov_len = len;
+
551 iov_count++;
+
552 return fuse_send_msg(se, ch, iov, iov_count);
+
553 }
+
554
+
555 res = posix_memalign(&mbuf, pagesize, len);
+
556 if (res != 0)
+
557 return res;
+
558
+
559 mem_buf.buf[0].mem = mbuf;
+
560 res = fuse_buf_copy(&mem_buf, buf, 0);
+
561 if (res < 0) {
+
562 free(mbuf);
+
563 return -res;
+
564 }
+
565 len = res;
+
566
+
567 iov[iov_count].iov_base = mbuf;
+
568 iov[iov_count].iov_len = len;
+
569 iov_count++;
+
570 res = fuse_send_msg(se, ch, iov, iov_count);
+
571 free(mbuf);
+
572
+
573 return res;
+
574}
+
575
+
576struct fuse_ll_pipe {
+
577 size_t size;
+
578 int can_grow;
+
579 int pipe[2];
+
580};
+
581
+
582static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
583{
+
584 close(llp->pipe[0]);
+
585 close(llp->pipe[1]);
+
586 free(llp);
+
587}
+
588
+
589#ifdef HAVE_SPLICE
+
590#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
591static int fuse_pipe(int fds[2])
+
592{
+
593 int rv = pipe(fds);
+
594
+
595 if (rv == -1)
+
596 return rv;
+
597
+
598 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
599 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
600 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
601 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
602 close(fds[0]);
+
603 close(fds[1]);
+
604 rv = -1;
+
605 }
+
606 return rv;
+
607}
+
608#else
+
609static int fuse_pipe(int fds[2])
+
610{
+
611 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
612}
+
613#endif
+
614
+
615static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
616{
+
617 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
618 if (llp == NULL) {
+
619 int res;
+
620
+
621 llp = malloc(sizeof(struct fuse_ll_pipe));
+
622 if (llp == NULL)
+
623 return NULL;
+
624
+
625 res = fuse_pipe(llp->pipe);
+
626 if (res == -1) {
+
627 free(llp);
+
628 return NULL;
+
629 }
+
630
+
631 /*
+
632 *the default size is 16 pages on linux
+
633 */
+
634 llp->size = pagesize * 16;
+
635 llp->can_grow = 1;
+
636
+
637 pthread_setspecific(se->pipe_key, llp);
+
638 }
+
639
+
640 return llp;
+
641}
+
642#endif
+
643
+
644static void fuse_ll_clear_pipe(struct fuse_session *se)
+
645{
+
646 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
647 if (llp) {
+
648 pthread_setspecific(se->pipe_key, NULL);
+
649 fuse_ll_pipe_free(llp);
+
650 }
+
651}
+
652
+
653#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
654static int read_back(int fd, char *buf, size_t len)
+
655{
+
656 int res;
+
657
+
658 res = read(fd, buf, len);
+
659 if (res == -1) {
+
660 fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+
661 return -EIO;
+
662 }
+
663 if (res != len) {
+
664 fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+
665 return -EIO;
+
666 }
+
667 return 0;
+
668}
+
669
+
670static int grow_pipe_to_max(int pipefd)
+
671{
+
672 int res;
+
673 long max;
+
674 long maxfd;
+
675 char buf[32];
+
676
+
677 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
678 if (maxfd < 0)
+
679 return -errno;
+
680
+
681 res = read(maxfd, buf, sizeof(buf) - 1);
+
682 if (res < 0) {
+
683 int saved_errno;
+
684
+
685 saved_errno = errno;
+
686 close(maxfd);
+
687 return -saved_errno;
+
688 }
+
689 close(maxfd);
+
690 buf[res] = '\0';
+
691
+
692 res = libfuse_strtol(buf, &max);
+
693 if (res)
+
694 return res;
+
695 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
696 if (res < 0)
+
697 return -errno;
+
698 return max;
+
699}
+
700
+
701static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
702 struct iovec *iov, int iov_count,
+
703 struct fuse_bufvec *buf, unsigned int flags)
+
704{
+
705 int res;
+
706 size_t len = fuse_buf_size(buf);
+
707 struct fuse_out_header *out = iov[0].iov_base;
+
708 struct fuse_ll_pipe *llp;
+
709 int splice_flags;
+
710 size_t pipesize;
+
711 size_t total_buf_size;
+
712 size_t idx;
+
713 size_t headerlen;
+
714 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
715
+
716 if (se->broken_splice_nonblock)
+
717 goto fallback;
+
718
+
719 if (flags & FUSE_BUF_NO_SPLICE)
+
720 goto fallback;
+
721
+
722 total_buf_size = 0;
+
723 for (idx = buf->idx; idx < buf->count; idx++) {
+
724 total_buf_size += buf->buf[idx].size;
+
725 if (idx == buf->idx)
+
726 total_buf_size -= buf->off;
+
727 }
+
728 if (total_buf_size < 2 * pagesize)
+
729 goto fallback;
+
730
+
731 if (se->conn.proto_minor < 14 ||
+
732 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
733 goto fallback;
+
734
+
735 llp = fuse_ll_get_pipe(se);
+
736 if (llp == NULL)
+
737 goto fallback;
+
738
+
739
+
740 headerlen = iov_length(iov, iov_count);
+
741
+
742 out->len = headerlen + len;
+
743
+
744 /*
+
745 * Heuristic for the required pipe size, does not work if the
+
746 * source contains less than page size fragments
+
747 */
+
748 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
749
+
750 if (llp->size < pipesize) {
+
751 if (llp->can_grow) {
+
752 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
753 if (res == -1) {
+
754 res = grow_pipe_to_max(llp->pipe[0]);
+
755 if (res > 0)
+
756 llp->size = res;
+
757 llp->can_grow = 0;
+
758 goto fallback;
+
759 }
+
760 llp->size = res;
+
761 }
+
762 if (llp->size < pipesize)
+
763 goto fallback;
+
764 }
+
765
+
766
+
767 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
768 if (res == -1)
+
769 goto fallback;
+
770
+
771 if (res != headerlen) {
+
772 res = -EIO;
+
773 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
774 headerlen);
+
775 goto clear_pipe;
+
776 }
+
777
+
778 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
779 pipe_buf.buf[0].fd = llp->pipe[1];
+
780
+
781 res = fuse_buf_copy(&pipe_buf, buf,
+ +
783 if (res < 0) {
+
784 if (res == -EAGAIN || res == -EINVAL) {
+
785 /*
+
786 * Should only get EAGAIN on kernels with
+
787 * broken SPLICE_F_NONBLOCK support (<=
+
788 * 2.6.35) where this error or a short read is
+
789 * returned even if the pipe itself is not
+
790 * full
+
791 *
+
792 * EINVAL might mean that splice can't handle
+
793 * this combination of input and output.
+
794 */
+
795 if (res == -EAGAIN)
+
796 se->broken_splice_nonblock = 1;
+
797
+
798 pthread_setspecific(se->pipe_key, NULL);
+
799 fuse_ll_pipe_free(llp);
+
800 goto fallback;
+
801 }
+
802 res = -res;
+
803 goto clear_pipe;
+
804 }
+
805
+
806 if (res != 0 && res < len) {
+
807 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
808 void *mbuf;
+
809 size_t now_len = res;
+
810 /*
+
811 * For regular files a short count is either
+
812 * 1) due to EOF, or
+
813 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
814 *
+
815 * For other inputs it's possible that we overflowed
+
816 * the pipe because of small buffer fragments.
+
817 */
+
818
+
819 res = posix_memalign(&mbuf, pagesize, len);
+
820 if (res != 0)
+
821 goto clear_pipe;
+
822
+
823 mem_buf.buf[0].mem = mbuf;
+
824 mem_buf.off = now_len;
+
825 res = fuse_buf_copy(&mem_buf, buf, 0);
+
826 if (res > 0) {
+
827 char *tmpbuf;
+
828 size_t extra_len = res;
+
829 /*
+
830 * Trickiest case: got more data. Need to get
+
831 * back the data from the pipe and then fall
+
832 * back to regular write.
+
833 */
+
834 tmpbuf = malloc(headerlen);
+
835 if (tmpbuf == NULL) {
+
836 free(mbuf);
+
837 res = ENOMEM;
+
838 goto clear_pipe;
+
839 }
+
840 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
841 free(tmpbuf);
+
842 if (res != 0) {
+
843 free(mbuf);
+
844 goto clear_pipe;
+
845 }
+
846 res = read_back(llp->pipe[0], mbuf, now_len);
+
847 if (res != 0) {
+
848 free(mbuf);
+
849 goto clear_pipe;
+
850 }
+
851 len = now_len + extra_len;
+
852 iov[iov_count].iov_base = mbuf;
+
853 iov[iov_count].iov_len = len;
+
854 iov_count++;
+
855 res = fuse_send_msg(se, ch, iov, iov_count);
+
856 free(mbuf);
+
857 return res;
+
858 }
+
859 free(mbuf);
+
860 res = now_len;
+
861 }
+
862 len = res;
+
863 out->len = headerlen + len;
+
864
+
865 if (se->debug) {
+
866 fuse_log(FUSE_LOG_DEBUG,
+
867 " unique: %llu, success, outsize: %i (splice)\n",
+
868 (unsigned long long) out->unique, out->len);
+
869 }
+
870
+
871 splice_flags = 0;
+
872 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
873 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
874 splice_flags |= SPLICE_F_MOVE;
+
875
+
876 if (se->io != NULL && se->io->splice_send != NULL) {
+
877 res = se->io->splice_send(llp->pipe[0], NULL,
+
878 ch ? ch->fd : se->fd, NULL, out->len,
+
879 splice_flags, se->userdata);
+
880 } else {
+
881 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
882 out->len, splice_flags);
+
883 }
+
884 if (res == -1) {
+
885 res = -errno;
+
886 perror("fuse: splice from pipe");
+
887 goto clear_pipe;
+
888 }
+
889 if (res != out->len) {
+
890 res = -EIO;
+
891 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
892 res, out->len);
+
893 goto clear_pipe;
+
894 }
+
895 return 0;
+
896
+
897clear_pipe:
+
898 fuse_ll_clear_pipe(se);
+
899 return res;
+
900
+
901fallback:
+
902 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
903}
+
904#else
+
905static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
906 struct iovec *iov, int iov_count,
+
907 struct fuse_bufvec *buf, unsigned int flags)
+
908{
+
909 size_t len = fuse_buf_size(buf);
+
910 (void) flags;
+
911
+
912 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
913}
+
914#endif
+
915
+
+ +
917 enum fuse_buf_copy_flags flags)
+
918{
+
919 struct iovec iov[2];
+
920 struct fuse_out_header out;
+
921 int res;
+
922
+
923 iov[0].iov_base = &out;
+
924 iov[0].iov_len = sizeof(struct fuse_out_header);
+
925
+
926 out.unique = req->unique;
+
927 out.error = 0;
+
928
+
929 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags);
+
930 if (res <= 0) {
+
931 fuse_free_req(req);
+
932 return res;
+
933 } else {
+
934 return fuse_reply_err(req, res);
+
935 }
+
936}
+
+
937
+
+
938int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
939{
+
940 struct fuse_statfs_out arg;
+
941 size_t size = req->se->conn.proto_minor < 4 ?
+
942 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
943
+
944 memset(&arg, 0, sizeof(arg));
+
945 convert_statfs(stbuf, &arg.st);
+
946
+
947 return send_reply_ok(req, &arg, size);
+
948}
+
+
949
+
+
950int fuse_reply_xattr(fuse_req_t req, size_t count)
+
951{
+
952 struct fuse_getxattr_out arg;
+
953
+
954 memset(&arg, 0, sizeof(arg));
+
955 arg.size = count;
+
956
+
957 return send_reply_ok(req, &arg, sizeof(arg));
+
958}
+
+
959
+
+
960int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
961{
+
962 struct fuse_lk_out arg;
+
963
+
964 memset(&arg, 0, sizeof(arg));
+
965 arg.lk.type = lock->l_type;
+
966 if (lock->l_type != F_UNLCK) {
+
967 arg.lk.start = lock->l_start;
+
968 if (lock->l_len == 0)
+
969 arg.lk.end = OFFSET_MAX;
+
970 else
+
971 arg.lk.end = lock->l_start + lock->l_len - 1;
+
972 }
+
973 arg.lk.pid = lock->l_pid;
+
974 return send_reply_ok(req, &arg, sizeof(arg));
+
975}
+
+
976
+
+
977int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
978{
+
979 struct fuse_bmap_out arg;
+
980
+
981 memset(&arg, 0, sizeof(arg));
+
982 arg.block = idx;
+
983
+
984 return send_reply_ok(req, &arg, sizeof(arg));
+
985}
+
+
986
+
987static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
988 size_t count)
+
989{
+
990 struct fuse_ioctl_iovec *fiov;
+
991 size_t i;
+
992
+
993 fiov = malloc(sizeof(fiov[0]) * count);
+
994 if (!fiov)
+
995 return NULL;
+
996
+
997 for (i = 0; i < count; i++) {
+
998 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
999 fiov[i].len = iov[i].iov_len;
+
1000 }
+
1001
+
1002 return fiov;
+
1003}
+
1004
+
+ +
1006 const struct iovec *in_iov, size_t in_count,
+
1007 const struct iovec *out_iov, size_t out_count)
+
1008{
+
1009 struct fuse_ioctl_out arg;
+
1010 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1011 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1012 struct iovec iov[4];
+
1013 size_t count = 1;
+
1014 int res;
+
1015
+
1016 memset(&arg, 0, sizeof(arg));
+
1017 arg.flags |= FUSE_IOCTL_RETRY;
+
1018 arg.in_iovs = in_count;
+
1019 arg.out_iovs = out_count;
+
1020 iov[count].iov_base = &arg;
+
1021 iov[count].iov_len = sizeof(arg);
+
1022 count++;
+
1023
+
1024 if (req->se->conn.proto_minor < 16) {
+
1025 if (in_count) {
+
1026 iov[count].iov_base = (void *)in_iov;
+
1027 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1028 count++;
+
1029 }
+
1030
+
1031 if (out_count) {
+
1032 iov[count].iov_base = (void *)out_iov;
+
1033 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1034 count++;
+
1035 }
+
1036 } else {
+
1037 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1038 if (sizeof(void *) == 4 && req->ioctl_64bit) {
+
1039 res = fuse_reply_err(req, EINVAL);
+
1040 goto out;
+
1041 }
+
1042
+
1043 if (in_count) {
+
1044 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1045 if (!in_fiov)
+
1046 goto enomem;
+
1047
+
1048 iov[count].iov_base = (void *)in_fiov;
+
1049 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1050 count++;
+
1051 }
+
1052 if (out_count) {
+
1053 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1054 if (!out_fiov)
+
1055 goto enomem;
+
1056
+
1057 iov[count].iov_base = (void *)out_fiov;
+
1058 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1059 count++;
+
1060 }
+
1061 }
+
1062
+
1063 res = send_reply_iov(req, 0, iov, count);
+
1064out:
+
1065 free(in_fiov);
+
1066 free(out_fiov);
+
1067
+
1068 return res;
+
1069
+
1070enomem:
+
1071 res = fuse_reply_err(req, ENOMEM);
+
1072 goto out;
+
1073}
+
+
1074
+
+
1075int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1076{
+
1077 struct fuse_ioctl_out arg;
+
1078 struct iovec iov[3];
+
1079 size_t count = 1;
+
1080
+
1081 memset(&arg, 0, sizeof(arg));
+
1082 arg.result = result;
+
1083 iov[count].iov_base = &arg;
+
1084 iov[count].iov_len = sizeof(arg);
+
1085 count++;
+
1086
+
1087 if (size) {
+
1088 iov[count].iov_base = (char *) buf;
+
1089 iov[count].iov_len = size;
+
1090 count++;
+
1091 }
+
1092
+
1093 return send_reply_iov(req, 0, iov, count);
+
1094}
+
+
1095
+
+
1096int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1097 int count)
+
1098{
+
1099 struct iovec *padded_iov;
+
1100 struct fuse_ioctl_out arg;
+
1101 int res;
+
1102
+
1103 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1104 if (padded_iov == NULL)
+
1105 return fuse_reply_err(req, ENOMEM);
+
1106
+
1107 memset(&arg, 0, sizeof(arg));
+
1108 arg.result = result;
+
1109 padded_iov[1].iov_base = &arg;
+
1110 padded_iov[1].iov_len = sizeof(arg);
+
1111
+
1112 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1113
+
1114 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1115 free(padded_iov);
+
1116
+
1117 return res;
+
1118}
+
+
1119
+
+
1120int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1121{
+
1122 struct fuse_poll_out arg;
+
1123
+
1124 memset(&arg, 0, sizeof(arg));
+
1125 arg.revents = revents;
+
1126
+
1127 return send_reply_ok(req, &arg, sizeof(arg));
+
1128}
+
+
1129
+
+
1130int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1131{
+
1132 struct fuse_lseek_out arg;
+
1133
+
1134 memset(&arg, 0, sizeof(arg));
+
1135 arg.offset = off;
+
1136
+
1137 return send_reply_ok(req, &arg, sizeof(arg));
+
1138}
+
+
1139
+
1140static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1141{
+
1142 char *name = (char *) inarg;
+
1143
+
1144 if (req->se->op.lookup)
+
1145 req->se->op.lookup(req, nodeid, name);
+
1146 else
+
1147 fuse_reply_err(req, ENOSYS);
+
1148}
+
1149
+
1150static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1151{
+
1152 struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
1153
+
1154 if (req->se->op.forget)
+
1155 req->se->op.forget(req, nodeid, arg->nlookup);
+
1156 else
+
1157 fuse_reply_none(req);
+
1158}
+
1159
+
1160static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+
1161 const void *inarg)
+
1162{
+
1163 struct fuse_batch_forget_in *arg = (void *) inarg;
+
1164 struct fuse_forget_one *param = (void *) PARAM(arg);
+
1165 unsigned int i;
+
1166
+
1167 (void) nodeid;
+
1168
+
1169 if (req->se->op.forget_multi) {
+
1170 req->se->op.forget_multi(req, arg->count,
+
1171 (struct fuse_forget_data *) param);
+
1172 } else if (req->se->op.forget) {
+
1173 for (i = 0; i < arg->count; i++) {
+
1174 struct fuse_forget_one *forget = &param[i];
+
1175 struct fuse_req *dummy_req;
+
1176
+
1177 dummy_req = fuse_ll_alloc_req(req->se);
+
1178 if (dummy_req == NULL)
+
1179 break;
+
1180
+
1181 dummy_req->unique = req->unique;
+
1182 dummy_req->ctx = req->ctx;
+
1183 dummy_req->ch = NULL;
+
1184
+
1185 req->se->op.forget(dummy_req, forget->nodeid,
+
1186 forget->nlookup);
+
1187 }
+
1188 fuse_reply_none(req);
+
1189 } else {
+
1190 fuse_reply_none(req);
+
1191 }
+
1192}
+
1193
+
1194static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1195{
+
1196 struct fuse_file_info *fip = NULL;
+
1197 struct fuse_file_info fi;
+
1198
+
1199 if (req->se->conn.proto_minor >= 9) {
+
1200 struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
1201
+
1202 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1203 memset(&fi, 0, sizeof(fi));
+
1204 fi.fh = arg->fh;
+
1205 fip = &fi;
+
1206 }
+
1207 }
+
1208
+
1209 if (req->se->op.getattr)
+
1210 req->se->op.getattr(req, nodeid, fip);
+
1211 else
+
1212 fuse_reply_err(req, ENOSYS);
+
1213}
+
1214
+
1215static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1216{
+
1217 struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
1218
+
1219 if (req->se->op.setattr) {
+
1220 struct fuse_file_info *fi = NULL;
+
1221 struct fuse_file_info fi_store;
+
1222 struct stat stbuf;
+
1223 memset(&stbuf, 0, sizeof(stbuf));
+
1224 convert_attr(arg, &stbuf);
+
1225 if (arg->valid & FATTR_FH) {
+
1226 arg->valid &= ~FATTR_FH;
+
1227 memset(&fi_store, 0, sizeof(fi_store));
+
1228 fi = &fi_store;
+
1229 fi->fh = arg->fh;
+
1230 }
+
1231 arg->valid &=
+
1232 FUSE_SET_ATTR_MODE |
+
1233 FUSE_SET_ATTR_UID |
+
1234 FUSE_SET_ATTR_GID |
+
1235 FUSE_SET_ATTR_SIZE |
+
1236 FUSE_SET_ATTR_ATIME |
+
1237 FUSE_SET_ATTR_MTIME |
+
1238 FUSE_SET_ATTR_KILL_SUID |
+
1239 FUSE_SET_ATTR_KILL_SGID |
+
1240 FUSE_SET_ATTR_ATIME_NOW |
+
1241 FUSE_SET_ATTR_MTIME_NOW |
+
1242 FUSE_SET_ATTR_CTIME;
+
1243
+
1244 req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+
1245 } else
+
1246 fuse_reply_err(req, ENOSYS);
+
1247}
+
1248
+
1249static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1250{
+
1251 struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
1252
+
1253 if (req->se->op.access)
+
1254 req->se->op.access(req, nodeid, arg->mask);
+
1255 else
+
1256 fuse_reply_err(req, ENOSYS);
+
1257}
+
1258
+
1259static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1260{
+
1261 (void) inarg;
+
1262
+
1263 if (req->se->op.readlink)
+
1264 req->se->op.readlink(req, nodeid);
+
1265 else
+
1266 fuse_reply_err(req, ENOSYS);
+
1267}
+
1268
+
1269static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1270{
+
1271 struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+
1272 char *name = PARAM(arg);
+
1273
+
1274 if (req->se->conn.proto_minor >= 12)
+
1275 req->ctx.umask = arg->umask;
+
1276 else
+
1277 name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1278
+
1279 if (req->se->op.mknod)
+
1280 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1281 else
+
1282 fuse_reply_err(req, ENOSYS);
+
1283}
+
1284
+
1285static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1286{
+
1287 struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
1288
+
1289 if (req->se->conn.proto_minor >= 12)
+
1290 req->ctx.umask = arg->umask;
+
1291
+
1292 if (req->se->op.mkdir)
+
1293 req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+
1294 else
+
1295 fuse_reply_err(req, ENOSYS);
+
1296}
+
1297
+
1298static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1299{
+
1300 char *name = (char *) inarg;
+
1301
+
1302 if (req->se->op.unlink)
+
1303 req->se->op.unlink(req, nodeid, name);
+
1304 else
+
1305 fuse_reply_err(req, ENOSYS);
+
1306}
+
1307
+
1308static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1309{
+
1310 char *name = (char *) inarg;
+
1311
+
1312 if (req->se->op.rmdir)
+
1313 req->se->op.rmdir(req, nodeid, name);
+
1314 else
+
1315 fuse_reply_err(req, ENOSYS);
+
1316}
+
1317
+
1318static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1319{
+
1320 char *name = (char *) inarg;
+
1321 char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
1322
+
1323 if (req->se->op.symlink)
+
1324 req->se->op.symlink(req, linkname, nodeid, name);
+
1325 else
+
1326 fuse_reply_err(req, ENOSYS);
+
1327}
+
1328
+
1329static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1330{
+
1331 struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+
1332 char *oldname = PARAM(arg);
+
1333 char *newname = oldname + strlen(oldname) + 1;
+
1334
+
1335 if (req->se->op.rename)
+
1336 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1337 0);
+
1338 else
+
1339 fuse_reply_err(req, ENOSYS);
+
1340}
+
1341
+
1342static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1343{
+
1344 struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg;
+
1345 char *oldname = PARAM(arg);
+
1346 char *newname = oldname + strlen(oldname) + 1;
+
1347
+
1348 if (req->se->op.rename)
+
1349 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1350 arg->flags);
+
1351 else
+
1352 fuse_reply_err(req, ENOSYS);
+
1353}
+
1354
+
1355static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1356{
+
1357 struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
1358
+
1359 if (req->se->op.link)
+
1360 req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+
1361 else
+
1362 fuse_reply_err(req, ENOSYS);
+
1363}
+
1364
+
1365static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1366{
+
1367 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1368
+
1369 if (req->se->op.tmpfile) {
+
1370 struct fuse_file_info fi;
+
1371
+
1372 memset(&fi, 0, sizeof(fi));
+
1373 fi.flags = arg->flags;
+
1374
+
1375 if (req->se->conn.proto_minor >= 12)
+
1376 req->ctx.umask = arg->umask;
+
1377
+
1378 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1379 } else
+
1380 fuse_reply_err(req, ENOSYS);
+
1381}
+
1382
+
1383static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1384{
+
1385 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1386
+
1387 if (req->se->op.create) {
+
1388 struct fuse_file_info fi;
+
1389 char *name = PARAM(arg);
+
1390
+
1391 memset(&fi, 0, sizeof(fi));
+
1392 fi.flags = arg->flags;
+
1393
+
1394 if (req->se->conn.proto_minor >= 12)
+
1395 req->ctx.umask = arg->umask;
+
1396 else
+
1397 name = (char *) inarg + sizeof(struct fuse_open_in);
+
1398
+
1399 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1400 } else
+
1401 fuse_reply_err(req, ENOSYS);
+
1402}
+
1403
+
1404static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1405{
+
1406 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1407 struct fuse_file_info fi;
+
1408
+
1409 memset(&fi, 0, sizeof(fi));
+
1410 fi.flags = arg->flags;
+
1411
+
1412 if (req->se->op.open)
+
1413 req->se->op.open(req, nodeid, &fi);
+
1414 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1415 fuse_reply_err(req, ENOSYS);
+
1416 else
+
1417 fuse_reply_open(req, &fi);
+
1418}
+
1419
+
1420static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1421{
+
1422 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1423
+
1424 if (req->se->op.read) {
+
1425 struct fuse_file_info fi;
+
1426
+
1427 memset(&fi, 0, sizeof(fi));
+
1428 fi.fh = arg->fh;
+
1429 if (req->se->conn.proto_minor >= 9) {
+
1430 fi.lock_owner = arg->lock_owner;
+
1431 fi.flags = arg->flags;
+
1432 }
+
1433 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1434 } else
+
1435 fuse_reply_err(req, ENOSYS);
+
1436}
+
1437
+
1438static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1439{
+
1440 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1441 struct fuse_file_info fi;
+
1442 char *param;
+
1443
+
1444 memset(&fi, 0, sizeof(fi));
+
1445 fi.fh = arg->fh;
+
1446 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1447
+
1448 if (req->se->conn.proto_minor < 9) {
+
1449 param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1450 } else {
+
1451 fi.lock_owner = arg->lock_owner;
+
1452 fi.flags = arg->flags;
+
1453 param = PARAM(arg);
+
1454 }
+
1455
+
1456 if (req->se->op.write)
+
1457 req->se->op.write(req, nodeid, param, arg->size,
+
1458 arg->offset, &fi);
+
1459 else
+
1460 fuse_reply_err(req, ENOSYS);
+
1461}
+
1462
+
1463static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+
1464 const struct fuse_buf *ibuf)
+
1465{
+
1466 struct fuse_session *se = req->se;
+
1467 struct fuse_bufvec bufv = {
+
1468 .buf[0] = *ibuf,
+
1469 .count = 1,
+
1470 };
+
1471 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1472 struct fuse_file_info fi;
+
1473
+
1474 memset(&fi, 0, sizeof(fi));
+
1475 fi.fh = arg->fh;
+
1476 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1477
+
1478 if (se->conn.proto_minor < 9) {
+
1479 bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1480 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1481 FUSE_COMPAT_WRITE_IN_SIZE;
+
1482 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1483 } else {
+
1484 fi.lock_owner = arg->lock_owner;
+
1485 fi.flags = arg->flags;
+
1486 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1487 bufv.buf[0].mem = PARAM(arg);
+
1488
+
1489 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1490 sizeof(struct fuse_write_in);
+
1491 }
+
1492 if (bufv.buf[0].size < arg->size) {
+
1493 fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n");
+
1494 fuse_reply_err(req, EIO);
+
1495 goto out;
+
1496 }
+
1497 bufv.buf[0].size = arg->size;
+
1498
+
1499 se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
1500
+
1501out:
+
1502 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1503 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1504 fuse_ll_clear_pipe(se);
+
1505}
+
1506
+
1507static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1508{
+
1509 struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+
1510 struct fuse_file_info fi;
+
1511
+
1512 memset(&fi, 0, sizeof(fi));
+
1513 fi.fh = arg->fh;
+
1514 fi.flush = 1;
+
1515 if (req->se->conn.proto_minor >= 7)
+
1516 fi.lock_owner = arg->lock_owner;
+
1517
+
1518 if (req->se->op.flush)
+
1519 req->se->op.flush(req, nodeid, &fi);
+
1520 else
+
1521 fuse_reply_err(req, ENOSYS);
+
1522}
+
1523
+
1524static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1525{
+
1526 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1527 struct fuse_file_info fi;
+
1528
+
1529 memset(&fi, 0, sizeof(fi));
+
1530 fi.flags = arg->flags;
+
1531 fi.fh = arg->fh;
+
1532 if (req->se->conn.proto_minor >= 8) {
+
1533 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1534 fi.lock_owner = arg->lock_owner;
+
1535 }
+
1536 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1537 fi.flock_release = 1;
+
1538 fi.lock_owner = arg->lock_owner;
+
1539 }
+
1540
+
1541 if (req->se->op.release)
+
1542 req->se->op.release(req, nodeid, &fi);
+
1543 else
+
1544 fuse_reply_err(req, 0);
+
1545}
+
1546
+
1547static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1548{
+
1549 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1550 struct fuse_file_info fi;
+
1551 int datasync = arg->fsync_flags & 1;
+
1552
+
1553 memset(&fi, 0, sizeof(fi));
+
1554 fi.fh = arg->fh;
+
1555
+
1556 if (req->se->op.fsync)
+
1557 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1558 else
+
1559 fuse_reply_err(req, ENOSYS);
+
1560}
+
1561
+
1562static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1563{
+
1564 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1565 struct fuse_file_info fi;
+
1566
+
1567 memset(&fi, 0, sizeof(fi));
+
1568 fi.flags = arg->flags;
+
1569
+
1570 if (req->se->op.opendir)
+
1571 req->se->op.opendir(req, nodeid, &fi);
+
1572 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1573 fuse_reply_err(req, ENOSYS);
+
1574 else
+
1575 fuse_reply_open(req, &fi);
+
1576}
+
1577
+
1578static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1579{
+
1580 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1581 struct fuse_file_info fi;
+
1582
+
1583 memset(&fi, 0, sizeof(fi));
+
1584 fi.fh = arg->fh;
+
1585
+
1586 if (req->se->op.readdir)
+
1587 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1588 else
+
1589 fuse_reply_err(req, ENOSYS);
+
1590}
+
1591
+
1592static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1593{
+
1594 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1595 struct fuse_file_info fi;
+
1596
+
1597 memset(&fi, 0, sizeof(fi));
+
1598 fi.fh = arg->fh;
+
1599
+
1600 if (req->se->op.readdirplus)
+
1601 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1602 else
+
1603 fuse_reply_err(req, ENOSYS);
+
1604}
+
1605
+
1606static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1607{
+
1608 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1609 struct fuse_file_info fi;
+
1610
+
1611 memset(&fi, 0, sizeof(fi));
+
1612 fi.flags = arg->flags;
+
1613 fi.fh = arg->fh;
+
1614
+
1615 if (req->se->op.releasedir)
+
1616 req->se->op.releasedir(req, nodeid, &fi);
+
1617 else
+
1618 fuse_reply_err(req, 0);
+
1619}
+
1620
+
1621static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1622{
+
1623 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1624 struct fuse_file_info fi;
+
1625 int datasync = arg->fsync_flags & 1;
+
1626
+
1627 memset(&fi, 0, sizeof(fi));
+
1628 fi.fh = arg->fh;
+
1629
+
1630 if (req->se->op.fsyncdir)
+
1631 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
1632 else
+
1633 fuse_reply_err(req, ENOSYS);
+
1634}
+
1635
+
1636static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1637{
+
1638 (void) nodeid;
+
1639 (void) inarg;
+
1640
+
1641 if (req->se->op.statfs)
+
1642 req->se->op.statfs(req, nodeid);
+
1643 else {
+
1644 struct statvfs buf = {
+
1645 .f_namemax = 255,
+
1646 .f_bsize = 512,
+
1647 };
+
1648 fuse_reply_statfs(req, &buf);
+
1649 }
+
1650}
+
1651
+
1652static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1653{
+
1654 struct fuse_session *se = req->se;
+
1655 unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT);
+
1656 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+
1657 char *name = xattr_ext ? PARAM(arg) :
+
1658 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
1659 char *value = name + strlen(name) + 1;
+
1660
+
1661 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
1662 if (req->se->op.setxattr)
+
1663 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
1664 arg->flags);
+
1665 else
+
1666 fuse_reply_err(req, ENOSYS);
+
1667}
+
1668
+
1669static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1670{
+
1671 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1672
+
1673 if (req->se->op.getxattr)
+
1674 req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+
1675 else
+
1676 fuse_reply_err(req, ENOSYS);
+
1677}
+
1678
+
1679static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1680{
+
1681 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1682
+
1683 if (req->se->op.listxattr)
+
1684 req->se->op.listxattr(req, nodeid, arg->size);
+
1685 else
+
1686 fuse_reply_err(req, ENOSYS);
+
1687}
+
1688
+
1689static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1690{
+
1691 char *name = (char *) inarg;
+
1692
+
1693 if (req->se->op.removexattr)
+
1694 req->se->op.removexattr(req, nodeid, name);
+
1695 else
+
1696 fuse_reply_err(req, ENOSYS);
+
1697}
+
1698
+
1699static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+
1700 struct flock *flock)
+
1701{
+
1702 memset(flock, 0, sizeof(struct flock));
+
1703 flock->l_type = fl->type;
+
1704 flock->l_whence = SEEK_SET;
+
1705 flock->l_start = fl->start;
+
1706 if (fl->end == OFFSET_MAX)
+
1707 flock->l_len = 0;
+
1708 else
+
1709 flock->l_len = fl->end - fl->start + 1;
+
1710 flock->l_pid = fl->pid;
+
1711}
+
1712
+
1713static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1714{
+
1715 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1716 struct fuse_file_info fi;
+
1717 struct flock flock;
+
1718
+
1719 memset(&fi, 0, sizeof(fi));
+
1720 fi.fh = arg->fh;
+
1721 fi.lock_owner = arg->owner;
+
1722
+
1723 convert_fuse_file_lock(&arg->lk, &flock);
+
1724 if (req->se->op.getlk)
+
1725 req->se->op.getlk(req, nodeid, &fi, &flock);
+
1726 else
+
1727 fuse_reply_err(req, ENOSYS);
+
1728}
+
1729
+
1730static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+
1731 const void *inarg, int sleep)
+
1732{
+
1733 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1734 struct fuse_file_info fi;
+
1735 struct flock flock;
+
1736
+
1737 memset(&fi, 0, sizeof(fi));
+
1738 fi.fh = arg->fh;
+
1739 fi.lock_owner = arg->owner;
+
1740
+
1741 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
1742 int op = 0;
+
1743
+
1744 switch (arg->lk.type) {
+
1745 case F_RDLCK:
+
1746 op = LOCK_SH;
+
1747 break;
+
1748 case F_WRLCK:
+
1749 op = LOCK_EX;
+
1750 break;
+
1751 case F_UNLCK:
+
1752 op = LOCK_UN;
+
1753 break;
+
1754 }
+
1755 if (!sleep)
+
1756 op |= LOCK_NB;
+
1757
+
1758 if (req->se->op.flock)
+
1759 req->se->op.flock(req, nodeid, &fi, op);
+
1760 else
+
1761 fuse_reply_err(req, ENOSYS);
+
1762 } else {
+
1763 convert_fuse_file_lock(&arg->lk, &flock);
+
1764 if (req->se->op.setlk)
+
1765 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
1766 else
+
1767 fuse_reply_err(req, ENOSYS);
+
1768 }
+
1769}
+
1770
+
1771static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1772{
+
1773 do_setlk_common(req, nodeid, inarg, 0);
+
1774}
+
1775
+
1776static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1777{
+
1778 do_setlk_common(req, nodeid, inarg, 1);
+
1779}
+
1780
+
1781static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
1782{
+
1783 struct fuse_req *curr;
+
1784
+
1785 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
1786 if (curr->unique == req->u.i.unique) {
+ +
1788 void *data;
+
1789
+
1790 curr->ref_cnt++;
+
1791 pthread_mutex_unlock(&se->lock);
+
1792
+
1793 /* Ugh, ugly locking */
+
1794 pthread_mutex_lock(&curr->lock);
+
1795 pthread_mutex_lock(&se->lock);
+
1796 curr->interrupted = 1;
+
1797 func = curr->u.ni.func;
+
1798 data = curr->u.ni.data;
+
1799 pthread_mutex_unlock(&se->lock);
+
1800 if (func)
+
1801 func(curr, data);
+
1802 pthread_mutex_unlock(&curr->lock);
+
1803
+
1804 pthread_mutex_lock(&se->lock);
+
1805 curr->ref_cnt--;
+
1806 if (!curr->ref_cnt) {
+
1807 destroy_req(curr);
+
1808 }
+
1809
+
1810 return 1;
+
1811 }
+
1812 }
+
1813 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1814 curr = curr->next) {
+
1815 if (curr->u.i.unique == req->u.i.unique)
+
1816 return 1;
+
1817 }
+
1818 return 0;
+
1819}
+
1820
+
1821static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1822{
+
1823 struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+
1824 struct fuse_session *se = req->se;
+
1825
+
1826 (void) nodeid;
+
1827 if (se->debug)
+
1828 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
1829 (unsigned long long) arg->unique);
+
1830
+
1831 req->u.i.unique = arg->unique;
+
1832
+
1833 pthread_mutex_lock(&se->lock);
+
1834 if (find_interrupted(se, req)) {
+
1835 fuse_chan_put(req->ch);
+
1836 req->ch = NULL;
+
1837 destroy_req(req);
+
1838 } else
+
1839 list_add_req(req, &se->interrupts);
+
1840 pthread_mutex_unlock(&se->lock);
+
1841}
+
1842
+
1843static struct fuse_req *check_interrupt(struct fuse_session *se,
+
1844 struct fuse_req *req)
+
1845{
+
1846 struct fuse_req *curr;
+
1847
+
1848 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1849 curr = curr->next) {
+
1850 if (curr->u.i.unique == req->unique) {
+
1851 req->interrupted = 1;
+
1852 list_del_req(curr);
+
1853 fuse_chan_put(curr->ch);
+
1854 curr->ch = NULL;
+
1855 destroy_req(curr);
+
1856 return NULL;
+
1857 }
+
1858 }
+
1859 curr = se->interrupts.next;
+
1860 if (curr != &se->interrupts) {
+
1861 list_del_req(curr);
+
1862 list_init_req(curr);
+
1863 return curr;
+
1864 } else
+
1865 return NULL;
+
1866}
+
1867
+
1868static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1869{
+
1870 struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
1871
+
1872 if (req->se->op.bmap)
+
1873 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
1874 else
+
1875 fuse_reply_err(req, ENOSYS);
+
1876}
+
1877
+
1878static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1879{
+
1880 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+
1881 unsigned int flags = arg->flags;
+
1882 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
1883 struct fuse_file_info fi;
+
1884
+
1885 if (flags & FUSE_IOCTL_DIR &&
+
1886 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
1887 fuse_reply_err(req, ENOTTY);
+
1888 return;
+
1889 }
+
1890
+
1891 memset(&fi, 0, sizeof(fi));
+
1892 fi.fh = arg->fh;
+
1893
+
1894 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
1895 !(flags & FUSE_IOCTL_32BIT)) {
+
1896 req->ioctl_64bit = 1;
+
1897 }
+
1898
+
1899 if (req->se->op.ioctl)
+
1900 req->se->op.ioctl(req, nodeid, arg->cmd,
+
1901 (void *)(uintptr_t)arg->arg, &fi, flags,
+
1902 in_buf, arg->in_size, arg->out_size);
+
1903 else
+
1904 fuse_reply_err(req, ENOSYS);
+
1905}
+
1906
+
+
1907void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
1908{
+
1909 free(ph);
+
1910}
+
+
1911
+
1912static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1913{
+
1914 struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+
1915 struct fuse_file_info fi;
+
1916
+
1917 memset(&fi, 0, sizeof(fi));
+
1918 fi.fh = arg->fh;
+
1919 fi.poll_events = arg->events;
+
1920
+
1921 if (req->se->op.poll) {
+
1922 struct fuse_pollhandle *ph = NULL;
+
1923
+
1924 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
1925 ph = malloc(sizeof(struct fuse_pollhandle));
+
1926 if (ph == NULL) {
+
1927 fuse_reply_err(req, ENOMEM);
+
1928 return;
+
1929 }
+
1930 ph->kh = arg->kh;
+
1931 ph->se = req->se;
+
1932 }
+
1933
+
1934 req->se->op.poll(req, nodeid, &fi, ph);
+
1935 } else {
+
1936 fuse_reply_err(req, ENOSYS);
+
1937 }
+
1938}
+
1939
+
1940static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1941{
+
1942 struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+
1943 struct fuse_file_info fi;
+
1944
+
1945 memset(&fi, 0, sizeof(fi));
+
1946 fi.fh = arg->fh;
+
1947
+
1948 if (req->se->op.fallocate)
+
1949 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+
1950 else
+
1951 fuse_reply_err(req, ENOSYS);
+
1952}
+
1953
+
1954static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
+
1955{
+
1956 struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
+
1957 struct fuse_file_info fi_in, fi_out;
+
1958
+
1959 memset(&fi_in, 0, sizeof(fi_in));
+
1960 fi_in.fh = arg->fh_in;
+
1961
+
1962 memset(&fi_out, 0, sizeof(fi_out));
+
1963 fi_out.fh = arg->fh_out;
+
1964
+
1965
+
1966 if (req->se->op.copy_file_range)
+
1967 req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
+
1968 &fi_in, arg->nodeid_out,
+
1969 arg->off_out, &fi_out, arg->len,
+
1970 arg->flags);
+
1971 else
+
1972 fuse_reply_err(req, ENOSYS);
+
1973}
+
1974
+
1975static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1976{
+
1977 struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg;
+
1978 struct fuse_file_info fi;
+
1979
+
1980 memset(&fi, 0, sizeof(fi));
+
1981 fi.fh = arg->fh;
+
1982
+
1983 if (req->se->op.lseek)
+
1984 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
1985 else
+
1986 fuse_reply_err(req, ENOSYS);
+
1987}
+
1988
+
1989static bool want_flags_valid(uint64_t capable, uint64_t want)
+
1990{
+
1991 uint64_t unknown_flags = want & (~capable);
+
1992 if (unknown_flags != 0) {
+
1993 fuse_log(FUSE_LOG_ERR,
+
1994 "fuse: unknown connection 'want' flags: 0x%08lx\n",
+
1995 unknown_flags);
+
1996 return false;
+
1997 }
+
1998 return true;
+
1999}
+
2000
+
2001/* Prevent bogus data races (bogus since "init" is called before
+
2002 * multi-threading becomes relevant */
+
2003static __attribute__((no_sanitize("thread")))
+
2004void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2005{
+
2006 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
2007 struct fuse_init_out outarg;
+
2008 struct fuse_session *se = req->se;
+
2009 size_t bufsize = se->bufsize;
+
2010 size_t outargsize = sizeof(outarg);
+
2011 uint64_t inargflags = 0;
+
2012 uint64_t outargflags = 0;
+
2013 bool buf_reallocable = se->buf_reallocable;
+
2014 (void) nodeid;
+
2015 if (se->debug) {
+
2016 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2017 if (arg->major == 7 && arg->minor >= 6) {
+
2018 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2019 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2020 arg->max_readahead);
+
2021 }
+
2022 }
+
2023 se->conn.proto_major = arg->major;
+
2024 se->conn.proto_minor = arg->minor;
+
2025 se->conn.capable_ext = 0;
+
2026 se->conn.want_ext = 0;
+
2027
+
2028 memset(&outarg, 0, sizeof(outarg));
+
2029 outarg.major = FUSE_KERNEL_VERSION;
+
2030 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2031
+
2032 if (arg->major < 7) {
+
2033 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2034 arg->major, arg->minor);
+
2035 fuse_reply_err(req, EPROTO);
+
2036 return;
+
2037 }
+
2038
+
2039 if (arg->major > 7) {
+
2040 /* Wait for a second INIT request with a 7.X version */
+
2041 send_reply_ok(req, &outarg, sizeof(outarg));
+
2042 return;
+
2043 }
+
2044
+
2045 if (arg->minor >= 6) {
+
2046 if (arg->max_readahead < se->conn.max_readahead)
+
2047 se->conn.max_readahead = arg->max_readahead;
+
2048 inargflags = arg->flags;
+
2049 if (inargflags & FUSE_INIT_EXT)
+
2050 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2051 if (inargflags & FUSE_ASYNC_READ)
+
2052 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2053 if (inargflags & FUSE_POSIX_LOCKS)
+
2054 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2055 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2056 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2057 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2058 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2059 if (inargflags & FUSE_DONT_MASK)
+
2060 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2061 if (inargflags & FUSE_FLOCK_LOCKS)
+
2062 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2063 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2064 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2065 if (inargflags & FUSE_DO_READDIRPLUS)
+
2066 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2067 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2068 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2069 if (inargflags & FUSE_ASYNC_DIO)
+
2070 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2071 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2072 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2073 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2074 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2075 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2076 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2077 if (inargflags & FUSE_POSIX_ACL)
+
2078 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2079 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2080 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2081 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2082 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2083 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2084 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2085 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2086 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2087 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2088 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2089 if (inargflags & FUSE_SETXATTR_EXT)
+
2090 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2091 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2092 size_t max_bufsize =
+
2093 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2094 + FUSE_BUFFER_HEADER_SIZE;
+
2095 if (bufsize > max_bufsize) {
+
2096 bufsize = max_bufsize;
+
2097 }
+
2098 buf_reallocable = false;
+
2099 }
+
2100 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2101 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2102 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2103 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2104 if (inargflags & FUSE_PASSTHROUGH)
+
2105 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2106 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2107 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2108 } else {
+
2109 se->conn.max_readahead = 0;
+
2110 }
+
2111
+
2112 if (se->conn.proto_minor >= 14) {
+
2113#ifdef HAVE_SPLICE
+
2114#ifdef HAVE_VMSPLICE
+
2115 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2116 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2118 }
+
2119#endif
+
2120 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2121 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2122 }
+
2123#endif
+
2124 }
+
2125 if (se->conn.proto_minor >= 18)
+
2126 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2127
+
2128 /* Default settings for modern filesystems.
+
2129 *
+
2130 * Most of these capabilities were disabled by default in
+
2131 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2132 * we can finally enable them by default (as long as they're
+
2133 * supported by the kernel).
+
2134 */
+
2135#define LL_SET_DEFAULT(cond, cap) \
+
2136 if ((cond)) \
+
2137 fuse_set_feature_flag(&se->conn, cap)
+
2138
+
2139 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2140 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2141 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2142 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2143 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2144 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2145 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2147 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2148 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2149 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2151
+
2152 /* This could safely become default, but libfuse needs an API extension
+
2153 * to support it
+
2154 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2155 */
+
2156
+
2157 se->conn.time_gran = 1;
+
2158
+
2159 se->got_init = 1;
+
2160 if (se->op.init) {
+
2161 uint64_t want_ext_default = se->conn.want_ext;
+
2162 uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext);
+
2163 int rc;
+
2164
+
2165 // Apply the first 32 bits of capable_ext to capable
+
2166 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2167 se->conn.want = want_default;
+
2168
+
2169 se->op.init(se->userdata, &se->conn);
+
2170
+
2171 /*
+
2172 * se->conn.want is 32-bit value and deprecated in favour of
+
2173 * se->conn.want_ext
+
2174 * Userspace might still use conn.want - we need to convert it
+
2175 */
+
2176 rc = convert_to_conn_want_ext(&se->conn, want_ext_default,
+
2177 want_default);
+
2178 if (rc != 0) {
+
2179 fuse_reply_err(req, EPROTO);
+
2180 se->error = -EPROTO;
+ +
2182 return;
+
2183 }
+
2184 }
+
2185
+
2186 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2187 fuse_reply_err(req, EPROTO);
+
2188 se->error = -EPROTO;
+ +
2190 return;
+
2191 }
+
2192
+
2193 unsigned max_read_mo = get_max_read(se->mo);
+
2194 if (se->conn.max_read != max_read_mo) {
+
2195 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2196 "requested different maximum read size (%u vs %u)\n",
+
2197 se->conn.max_read, max_read_mo);
+
2198 fuse_reply_err(req, EPROTO);
+
2199 se->error = -EPROTO;
+ +
2201 return;
+
2202 }
+
2203
+
2204 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2205 fuse_log(FUSE_LOG_ERR,
+
2206 "fuse: warning: buffer size too small: %zu\n",
+
2207 bufsize);
+
2208 bufsize = FUSE_MIN_READ_BUFFER;
+
2209 }
+
2210
+
2211 if (buf_reallocable)
+
2212 bufsize = UINT_MAX;
+
2213 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2214 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2215
+
2216 if (arg->flags & FUSE_MAX_PAGES) {
+
2217 outarg.flags |= FUSE_MAX_PAGES;
+
2218 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2219 }
+
2220 outargflags = outarg.flags;
+
2221 /* Always enable big writes, this is superseded
+
2222 by the max_write option */
+
2223 outargflags |= FUSE_BIG_WRITES;
+
2224
+
2225 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2226 outargflags |= FUSE_ASYNC_READ;
+
2227 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2228 outargflags |= FUSE_POSIX_LOCKS;
+
2229 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2230 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2231 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2232 outargflags |= FUSE_EXPORT_SUPPORT;
+
2233 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2234 outargflags |= FUSE_DONT_MASK;
+
2235 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2236 outargflags |= FUSE_FLOCK_LOCKS;
+
2237 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2238 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2239 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2240 outargflags |= FUSE_DO_READDIRPLUS;
+
2241 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2242 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2243 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2244 outargflags |= FUSE_ASYNC_DIO;
+
2245 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2246 outargflags |= FUSE_WRITEBACK_CACHE;
+
2247 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2248 outargflags |= FUSE_PARALLEL_DIROPS;
+
2249 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2250 outargflags |= FUSE_POSIX_ACL;
+
2251 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2252 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2253 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2254 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2255 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2256 outargflags |= FUSE_CACHE_SYMLINKS;
+
2257 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2258 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2259 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2260 outargflags |= FUSE_SETXATTR_EXT;
+
2261 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2262 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2263 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2264 outargflags |= FUSE_PASSTHROUGH;
+
2265 /*
+
2266 * outarg.max_stack_depth includes the fuse stack layer,
+
2267 * so it is one more than max_backing_stack_depth.
+
2268 */
+
2269 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2270 }
+
2271 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2272 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2273
+
2274 if (inargflags & FUSE_INIT_EXT) {
+
2275 outargflags |= FUSE_INIT_EXT;
+
2276 outarg.flags2 = outargflags >> 32;
+
2277 }
+
2278
+
2279 outarg.flags = outargflags;
+
2280
+
2281 outarg.max_readahead = se->conn.max_readahead;
+
2282 outarg.max_write = se->conn.max_write;
+
2283 if (se->conn.proto_minor >= 13) {
+
2284 if (se->conn.max_background >= (1 << 16))
+
2285 se->conn.max_background = (1 << 16) - 1;
+
2286 if (se->conn.congestion_threshold > se->conn.max_background)
+
2287 se->conn.congestion_threshold = se->conn.max_background;
+
2288 if (!se->conn.congestion_threshold) {
+
2289 se->conn.congestion_threshold =
+
2290 se->conn.max_background * 3 / 4;
+
2291 }
+
2292
+
2293 outarg.max_background = se->conn.max_background;
+
2294 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2295 }
+
2296 if (se->conn.proto_minor >= 23)
+
2297 outarg.time_gran = se->conn.time_gran;
+
2298
+
2299 if (se->debug) {
+
2300 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2301 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2302 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2303 outarg.max_readahead);
+
2304 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2305 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2306 outarg.max_background);
+
2307 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2308 outarg.congestion_threshold);
+
2309 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2310 outarg.time_gran);
+
2311 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2312 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2313 outarg.max_stack_depth);
+
2314 }
+
2315 if (arg->minor < 5)
+
2316 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2317 else if (arg->minor < 23)
+
2318 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2319
+
2320 send_reply_ok(req, &outarg, outargsize);
+
2321}
+
2322
+
2323static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2324{
+
2325 struct fuse_session *se = req->se;
+
2326
+
2327 (void) nodeid;
+
2328 (void) inarg;
+
2329
+
2330 se->got_destroy = 1;
+
2331 se->got_init = 0;
+
2332 if (se->op.destroy)
+
2333 se->op.destroy(se->userdata);
+
2334
+
2335 send_reply_ok(req, NULL, 0);
+
2336}
+
2337
+
2338static void list_del_nreq(struct fuse_notify_req *nreq)
+
2339{
+
2340 struct fuse_notify_req *prev = nreq->prev;
+
2341 struct fuse_notify_req *next = nreq->next;
+
2342 prev->next = next;
+
2343 next->prev = prev;
+
2344}
+
2345
+
2346static void list_add_nreq(struct fuse_notify_req *nreq,
+
2347 struct fuse_notify_req *next)
+
2348{
+
2349 struct fuse_notify_req *prev = next->prev;
+
2350 nreq->next = next;
+
2351 nreq->prev = prev;
+
2352 prev->next = nreq;
+
2353 next->prev = nreq;
+
2354}
+
2355
+
2356static void list_init_nreq(struct fuse_notify_req *nreq)
+
2357{
+
2358 nreq->next = nreq;
+
2359 nreq->prev = nreq;
+
2360}
+
2361
+
2362static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
2363 const void *inarg, const struct fuse_buf *buf)
+
2364{
+
2365 struct fuse_session *se = req->se;
+
2366 struct fuse_notify_req *nreq;
+
2367 struct fuse_notify_req *head;
+
2368
+
2369 pthread_mutex_lock(&se->lock);
+
2370 head = &se->notify_list;
+
2371 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
2372 if (nreq->unique == req->unique) {
+
2373 list_del_nreq(nreq);
+
2374 break;
+
2375 }
+
2376 }
+
2377 pthread_mutex_unlock(&se->lock);
+
2378
+
2379 if (nreq != head)
+
2380 nreq->reply(nreq, req, nodeid, inarg, buf);
+
2381}
+
2382
+
2383static int send_notify_iov(struct fuse_session *se, int notify_code,
+
2384 struct iovec *iov, int count)
+
2385{
+
2386 struct fuse_out_header out;
+
2387
+
2388 if (!se->got_init)
+
2389 return -ENOTCONN;
+
2390
+
2391 out.unique = 0;
+
2392 out.error = notify_code;
+
2393 iov[0].iov_base = &out;
+
2394 iov[0].iov_len = sizeof(struct fuse_out_header);
+
2395
+
2396 return fuse_send_msg(se, NULL, iov, count);
+
2397}
+
2398
+
+
2399int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
2400{
+
2401 if (ph != NULL) {
+
2402 struct fuse_notify_poll_wakeup_out outarg;
+
2403 struct iovec iov[2];
+
2404
+
2405 outarg.kh = ph->kh;
+
2406
+
2407 iov[1].iov_base = &outarg;
+
2408 iov[1].iov_len = sizeof(outarg);
+
2409
+
2410 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
2411 } else {
+
2412 return 0;
+
2413 }
+
2414}
+
+
2415
+
+
2416int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
2417 off_t off, off_t len)
+
2418{
+
2419 struct fuse_notify_inval_inode_out outarg;
+
2420 struct iovec iov[2];
+
2421
+
2422 if (!se)
+
2423 return -EINVAL;
+
2424
+
2425 if (se->conn.proto_minor < 12)
+
2426 return -ENOSYS;
+
2427
+
2428 outarg.ino = ino;
+
2429 outarg.off = off;
+
2430 outarg.len = len;
+
2431
+
2432 iov[1].iov_base = &outarg;
+
2433 iov[1].iov_len = sizeof(outarg);
+
2434
+
2435 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
2436}
+
+
2437
+
2457static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
2458 const char *name, size_t namelen,
+
2459 enum fuse_notify_entry_flags flags)
+
2460{
+
2461 struct fuse_notify_inval_entry_out outarg;
+
2462 struct iovec iov[3];
+
2463
+
2464 if (!se)
+
2465 return -EINVAL;
+
2466
+
2467 if (se->conn.proto_minor < 12)
+
2468 return -ENOSYS;
+
2469
+
2470 outarg.parent = parent;
+
2471 outarg.namelen = namelen;
+
2472 outarg.flags = 0;
+
2473 if (flags & FUSE_LL_EXPIRE_ONLY)
+
2474 outarg.flags |= FUSE_EXPIRE_ONLY;
+
2475
+
2476 iov[1].iov_base = &outarg;
+
2477 iov[1].iov_len = sizeof(outarg);
+
2478 iov[2].iov_base = (void *)name;
+
2479 iov[2].iov_len = namelen + 1;
+
2480
+
2481 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
2482}
+
2483
+
+
2484int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
2485 const char *name, size_t namelen)
+
2486{
+
2487 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
2488}
+
+
2489
+
+
2490int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
2491 const char *name, size_t namelen)
+
2492{
+
2493 if (!se)
+
2494 return -EINVAL;
+
2495
+
2496 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
2497 return -ENOSYS;
+
2498
+
2499 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
2500}
+
+
2501
+
2502
+
+
2503int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
2504 fuse_ino_t parent, fuse_ino_t child,
+
2505 const char *name, size_t namelen)
+
2506{
+
2507 struct fuse_notify_delete_out outarg;
+
2508 struct iovec iov[3];
+
2509
+
2510 if (!se)
+
2511 return -EINVAL;
+
2512
+
2513 if (se->conn.proto_minor < 18)
+
2514 return -ENOSYS;
+
2515
+
2516 outarg.parent = parent;
+
2517 outarg.child = child;
+
2518 outarg.namelen = namelen;
+
2519 outarg.padding = 0;
+
2520
+
2521 iov[1].iov_base = &outarg;
+
2522 iov[1].iov_len = sizeof(outarg);
+
2523 iov[2].iov_base = (void *)name;
+
2524 iov[2].iov_len = namelen + 1;
+
2525
+
2526 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
2527}
+
+
2528
+
+
2529int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
2530 off_t offset, struct fuse_bufvec *bufv,
+
2531 enum fuse_buf_copy_flags flags)
+
2532{
+
2533 struct fuse_out_header out;
+
2534 struct fuse_notify_store_out outarg;
+
2535 struct iovec iov[3];
+
2536 size_t size = fuse_buf_size(bufv);
+
2537 int res;
+
2538
+
2539 if (!se)
+
2540 return -EINVAL;
+
2541
+
2542 if (se->conn.proto_minor < 15)
+
2543 return -ENOSYS;
+
2544
+
2545 out.unique = 0;
+
2546 out.error = FUSE_NOTIFY_STORE;
+
2547
+
2548 outarg.nodeid = ino;
+
2549 outarg.offset = offset;
+
2550 outarg.size = size;
+
2551 outarg.padding = 0;
+
2552
+
2553 iov[0].iov_base = &out;
+
2554 iov[0].iov_len = sizeof(out);
+
2555 iov[1].iov_base = &outarg;
+
2556 iov[1].iov_len = sizeof(outarg);
+
2557
+
2558 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags);
+
2559 if (res > 0)
+
2560 res = -res;
+
2561
+
2562 return res;
+
2563}
+
+
2564
+
2565struct fuse_retrieve_req {
+
2566 struct fuse_notify_req nreq;
+
2567 void *cookie;
+
2568};
+
2569
+
2570static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
2571 fuse_req_t req, fuse_ino_t ino,
+
2572 const void *inarg,
+
2573 const struct fuse_buf *ibuf)
+
2574{
+
2575 struct fuse_session *se = req->se;
+
2576 struct fuse_retrieve_req *rreq =
+
2577 container_of(nreq, struct fuse_retrieve_req, nreq);
+
2578 const struct fuse_notify_retrieve_in *arg = inarg;
+
2579 struct fuse_bufvec bufv = {
+
2580 .buf[0] = *ibuf,
+
2581 .count = 1,
+
2582 };
+
2583
+
2584 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
2585 bufv.buf[0].mem = PARAM(arg);
+
2586
+
2587 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
2588 sizeof(struct fuse_notify_retrieve_in);
+
2589
+
2590 if (bufv.buf[0].size < arg->size) {
+
2591 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
2592 fuse_reply_none(req);
+
2593 goto out;
+
2594 }
+
2595 bufv.buf[0].size = arg->size;
+
2596
+
2597 if (se->op.retrieve_reply) {
+
2598 se->op.retrieve_reply(req, rreq->cookie, ino,
+
2599 arg->offset, &bufv);
+
2600 } else {
+
2601 fuse_reply_none(req);
+
2602 }
+
2603out:
+
2604 free(rreq);
+
2605 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
2606 fuse_ll_clear_pipe(se);
+
2607}
+
2608
+
+
2609int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
2610 size_t size, off_t offset, void *cookie)
+
2611{
+
2612 struct fuse_notify_retrieve_out outarg;
+
2613 struct iovec iov[2];
+
2614 struct fuse_retrieve_req *rreq;
+
2615 int err;
+
2616
+
2617 if (!se)
+
2618 return -EINVAL;
+
2619
+
2620 if (se->conn.proto_minor < 15)
+
2621 return -ENOSYS;
+
2622
+
2623 rreq = malloc(sizeof(*rreq));
+
2624 if (rreq == NULL)
+
2625 return -ENOMEM;
+
2626
+
2627 pthread_mutex_lock(&se->lock);
+
2628 rreq->cookie = cookie;
+
2629 rreq->nreq.unique = se->notify_ctr++;
+
2630 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
2631 list_add_nreq(&rreq->nreq, &se->notify_list);
+
2632 pthread_mutex_unlock(&se->lock);
+
2633
+
2634 outarg.notify_unique = rreq->nreq.unique;
+
2635 outarg.nodeid = ino;
+
2636 outarg.offset = offset;
+
2637 outarg.size = size;
+
2638 outarg.padding = 0;
+
2639
+
2640 iov[1].iov_base = &outarg;
+
2641 iov[1].iov_len = sizeof(outarg);
+
2642
+
2643 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
2644 if (err) {
+
2645 pthread_mutex_lock(&se->lock);
+
2646 list_del_nreq(&rreq->nreq);
+
2647 pthread_mutex_unlock(&se->lock);
+
2648 free(rreq);
+
2649 }
+
2650
+
2651 return err;
+
2652}
+
+
2653
+
+ +
2655{
+
2656 return req->se->userdata;
+
2657}
+
+
2658
+
+ +
2660{
+
2661 return &req->ctx;
+
2662}
+
+
2663
+
+ +
2665 void *data)
+
2666{
+
2667 pthread_mutex_lock(&req->lock);
+
2668 pthread_mutex_lock(&req->se->lock);
+
2669 req->u.ni.func = func;
+
2670 req->u.ni.data = data;
+
2671 pthread_mutex_unlock(&req->se->lock);
+
2672 if (req->interrupted && func)
+
2673 func(req, data);
+
2674 pthread_mutex_unlock(&req->lock);
+
2675}
+
+
2676
+
+ +
2678{
+
2679 int interrupted;
+
2680
+
2681 pthread_mutex_lock(&req->se->lock);
+
2682 interrupted = req->interrupted;
+
2683 pthread_mutex_unlock(&req->se->lock);
+
2684
+
2685 return interrupted;
+
2686}
+
+
2687
+
2688static struct {
+
2689 void (*func)(fuse_req_t, fuse_ino_t, const void *);
+
2690 const char *name;
+
2691} fuse_ll_ops[] = {
+
2692 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
2693 [FUSE_FORGET] = { do_forget, "FORGET" },
+
2694 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
2695 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
2696 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
2697 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
2698 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
2699 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
2700 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
2701 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
2702 [FUSE_RENAME] = { do_rename, "RENAME" },
+
2703 [FUSE_LINK] = { do_link, "LINK" },
+
2704 [FUSE_OPEN] = { do_open, "OPEN" },
+
2705 [FUSE_READ] = { do_read, "READ" },
+
2706 [FUSE_WRITE] = { do_write, "WRITE" },
+
2707 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
2708 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
2709 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
2710 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
2711 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
2712 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
2713 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
2714 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
2715 [FUSE_INIT] = { do_init, "INIT" },
+
2716 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
2717 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
2718 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
2719 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
2720 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
2721 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
2722 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
2723 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
2724 [FUSE_CREATE] = { do_create, "CREATE" },
+
2725 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
2726 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
2727 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
2728 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
2729 [FUSE_POLL] = { do_poll, "POLL" },
+
2730 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
2731 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
2732 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
2733 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
2734 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
2735 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
2736 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
2737 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
2738 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
2739};
+
2740
+
2741/*
+
2742 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
2743 * Without ABI compatibility we could use the size of the array.
+
2744 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
2745 */
+
2746#define FUSE_MAXOP (CUSE_INIT + 1)
+
2747
+
2748static const char *opname(enum fuse_opcode opcode)
+
2749{
+
2750 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
2751 return "???";
+
2752 else
+
2753 return fuse_ll_ops[opcode].name;
+
2754}
+
2755
+
2756static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
2757 struct fuse_bufvec *src)
+
2758{
+
2759 ssize_t res = fuse_buf_copy(dst, src, 0);
+
2760 if (res < 0) {
+
2761 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
2762 return res;
+
2763 }
+
2764 if ((size_t)res < fuse_buf_size(dst)) {
+
2765 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
2766 return -1;
+
2767 }
+
2768 return 0;
+
2769}
+
2770
+
+
2771void fuse_session_process_buf(struct fuse_session *se,
+
2772 const struct fuse_buf *buf)
+
2773{
+
2774 fuse_session_process_buf_internal(se, buf, NULL);
+
2775}
+
+
2776
+
2777/* libfuse internal handler */
+
2778void fuse_session_process_buf_internal(struct fuse_session *se,
+
2779 const struct fuse_buf *buf, struct fuse_chan *ch)
+
2780{
+
2781 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
2782 sizeof(struct fuse_write_in);
+
2783 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
2784 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
2785 struct fuse_in_header *in;
+
2786 const void *inarg;
+
2787 struct fuse_req *req;
+
2788 void *mbuf = NULL;
+
2789 int err;
+
2790 int res;
+
2791
+
2792 if (buf->flags & FUSE_BUF_IS_FD) {
+
2793 if (buf->size < tmpbuf.buf[0].size)
+
2794 tmpbuf.buf[0].size = buf->size;
+
2795
+
2796 mbuf = malloc(tmpbuf.buf[0].size);
+
2797 if (mbuf == NULL) {
+
2798 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
2799 goto clear_pipe;
+
2800 }
+
2801 tmpbuf.buf[0].mem = mbuf;
+
2802
+
2803 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2804 if (res < 0)
+
2805 goto clear_pipe;
+
2806
+
2807 in = mbuf;
+
2808 } else {
+
2809 in = buf->mem;
+
2810 }
+
2811
+
2812 if (se->debug) {
+
2813 fuse_log(FUSE_LOG_DEBUG,
+
2814 "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
2815 (unsigned long long) in->unique,
+
2816 opname((enum fuse_opcode) in->opcode), in->opcode,
+
2817 (unsigned long long) in->nodeid, buf->size, in->pid);
+
2818 }
+
2819
+
2820 req = fuse_ll_alloc_req(se);
+
2821 if (req == NULL) {
+
2822 struct fuse_out_header out = {
+
2823 .unique = in->unique,
+
2824 .error = -ENOMEM,
+
2825 };
+
2826 struct iovec iov = {
+
2827 .iov_base = &out,
+
2828 .iov_len = sizeof(struct fuse_out_header),
+
2829 };
+
2830
+
2831 fuse_send_msg(se, ch, &iov, 1);
+
2832 goto clear_pipe;
+
2833 }
+
2834
+
2835 req->unique = in->unique;
+
2836 req->ctx.uid = in->uid;
+
2837 req->ctx.gid = in->gid;
+
2838 req->ctx.pid = in->pid;
+
2839 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
2840
+
2841 err = EIO;
+
2842 if (!se->got_init) {
+
2843 enum fuse_opcode expected;
+
2844
+
2845 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
2846 if (in->opcode != expected)
+
2847 goto reply_err;
+
2848 } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+
2849 goto reply_err;
+
2850
+
2851 err = EACCES;
+
2852 /* Implement -o allow_root */
+
2853 if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
+
2854 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+
2855 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+
2856 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+
2857 in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+
2858 in->opcode != FUSE_NOTIFY_REPLY &&
+
2859 in->opcode != FUSE_READDIRPLUS)
+
2860 goto reply_err;
+
2861
+
2862 err = ENOSYS;
+
2863 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
2864 goto reply_err;
+
2865 /* Do not process interrupt request */
+
2866 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
2867 if (se->debug)
+
2868 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
2869 goto reply_err;
+
2870 }
+
2871 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
2872 struct fuse_req *intr;
+
2873 pthread_mutex_lock(&se->lock);
+
2874 intr = check_interrupt(se, req);
+
2875 list_add_req(req, &se->list);
+
2876 pthread_mutex_unlock(&se->lock);
+
2877 if (intr)
+
2878 fuse_reply_err(intr, EAGAIN);
+
2879 }
+
2880
+
2881 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
2882 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
2883 in->opcode != FUSE_NOTIFY_REPLY) {
+
2884 void *newmbuf;
+
2885
+
2886 err = ENOMEM;
+
2887 newmbuf = realloc(mbuf, buf->size);
+
2888 if (newmbuf == NULL)
+
2889 goto reply_err;
+
2890 mbuf = newmbuf;
+
2891
+
2892 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
2893 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
2894
+
2895 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2896 err = -res;
+
2897 if (res < 0)
+
2898 goto reply_err;
+
2899
+
2900 in = mbuf;
+
2901 }
+
2902
+
2903 inarg = (void *) &in[1];
+
2904 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
2905 do_write_buf(req, in->nodeid, inarg, buf);
+
2906 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
2907 do_notify_reply(req, in->nodeid, inarg, buf);
+
2908 else
+
2909 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
2910
+
2911out_free:
+
2912 free(mbuf);
+
2913 return;
+
2914
+
2915reply_err:
+
2916 fuse_reply_err(req, err);
+
2917clear_pipe:
+
2918 if (buf->flags & FUSE_BUF_IS_FD)
+
2919 fuse_ll_clear_pipe(se);
+
2920 goto out_free;
+
2921}
+
2922
+
2923#define LL_OPTION(n,o,v) \
+
2924 { n, offsetof(struct fuse_session, o), v }
+
2925
+
2926static const struct fuse_opt fuse_ll_opts[] = {
+
2927 LL_OPTION("debug", debug, 1),
+
2928 LL_OPTION("-d", debug, 1),
+
2929 LL_OPTION("--debug", debug, 1),
+
2930 LL_OPTION("allow_root", deny_others, 1),
+ +
2932};
+
2933
+
+ +
2935{
+
2936 printf("using FUSE kernel interface version %i.%i\n",
+
2937 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
2938 fuse_mount_version();
+
2939}
+
+
2940
+
+ +
2942{
+
2943 /* These are not all options, but the ones that are
+
2944 potentially of interest to an end-user */
+
2945 printf(
+
2946" -o allow_other allow access by all users\n"
+
2947" -o allow_root allow access by root\n"
+
2948" -o auto_unmount auto unmount on process termination\n");
+
2949}
+
+
2950
+
+
2951void fuse_session_destroy(struct fuse_session *se)
+
2952{
+
2953 struct fuse_ll_pipe *llp;
+
2954
+
2955 if (se->got_init && !se->got_destroy) {
+
2956 if (se->op.destroy)
+
2957 se->op.destroy(se->userdata);
+
2958 }
+
2959 llp = pthread_getspecific(se->pipe_key);
+
2960 if (llp != NULL)
+
2961 fuse_ll_pipe_free(llp);
+
2962 pthread_key_delete(se->pipe_key);
+
2963 pthread_mutex_destroy(&se->lock);
+
2964 free(se->cuse_data);
+
2965 if (se->fd != -1)
+
2966 close(se->fd);
+
2967 if (se->io != NULL)
+
2968 free(se->io);
+
2969 destroy_mount_opts(se->mo);
+
2970 free(se);
+
2971}
+
+
2972
+
2973
+
2974static void fuse_ll_pipe_destructor(void *data)
+
2975{
+
2976 struct fuse_ll_pipe *llp = data;
+
2977 fuse_ll_pipe_free(llp);
+
2978}
+
2979
+
2980void fuse_buf_free(struct fuse_buf *buf)
+
2981{
+
2982 if (buf->mem == NULL)
+
2983 return;
+
2984
+
2985 size_t write_header_sz =
+
2986 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
2987
+
2988 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
2989 free(ptr);
+
2990 buf->mem = NULL;
+
2991}
+
2992
+
2993/*
+
2994 * This is used to allocate buffers that hold fuse requests
+
2995 */
+
2996static void *buf_alloc(size_t size, bool internal)
+
2997{
+
2998 /*
+
2999 * For libfuse internal caller add in alignment. That cannot be done
+
3000 * for an external caller, as it is not guaranteed that the external
+
3001 * caller frees the raw pointer.
+
3002 */
+
3003 if (internal) {
+
3004 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3005 sizeof(struct fuse_write_in);
+
3006 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3007
+
3008 char *buf = aligned_alloc(pagesize, new_size);
+
3009 if (buf == NULL)
+
3010 return NULL;
+
3011
+
3012 buf += pagesize - write_header_sz;
+
3013
+
3014 return buf;
+
3015 } else {
+
3016 return malloc(size);
+
3017 }
+
3018}
+
3019
+
3020/*
+
3021 *@param internal true if called from libfuse internal code
+
3022 */
+
3023static int _fuse_session_receive_buf(struct fuse_session *se,
+
3024 struct fuse_buf *buf, struct fuse_chan *ch,
+
3025 bool internal)
+
3026{
+
3027 int err;
+
3028 ssize_t res;
+
3029 size_t bufsize = se->bufsize;
+
3030#ifdef HAVE_SPLICE
+
3031 struct fuse_ll_pipe *llp;
+
3032 struct fuse_buf tmpbuf;
+
3033
+
3034 if (se->conn.proto_minor < 14 ||
+
3035 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3036 goto fallback;
+
3037
+
3038 llp = fuse_ll_get_pipe(se);
+
3039 if (llp == NULL)
+
3040 goto fallback;
+
3041
+
3042 if (llp->size < bufsize) {
+
3043 if (llp->can_grow) {
+
3044 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3045 if (res == -1) {
+
3046 llp->can_grow = 0;
+
3047 res = grow_pipe_to_max(llp->pipe[0]);
+
3048 if (res > 0)
+
3049 llp->size = res;
+
3050 goto fallback;
+
3051 }
+
3052 llp->size = res;
+
3053 }
+
3054 if (llp->size < bufsize)
+
3055 goto fallback;
+
3056 }
+
3057
+
3058 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3059 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3060 llp->pipe[1], NULL, bufsize, 0,
+
3061 se->userdata);
+
3062 } else {
+
3063 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3064 bufsize, 0);
+
3065 }
+
3066 err = errno;
+
3067
+
3068 if (fuse_session_exited(se))
+
3069 return 0;
+
3070
+
3071 if (res == -1) {
+
3072 if (err == ENODEV) {
+
3073 /* Filesystem was unmounted, or connection was aborted
+
3074 via /sys/fs/fuse/connections */
+ +
3076 return 0;
+
3077 }
+
3078 if (err != EINTR && err != EAGAIN)
+
3079 perror("fuse: splice from device");
+
3080 return -err;
+
3081 }
+
3082
+
3083 if (res < sizeof(struct fuse_in_header)) {
+
3084 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3085 return -EIO;
+
3086 }
+
3087
+
3088 tmpbuf = (struct fuse_buf){
+
3089 .size = res,
+
3090 .flags = FUSE_BUF_IS_FD,
+
3091 .fd = llp->pipe[0],
+
3092 };
+
3093
+
3094 /*
+
3095 * Don't bother with zero copy for small requests.
+
3096 * fuse_loop_mt() needs to check for FORGET so this more than
+
3097 * just an optimization.
+
3098 */
+
3099 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3100 pagesize) {
+
3101 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3102 struct fuse_bufvec dst = { .count = 1 };
+
3103
+
3104 if (!buf->mem) {
+
3105 buf->mem = buf_alloc(se->bufsize, internal);
+
3106 if (!buf->mem) {
+
3107 fuse_log(
+
3108 FUSE_LOG_ERR,
+
3109 "fuse: failed to allocate read buffer\n");
+
3110 return -ENOMEM;
+
3111 }
+
3112 buf->mem_size = se->bufsize;
+
3113 if (internal)
+
3114 se->buf_reallocable = true;
+
3115 }
+
3116 buf->size = se->bufsize;
+
3117 buf->flags = 0;
+
3118 dst.buf[0] = *buf;
+
3119
+
3120 res = fuse_buf_copy(&dst, &src, 0);
+
3121 if (res < 0) {
+
3122 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3123 strerror(-res));
+
3124 fuse_ll_clear_pipe(se);
+
3125 return res;
+
3126 }
+
3127 if (res < tmpbuf.size) {
+
3128 fuse_log(FUSE_LOG_ERR,
+
3129 "fuse: copy from pipe: short read\n");
+
3130 fuse_ll_clear_pipe(se);
+
3131 return -EIO;
+
3132 }
+
3133 assert(res == tmpbuf.size);
+
3134
+
3135 } else {
+
3136 /* Don't overwrite buf->mem, as that would cause a leak */
+
3137 buf->fd = tmpbuf.fd;
+
3138 buf->flags = tmpbuf.flags;
+
3139 }
+
3140 buf->size = tmpbuf.size;
+
3141
+
3142 return res;
+
3143
+
3144fallback:
+
3145#endif
+
3146 if (!buf->mem) {
+
3147 buf->mem = buf_alloc(se->bufsize, internal);
+
3148 if (!buf->mem) {
+
3149 fuse_log(FUSE_LOG_ERR,
+
3150 "fuse: failed to allocate read buffer\n");
+
3151 return -ENOMEM;
+
3152 }
+
3153 buf->mem_size = se->bufsize;
+
3154 if (internal)
+
3155 se->buf_reallocable = true;
+
3156 }
+
3157
+
3158restart:
+
3159 if (se->buf_reallocable)
+
3160 bufsize = buf->mem_size;
+
3161 if (se->io != NULL) {
+
3162 /* se->io->read is never NULL if se->io is not NULL as
+
3163 specified by fuse_session_custom_io()*/
+
3164 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
3165 se->userdata);
+
3166 } else {
+
3167 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
3168 }
+
3169 err = errno;
+
3170
+
3171 if (fuse_session_exited(se))
+
3172 return 0;
+
3173 if (res == -1) {
+
3174 if (err == EINVAL && se->buf_reallocable &&
+
3175 se->bufsize > buf->mem_size) {
+
3176 void *newbuf = buf_alloc(se->bufsize, internal);
+
3177 if (!newbuf) {
+
3178 fuse_log(
+
3179 FUSE_LOG_ERR,
+
3180 "fuse: failed to (re)allocate read buffer\n");
+
3181 return -ENOMEM;
+
3182 }
+
3183 fuse_buf_free(buf);
+
3184 buf->mem = newbuf;
+
3185 buf->mem_size = se->bufsize;
+
3186 se->buf_reallocable = true;
+
3187 goto restart;
+
3188 }
+
3189
+
3190 /* ENOENT means the operation was interrupted, it's safe
+
3191 to restart */
+
3192 if (err == ENOENT)
+
3193 goto restart;
+
3194
+
3195 if (err == ENODEV) {
+
3196 /* Filesystem was unmounted, or connection was aborted
+
3197 via /sys/fs/fuse/connections */
+ +
3199 return 0;
+
3200 }
+
3201 /* Errors occurring during normal operation: EINTR (read
+
3202 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
3203 umounted) */
+
3204 if (err != EINTR && err != EAGAIN)
+
3205 perror("fuse: reading device");
+
3206 return -err;
+
3207 }
+
3208 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
3209 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
3210 return -EIO;
+
3211 }
+
3212
+
3213 buf->size = res;
+
3214
+
3215 return res;
+
3216}
+
3217
+
+
3218int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
3219{
+
3220 return _fuse_session_receive_buf(se, buf, NULL, false);
+
3221}
+
+
3222
+
3223/* libfuse internal handler */
+
3224int fuse_session_receive_buf_internal(struct fuse_session *se,
+
3225 struct fuse_buf *buf,
+
3226 struct fuse_chan *ch)
+
3227{
+
3228 return _fuse_session_receive_buf(se, buf, ch, true);
+
3229}
+
3230
+
3231struct fuse_session *
+
3232fuse_session_new_versioned(struct fuse_args *args,
+
3233 const struct fuse_lowlevel_ops *op, size_t op_size,
+
3234 struct libfuse_version *version, void *userdata)
+
3235{
+
3236 int err;
+
3237 struct fuse_session *se;
+
3238 struct mount_opts *mo;
+
3239
+
3240 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
3241 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3242 op_size = sizeof(struct fuse_lowlevel_ops);
+
3243 }
+
3244
+
3245 if (args->argc == 0) {
+
3246 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
3247 return NULL;
+
3248 }
+
3249
+
3250 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
3251 if (se == NULL) {
+
3252 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
3253 goto out1;
+
3254 }
+
3255 se->fd = -1;
+
3256 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
3257 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
3258 se->conn.max_readahead = UINT_MAX;
+
3259
+
3260 /* Parse options */
+
3261 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
3262 goto out2;
+
3263 if(se->deny_others) {
+
3264 /* Allowing access only by root is done by instructing
+
3265 * kernel to allow access by everyone, and then restricting
+
3266 * access to root and mountpoint owner in libfuse.
+
3267 */
+
3268 // We may be adding the option a second time, but
+
3269 // that doesn't hurt.
+
3270 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
3271 goto out2;
+
3272 }
+
3273 mo = parse_mount_opts(args);
+
3274 if (mo == NULL)
+
3275 goto out3;
+
3276
+
3277 if(args->argc == 1 &&
+
3278 args->argv[0][0] == '-') {
+
3279 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
3280 "will be ignored\n");
+
3281 } else if (args->argc != 1) {
+
3282 int i;
+
3283 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
3284 for(i = 1; i < args->argc-1; i++)
+
3285 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
3286 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
3287 goto out4;
+
3288 }
+
3289
+
3290 if (se->debug)
+
3291 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
3292
+
3293 list_init_req(&se->list);
+
3294 list_init_req(&se->interrupts);
+
3295 list_init_nreq(&se->notify_list);
+
3296 se->notify_ctr = 1;
+
3297 pthread_mutex_init(&se->lock, NULL);
+
3298
+
3299 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
3300 if (err) {
+
3301 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
3302 strerror(err));
+
3303 goto out5;
+
3304 }
+
3305
+
3306 memcpy(&se->op, op, op_size);
+
3307 se->owner = getuid();
+
3308 se->userdata = userdata;
+
3309
+
3310 se->mo = mo;
+
3311
+
3312 /* Fuse server application should pass the version it was compiled
+
3313 * against and pass it. If a libfuse version accidentally introduces an
+
3314 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
3315 * by checking the version numbers.
+
3316 */
+
3317 se->version = *version;
+
3318
+
3319 return se;
+
3320
+
3321out5:
+
3322 pthread_mutex_destroy(&se->lock);
+
3323out4:
+
3324 fuse_opt_free_args(args);
+
3325out3:
+
3326 if (mo != NULL)
+
3327 destroy_mount_opts(mo);
+
3328out2:
+
3329 free(se);
+
3330out1:
+
3331 return NULL;
+
3332}
+
3333
+
3334struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3335 const struct fuse_lowlevel_ops *op,
+
3336 size_t op_size, void *userdata);
+
3337struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3338 const struct fuse_lowlevel_ops *op,
+
3339 size_t op_size,
+
3340 void *userdata)
+
3341{
+
3342 /* unknown version */
+
3343 struct libfuse_version version = { 0 };
+
3344
+
3345 return fuse_session_new_versioned(args, op, op_size, &version,
+
3346 userdata);
+
3347}
+
3348
+
3349FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
3350int fuse_session_custom_io_317(struct fuse_session *se,
+
3351 const struct fuse_custom_io *io, size_t op_size, int fd)
+
3352{
+
3353 if (sizeof(struct fuse_custom_io) < op_size) {
+
3354 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3355 op_size = sizeof(struct fuse_custom_io);
+
3356 }
+
3357
+
3358 if (fd < 0) {
+
3359 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
3360 "fuse_session_custom_io()\n", fd);
+
3361 return -EBADF;
+
3362 }
+
3363 if (io == NULL) {
+
3364 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
3365 "fuse_session_custom_io()\n");
+
3366 return -EINVAL;
+
3367 } else if (io->read == NULL || io->writev == NULL) {
+
3368 /* If the user provides their own file descriptor, we can't
+
3369 guarantee that the default behavior of the io operations made
+
3370 in libfuse will function properly. Therefore, we enforce the
+
3371 user to implement these io operations when using custom io. */
+
3372 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
3373 "implement both io->read() and io->writev\n");
+
3374 return -EINVAL;
+
3375 }
+
3376
+
3377 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
3378 if (se->io == NULL) {
+
3379 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
3380 "Error: %s\n", strerror(errno));
+
3381 return -errno;
+
3382 }
+
3383
+
3384 se->fd = fd;
+
3385 memcpy(se->io, io, op_size);
+
3386 return 0;
+
3387}
+
3388
+
3389int fuse_session_custom_io_30(struct fuse_session *se,
+
3390 const struct fuse_custom_io *io, int fd);
+
3391FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
3392int fuse_session_custom_io_30(struct fuse_session *se,
+
3393 const struct fuse_custom_io *io, int fd)
+
3394{
+
3395 return fuse_session_custom_io_317(se, io,
+
3396 offsetof(struct fuse_custom_io, clone_fd), fd);
+
3397}
+
3398
+
+
3399int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
3400{
+
3401 int fd;
+
3402
+
3403 if (mountpoint == NULL) {
+
3404 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
3405 return -1;
+
3406 }
+
3407
+
3408 /*
+
3409 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
3410 * would ensue.
+
3411 */
+
3412 do {
+
3413 fd = open("/dev/null", O_RDWR);
+
3414 if (fd > 2)
+
3415 close(fd);
+
3416 } while (fd >= 0 && fd <= 2);
+
3417
+
3418 /*
+
3419 * To allow FUSE daemons to run without privileges, the caller may open
+
3420 * /dev/fuse before launching the file system and pass on the file
+
3421 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
3422 * parent process takes care of performing the mount in this case.
+
3423 */
+
3424 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
3425 if (fd != -1) {
+
3426 if (fcntl(fd, F_GETFD) == -1) {
+
3427 fuse_log(FUSE_LOG_ERR,
+
3428 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
3429 fd);
+
3430 return -1;
+
3431 }
+
3432 se->fd = fd;
+
3433 return 0;
+
3434 }
+
3435
+
3436 /* Open channel */
+
3437 fd = fuse_kern_mount(mountpoint, se->mo);
+
3438 if (fd == -1)
+
3439 return -1;
+
3440 se->fd = fd;
+
3441
+
3442 /* Save mountpoint */
+
3443 se->mountpoint = strdup(mountpoint);
+
3444 if (se->mountpoint == NULL)
+
3445 goto error_out;
+
3446
+
3447 return 0;
+
3448
+
3449error_out:
+
3450 fuse_kern_unmount(mountpoint, fd);
+
3451 return -1;
+
3452}
+
+
3453
+
+
3454int fuse_session_fd(struct fuse_session *se)
+
3455{
+
3456 return se->fd;
+
3457}
+
+
3458
+
+
3459void fuse_session_unmount(struct fuse_session *se)
+
3460{
+
3461 if (se->mountpoint != NULL) {
+
3462 fuse_kern_unmount(se->mountpoint, se->fd);
+
3463 se->fd = -1;
+
3464 free(se->mountpoint);
+
3465 se->mountpoint = NULL;
+
3466 }
+
3467}
+
+
3468
+
3469#ifdef linux
+
3470int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3471{
+
3472 char *buf;
+
3473 size_t bufsize = 1024;
+
3474 char path[128];
+
3475 int ret;
+
3476 int fd;
+
3477 unsigned long pid = req->ctx.pid;
+
3478 char *s;
+
3479
+
3480 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
3481
+
3482retry:
+
3483 buf = malloc(bufsize);
+
3484 if (buf == NULL)
+
3485 return -ENOMEM;
+
3486
+
3487 ret = -EIO;
+
3488 fd = open(path, O_RDONLY);
+
3489 if (fd == -1)
+
3490 goto out_free;
+
3491
+
3492 ret = read(fd, buf, bufsize);
+
3493 close(fd);
+
3494 if (ret < 0) {
+
3495 ret = -EIO;
+
3496 goto out_free;
+
3497 }
+
3498
+
3499 if ((size_t)ret == bufsize) {
+
3500 free(buf);
+
3501 bufsize *= 4;
+
3502 goto retry;
+
3503 }
+
3504
+
3505 buf[ret] = '\0';
+
3506 ret = -EIO;
+
3507 s = strstr(buf, "\nGroups:");
+
3508 if (s == NULL)
+
3509 goto out_free;
+
3510
+
3511 s += 8;
+
3512 ret = 0;
+
3513 while (1) {
+
3514 char *end;
+
3515 unsigned long val = strtoul(s, &end, 0);
+
3516 if (end == s)
+
3517 break;
+
3518
+
3519 s = end;
+
3520 if (ret < size)
+
3521 list[ret] = val;
+
3522 ret++;
+
3523 }
+
3524
+
3525out_free:
+
3526 free(buf);
+
3527 return ret;
+
3528}
+
3529#else /* linux */
+
3530/*
+
3531 * This is currently not implemented on other than Linux...
+
3532 */
+
+
3533int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3534{
+
3535 (void) req; (void) size; (void) list;
+
3536 return -ENOSYS;
+
3537}
+
+
3538#endif
+
3539
+
3540/* Prevent spurious data race warning - we don't care
+
3541 * about races for this flag */
+
3542__attribute__((no_sanitize_thread))
+
3543void fuse_session_exit(struct fuse_session *se)
+
3544{
+
3545 se->exited = 1;
+
3546}
+
3547
+
3548__attribute__((no_sanitize_thread))
+
3549void fuse_session_reset(struct fuse_session *se)
+
3550{
+
3551 se->exited = 0;
+
3552 se->error = 0;
+
3553}
+
3554
+
3555__attribute__((no_sanitize_thread))
+
3556int fuse_session_exited(struct fuse_session *se)
+
3557{
+
3558 return se->exited;
+
3559}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
@ FUSE_BUF_IS_FD
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
struct fuse_req * fuse_req_t
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
uint64_t fuse_ino_t
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__misc_8h_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..16c74ec --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__opt_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..bd2b3f4 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__opt_8c_source.html @@ -0,0 +1,516 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
+
143
+
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2fuse__signals_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..9452bd7 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2fuse__signals_8c_source.html @@ -0,0 +1,269 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
26static int ignore_sigs[] = { SIGPIPE};
+
27static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
28static struct fuse_session *fuse_instance;
+
29
+
30#define BT_STACK_SZ (1024 * 1024)
+
31static void *backtrace_buffer[BT_STACK_SZ];
+
32
+
33static void dump_stack(void)
+
34{
+
35#ifdef HAVE_BACKTRACE
+
36 char **strings;
+
37
+
38 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
39 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
40
+
41 if (strings == NULL) {
+
42 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
43 strerror(errno));
+
44 return;
+
45 }
+
46
+
47 for (int idx = 0; idx < nptrs; idx++)
+
48 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
49
+
50 free(strings);
+
51#endif
+
52}
+
53
+
54static void exit_handler(int sig)
+
55{
+
56 if (fuse_instance == NULL)
+
57 return;
+
58
+
59 fuse_session_exit(fuse_instance);
+
60
+
61 if (sig < 0) {
+
62 fuse_log(FUSE_LOG_ERR,
+
63 "assertion error: signal value <= 0\n");
+
64 dump_stack();
+
65 abort();
+
66 fuse_instance->error = sig;
+
67 }
+
68
+
69 fuse_instance->error = sig;
+
70}
+
71
+
72static void exit_backtrace(int sig)
+
73{
+
74 if (fuse_instance == NULL)
+
75 return;
+
76
+
77 fuse_session_exit(fuse_instance);
+
78
+
79 fuse_remove_signal_handlers(fuse_instance);
+
80 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
81 dump_stack();
+
82 abort();
+
83}
+
84
+
85
+
86static void do_nothing(int sig)
+
87{
+
88 (void) sig;
+
89}
+
90
+
91static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
92{
+
93 struct sigaction sa;
+
94 struct sigaction old_sa;
+
95
+
96 memset(&sa, 0, sizeof(struct sigaction));
+
97 sa.sa_handler = remove ? SIG_DFL : handler;
+
98 sigemptyset(&(sa.sa_mask));
+
99 sa.sa_flags = 0;
+
100
+
101 if (sigaction(sig, NULL, &old_sa) == -1) {
+
102 perror("fuse: cannot get old signal handler");
+
103 return -1;
+
104 }
+
105
+
106 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
107 sigaction(sig, &sa, NULL) == -1) {
+
108 perror("fuse: cannot set signal handler");
+
109 return -1;
+
110 }
+
111 return 0;
+
112}
+
113
+
114static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
115 void (*handler)(int))
+
116{
+
117 for (int idx = 0; idx < nr_signals; idx++) {
+
118 int signal = signals[idx];
+
119
+
120 /*
+
121 * If we used SIG_IGN instead of the do_nothing function,
+
122 * then we would be unable to tell if we set SIG_IGN (and
+
123 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
124 * or if it was already set to SIG_IGN (and should be left
+
125 * untouched.
+
126 */
+
127 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
128 fuse_log(FUSE_LOG_ERR,
+
129 "Failed to install signal handler for sig %d\n",
+
130 signal);
+
131 return -1;
+
132 }
+
133 }
+
134
+
135 return 0;
+
136}
+
137
+
+
138int fuse_set_signal_handlers(struct fuse_session *se)
+
139{
+
140 size_t nr_signals;
+
141 int rc;
+
142
+
143 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
144 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
145 if (rc < 0)
+
146 return rc;
+
147
+
148 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
149 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
150 if (rc < 0)
+
151 return rc;
+
152
+
153 if (fuse_instance == NULL)
+
154 fuse_instance = se;
+
155 return 0;
+
156}
+
+
157
+
+
158int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
159{
+
160 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
161
+
162 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
163 exit_backtrace);
+
164 if (rc < 0)
+
165 return rc;
+
166
+
167 if (fuse_instance == NULL)
+
168 fuse_instance = se;
+
169
+
170 return 0;
+
171}
+
+
172
+
173static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
174 void (*handler)(int))
+
175{
+
176 for (int idx = 0; idx < nr_signals; idx++)
+
177 set_one_signal_handler(signals[idx], handler, 1);
+
178}
+
179
+
+
180void fuse_remove_signal_handlers(struct fuse_session *se)
+
181{
+
182 size_t nr_signals;
+
183
+
184 if (fuse_instance != se)
+
185 fuse_log(FUSE_LOG_ERR,
+
186 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
187 else
+
188 fuse_instance = NULL;
+
189
+
190 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
191 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
192
+
193 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
194 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
195
+
196 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
197 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
198}
+
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2helper_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2helper_8c_source.html new file mode 100644 index 0000000..7cc85a3 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2helper_8c_source.html @@ -0,0 +1,613 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
+ +
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
+ +
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
+
252
+
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
+
306
+
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond,cap) \
+
427 if (cond) conn->want_ext |= (cap)
+
428#define LL_DISABLE(cond,cap) \
+
429 if (cond) conn->want_ext &= ~(cap)
+
430
+
431 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
432 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
433
+
434 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
435 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
436
+
437 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
438 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
439
+
440 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
441 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
442
+
443 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
444 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
445
+
446 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
447 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
448
+
449 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
450 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
451
+
452 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
453 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
454
+
455 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
456 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
457}
+
+
458
+
+
459struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
460{
+
461 struct fuse_conn_info_opts *opts;
+
462
+
463 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
464 if(opts == NULL) {
+
465 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
466 return NULL;
+
467 }
+
468 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
469 free(opts);
+
470 return NULL;
+
471 }
+
472 return opts;
+
473}
+
+
474
+
+
475int fuse_open_channel(const char *mountpoint, const char* options)
+
476{
+
477 struct mount_opts *opts = NULL;
+
478 int fd = -1;
+
479 const char *argv[] = { "", "-o", options };
+
480 int argc = sizeof(argv) / sizeof(argv[0]);
+
481 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
482
+
483 opts = parse_mount_opts(&args);
+
484 if (opts == NULL)
+
485 return -1;
+
486
+
487 fd = fuse_kern_mount(mountpoint, opts);
+
488 destroy_mount_opts(opts);
+
489
+
490 return fd;
+
491}
+
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:475
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:459
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2modules_2iconv_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..3c65ade --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2modules_2iconv_8c_source.html @@ -0,0 +1,816 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571static void *iconv_init(struct fuse_conn_info *conn,
+
572 struct fuse_config *cfg)
+
573{
+
574 struct iconv *ic = iconv_get();
+
575 fuse_fs_init(ic->next, conn, cfg);
+
576 /* Don't touch cfg->nullpath_ok, we can work with
+
577 either */
+
578 return ic;
+
579}
+
580
+
581static void iconv_destroy(void *data)
+
582{
+
583 struct iconv *ic = data;
+
584 fuse_fs_destroy(ic->next);
+
585 iconv_close(ic->tofs);
+
586 iconv_close(ic->fromfs);
+
587 pthread_mutex_destroy(&ic->lock);
+
588 free(ic->from_code);
+
589 free(ic->to_code);
+
590 free(ic);
+
591}
+
592
+
593static const struct fuse_operations iconv_oper = {
+
594 .destroy = iconv_destroy,
+
595 .init = iconv_init,
+
596 .getattr = iconv_getattr,
+
597 .access = iconv_access,
+
598 .readlink = iconv_readlink,
+
599 .opendir = iconv_opendir,
+
600 .readdir = iconv_readdir,
+
601 .releasedir = iconv_releasedir,
+
602 .mknod = iconv_mknod,
+
603 .mkdir = iconv_mkdir,
+
604 .symlink = iconv_symlink,
+
605 .unlink = iconv_unlink,
+
606 .rmdir = iconv_rmdir,
+
607 .rename = iconv_rename,
+
608 .link = iconv_link,
+
609 .chmod = iconv_chmod,
+
610 .chown = iconv_chown,
+
611 .truncate = iconv_truncate,
+
612 .utimens = iconv_utimens,
+
613 .create = iconv_create,
+
614 .open = iconv_open_file,
+
615 .read_buf = iconv_read_buf,
+
616 .write_buf = iconv_write_buf,
+
617 .statfs = iconv_statfs,
+
618 .flush = iconv_flush,
+
619 .release = iconv_release,
+
620 .fsync = iconv_fsync,
+
621 .fsyncdir = iconv_fsyncdir,
+
622 .setxattr = iconv_setxattr,
+
623 .getxattr = iconv_getxattr,
+
624 .listxattr = iconv_listxattr,
+
625 .removexattr = iconv_removexattr,
+
626 .lock = iconv_lock,
+
627 .flock = iconv_flock,
+
628 .bmap = iconv_bmap,
+
629 .lseek = iconv_lseek,
+
630};
+
631
+
632static const struct fuse_opt iconv_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
636 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
638};
+
639
+
640static void iconv_help(void)
+
641{
+
642 char *charmap;
+
643 const char *old = setlocale(LC_CTYPE, "");
+
644
+
645 charmap = strdup(nl_langinfo(CODESET));
+
646 if (old)
+
647 setlocale(LC_CTYPE, old);
+
648 else
+
649 perror("setlocale");
+
650
+
651 printf(
+
652" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
653" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
654 charmap);
+
655 free(charmap);
+
656}
+
657
+
658static int iconv_opt_proc(void *data, const char *arg, int key,
+
659 struct fuse_args *outargs)
+
660{
+
661 (void) data; (void) arg; (void) outargs;
+
662
+
663 if (!key) {
+
664 iconv_help();
+
665 return -1;
+
666 }
+
667
+
668 return 1;
+
669}
+
670
+
671static struct fuse_fs *iconv_new(struct fuse_args *args,
+
672 struct fuse_fs *next[])
+
673{
+
674 struct fuse_fs *fs;
+
675 struct iconv *ic;
+
676 const char *old = NULL;
+
677 const char *from;
+
678 const char *to;
+
679
+
680 ic = calloc(1, sizeof(struct iconv));
+
681 if (ic == NULL) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
683 return NULL;
+
684 }
+
685
+
686 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
687 goto out_free;
+
688
+
689 if (!next[0] || next[1]) {
+
690 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
691 goto out_free;
+
692 }
+
693
+
694 from = ic->from_code ? ic->from_code : "UTF-8";
+
695 to = ic->to_code ? ic->to_code : "";
+
696 /* FIXME: detect charset equivalence? */
+
697 if (!to[0])
+
698 old = setlocale(LC_CTYPE, "");
+
699 ic->tofs = iconv_open(from, to);
+
700 if (ic->tofs == (iconv_t) -1) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
702 to, from);
+
703 goto out_free;
+
704 }
+
705 ic->fromfs = iconv_open(to, from);
+
706 if (ic->tofs == (iconv_t) -1) {
+
707 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
708 from, to);
+
709 goto out_iconv_close_to;
+
710 }
+
711 if (old) {
+
712 setlocale(LC_CTYPE, old);
+
713 old = NULL;
+
714 }
+
715
+
716 ic->next = next[0];
+
717 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
718 if (!fs)
+
719 goto out_iconv_close_from;
+
720
+
721 return fs;
+
722
+
723out_iconv_close_from:
+
724 iconv_close(ic->fromfs);
+
725out_iconv_close_to:
+
726 iconv_close(ic->tofs);
+
727out_free:
+
728 free(ic->from_code);
+
729 free(ic->to_code);
+
730 free(ic);
+
731 if (old) {
+
732 setlocale(LC_CTYPE, old);
+
733 }
+
734 return NULL;
+
735}
+
736
+
737FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1398
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2modules_2subdir_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..411f08e --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2modules_2subdir_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556static void *subdir_init(struct fuse_conn_info *conn,
+
557 struct fuse_config *cfg)
+
558{
+
559 struct subdir *d = subdir_get();
+
560 fuse_fs_init(d->next, conn, cfg);
+
561 /* Don't touch cfg->nullpath_ok, we can work with
+
562 either */
+
563 return d;
+
564}
+
565
+
566static void subdir_destroy(void *data)
+
567{
+
568 struct subdir *d = data;
+
569 fuse_fs_destroy(d->next);
+
570 free(d->base);
+
571 free(d);
+
572}
+
573
+
574static const struct fuse_operations subdir_oper = {
+
575 .destroy = subdir_destroy,
+
576 .init = subdir_init,
+
577 .getattr = subdir_getattr,
+
578 .access = subdir_access,
+
579 .readlink = subdir_readlink,
+
580 .opendir = subdir_opendir,
+
581 .readdir = subdir_readdir,
+
582 .releasedir = subdir_releasedir,
+
583 .mknod = subdir_mknod,
+
584 .mkdir = subdir_mkdir,
+
585 .symlink = subdir_symlink,
+
586 .unlink = subdir_unlink,
+
587 .rmdir = subdir_rmdir,
+
588 .rename = subdir_rename,
+
589 .link = subdir_link,
+
590 .chmod = subdir_chmod,
+
591 .chown = subdir_chown,
+
592 .truncate = subdir_truncate,
+
593 .utimens = subdir_utimens,
+
594 .create = subdir_create,
+
595 .open = subdir_open,
+
596 .read_buf = subdir_read_buf,
+
597 .write_buf = subdir_write_buf,
+
598 .statfs = subdir_statfs,
+
599 .flush = subdir_flush,
+
600 .release = subdir_release,
+
601 .fsync = subdir_fsync,
+
602 .fsyncdir = subdir_fsyncdir,
+
603 .setxattr = subdir_setxattr,
+
604 .getxattr = subdir_getxattr,
+
605 .listxattr = subdir_listxattr,
+
606 .removexattr = subdir_removexattr,
+
607 .lock = subdir_lock,
+
608 .flock = subdir_flock,
+
609 .bmap = subdir_bmap,
+
610 .lseek = subdir_lseek,
+
611};
+
612
+
613static const struct fuse_opt subdir_opts[] = {
+
614 FUSE_OPT_KEY("-h", 0),
+
615 FUSE_OPT_KEY("--help", 0),
+
616 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
617 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
618 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
620};
+
621
+
622static void subdir_help(void)
+
623{
+
624 printf(
+
625" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
626" -o [no]rellinks transform absolute symlinks to relative\n");
+
627}
+
628
+
629static int subdir_opt_proc(void *data, const char *arg, int key,
+
630 struct fuse_args *outargs)
+
631{
+
632 (void) data; (void) arg; (void) outargs;
+
633
+
634 if (!key) {
+
635 subdir_help();
+
636 return -1;
+
637 }
+
638
+
639 return 1;
+
640}
+
641
+
642static struct fuse_fs *subdir_new(struct fuse_args *args,
+
643 struct fuse_fs *next[])
+
644{
+
645 struct fuse_fs *fs;
+
646 struct subdir *d;
+
647
+
648 d = calloc(1, sizeof(struct subdir));
+
649 if (d == NULL) {
+
650 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
651 return NULL;
+
652 }
+
653
+
654 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
655 goto out_free;
+
656
+
657 if (!next[0] || next[1]) {
+
658 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
659 goto out_free;
+
660 }
+
661
+
662 if (!d->base) {
+
663 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
664 goto out_free;
+
665 }
+
666
+
667 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
668 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
669 if (!tmp) {
+
670 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
671 goto out_free;
+
672 }
+
673 d->base = tmp;
+
674 strcat(d->base, "/");
+
675 }
+
676 d->baselen = strlen(d->base);
+
677 d->next = next[0];
+
678 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
679 if (!fs)
+
680 goto out_free;
+
681 return fs;
+
682
+
683out_free:
+
684 free(d->base);
+
685 free(d);
+
686 return NULL;
+
687}
+
688
+
689FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1398
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2mount_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2mount_8c_source.html new file mode 100644 index 0000000..ec07931 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2mount_8c_source.html @@ -0,0 +1,792 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52
+
53#ifndef MS_DIRSYNC
+
54#define MS_DIRSYNC 128
+
55#endif
+
56
+
57enum {
+
58 KEY_KERN_FLAG,
+
59 KEY_KERN_OPT,
+
60 KEY_FUSERMOUNT_OPT,
+
61 KEY_SUBTYPE_OPT,
+
62 KEY_MTAB_OPT,
+
63 KEY_ALLOW_OTHER,
+
64 KEY_RO,
+
65};
+
66
+
67struct mount_opts {
+
68 int allow_other;
+
69 int flags;
+
70 int auto_unmount;
+
71 int blkdev;
+
72 char *fsname;
+
73 char *subtype;
+
74 char *subtype_opt;
+
75 char *mtab_opts;
+
76 char *fusermount_opts;
+
77 char *kernel_opts;
+
78 unsigned max_read;
+
79};
+
80
+
81#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
82
+
83static const struct fuse_opt fuse_mount_opts[] = {
+
84 FUSE_MOUNT_OPT("allow_other", allow_other),
+
85 FUSE_MOUNT_OPT("blkdev", blkdev),
+
86 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
87 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
88 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
89 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
90 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
91 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
92 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
95 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
96 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
103 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-r", KEY_RO),
+
105 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
106 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
122};
+
123
+
124/*
+
125 * Running fusermount by calling 'posix_spawn'
+
126 *
+
127 * @param out_pid might be NULL
+
128 */
+
129static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
130 char const * const argv[], pid_t *out_pid)
+
131{
+
132 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
133 pid_t pid;
+
134
+
135 /* See man 7 environ for the global environ pointer */
+
136
+
137 /* first try the install path */
+
138 int status = posix_spawn(&pid, full_path, action, NULL,
+
139 (char * const *) argv, environ);
+
140 if (status != 0) {
+
141 /* if that fails, try a system install */
+
142 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
143 (char * const *) argv, environ);
+
144 }
+
145
+
146 if (status != 0) {
+
147 fuse_log(FUSE_LOG_ERR,
+
148 "On calling fusermount posix_spawn failed: %s\n",
+
149 strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
+
345 FUSERMOUNT_PROG, strerror(-status));
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
394 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
+
410 strerror(-status));
+
411 return -1;
+
412 }
+
413 // passed to child now, so can close here.
+
414 close(fds[0]);
+
415
+
416 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
417 // process exits.
+
418 return 0;
+
419 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
420}
+
421
+
422static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
423 const char *opts, int quiet)
+
424{
+
425 int fds[2];
+
426 pid_t pid;
+
427 int res;
+
428
+
429 if (!mountpoint) {
+
430 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
431 return -1;
+
432 }
+
433
+
434 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
435 if(res == -1) {
+
436 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
437 FUSERMOUNT_PROG, strerror(errno));
+
438 return -1;
+
439 }
+
440
+
441 char arg_fd_entry[30];
+
442 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
443 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
444 /*
+
445 * This helps to identify the FD hold by parent process.
+
446 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
447 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
448 * One potential use case is to satisfy FD-Leak checks.
+
449 */
+
450 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
451 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
452
+
453 char const *const argv[] = {
+
454 FUSERMOUNT_PROG,
+
455 "-o", opts ? opts : "",
+
456 "--",
+
457 mountpoint,
+
458 NULL,
+
459 };
+
460
+
461
+
462 posix_spawn_file_actions_t action;
+
463 posix_spawn_file_actions_init(&action);
+
464
+
465 if (quiet) {
+
466 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
467 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
468 }
+
469 posix_spawn_file_actions_addclose(&action, fds[1]);
+
470
+
471 int status = fusermount_posix_spawn(&action, argv, &pid);
+
472
+
473 posix_spawn_file_actions_destroy(&action);
+
474
+
475 if(status != 0) {
+
476 close(fds[0]);
+
477 close(fds[1]);
+
478 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
+
479 FUSERMOUNT_PROG, strerror(-status));
+
480 return -1;
+
481 }
+
482
+
483 // passed to child now, so can close here.
+
484 close(fds[0]);
+
485
+
486 int fd = receive_fd(fds[1]);
+
487
+
488 if (!mo->auto_unmount) {
+
489 /* with auto_unmount option fusermount3 will not exit until
+
490 this socket is closed */
+
491 close(fds[1]);
+
492 waitpid(pid, NULL, 0); /* bury zombie */
+
493 }
+
494
+
495 if (fd >= 0)
+
496 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
497
+
498 return fd;
+
499}
+
500
+
501#ifndef O_CLOEXEC
+
502#define O_CLOEXEC 0
+
503#endif
+
504
+
505static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
506 const char *mnt_opts)
+
507{
+
508 char tmp[128];
+
509 const char *devname = "/dev/fuse";
+
510 char *source = NULL;
+
511 char *type = NULL;
+
512 struct stat stbuf;
+
513 int fd;
+
514 int res;
+
515
+
516 if (!mnt) {
+
517 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
518 return -1;
+
519 }
+
520
+
521 res = stat(mnt, &stbuf);
+
522 if (res == -1) {
+
523 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
524 mnt, strerror(errno));
+
525 return -1;
+
526 }
+
527
+
528 fd = open(devname, O_RDWR | O_CLOEXEC);
+
529 if (fd == -1) {
+
530 if (errno == ENODEV || errno == ENOENT)
+
531 fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n");
+
532 else
+
533 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
534 devname, strerror(errno));
+
535 return -1;
+
536 }
+
537 if (!O_CLOEXEC)
+
538 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
539
+
540 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
541 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
542
+
543 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
544 if (res == -1)
+
545 goto out_close;
+
546
+
547 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
548 (mo->subtype ? strlen(mo->subtype) : 0) +
+
549 strlen(devname) + 32);
+
550
+
551 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
552 if (!type || !source) {
+
553 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
554 goto out_close;
+
555 }
+
556
+
557 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
558 if (mo->subtype) {
+
559 strcat(type, ".");
+
560 strcat(type, mo->subtype);
+
561 }
+
562 strcpy(source,
+
563 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
564
+
565 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
566 if (res == -1 && errno == ENODEV && mo->subtype) {
+
567 /* Probably missing subtype support */
+
568 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
569 if (mo->fsname) {
+
570 if (!mo->blkdev)
+
571 sprintf(source, "%s#%s", mo->subtype,
+
572 mo->fsname);
+
573 } else {
+
574 strcpy(source, type);
+
575 }
+
576 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
577 }
+
578 if (res == -1) {
+
579 /*
+
580 * Maybe kernel doesn't support unprivileged mounts, in this
+
581 * case try falling back to fusermount3
+
582 */
+
583 if (errno == EPERM) {
+
584 res = -2;
+
585 } else {
+
586 int errno_save = errno;
+
587 if (mo->blkdev && errno == ENODEV &&
+
588 !fuse_mnt_check_fuseblk())
+
589 fuse_log(FUSE_LOG_ERR,
+
590 "fuse: 'fuseblk' support missing\n");
+
591 else
+
592 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
593 strerror(errno_save));
+
594 }
+
595
+
596 goto out_close;
+
597 }
+
598
+
599#ifndef IGNORE_MTAB
+
600 if (geteuid() == 0) {
+
601 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
602 res = -1;
+
603 if (!newmnt)
+
604 goto out_umount;
+
605
+
606 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
607 mnt_opts);
+
608 free(newmnt);
+
609 if (res == -1)
+
610 goto out_umount;
+
611 }
+
612#endif /* IGNORE_MTAB */
+
613 free(type);
+
614 free(source);
+
615
+
616 return fd;
+
617
+
618out_umount:
+
619 umount2(mnt, 2); /* lazy umount */
+
620out_close:
+
621 free(type);
+
622 free(source);
+
623 close(fd);
+
624 return res;
+
625}
+
626
+
627static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
628{
+
629 int i;
+
630
+
631 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
632 return -1;
+
633
+
634 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
635 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
636 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
637 return -1;
+
638 }
+
639 return 0;
+
640}
+
641
+
642struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
643{
+
644 struct mount_opts *mo;
+
645
+
646 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
647 if (mo == NULL)
+
648 return NULL;
+
649
+
650 memset(mo, 0, sizeof(struct mount_opts));
+
651 mo->flags = MS_NOSUID | MS_NODEV;
+
652
+
653 if (args &&
+
654 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
655 goto err_out;
+
656
+
657 return mo;
+
658
+
659err_out:
+
660 destroy_mount_opts(mo);
+
661 return NULL;
+
662}
+
663
+
664void destroy_mount_opts(struct mount_opts *mo)
+
665{
+
666 free(mo->fsname);
+
667 free(mo->subtype);
+
668 free(mo->fusermount_opts);
+
669 free(mo->subtype_opt);
+
670 free(mo->kernel_opts);
+
671 free(mo->mtab_opts);
+
672 free(mo);
+
673}
+
674
+
675
+
676int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
677{
+
678 int res = -1;
+
679 char *mnt_opts = NULL;
+
680
+
681 res = -1;
+
682 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
683 goto out;
+
684 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
685 goto out;
+
686 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
687 goto out;
+
688
+
689 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
690 if (res >= 0 && mo->auto_unmount) {
+
691 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
692 // Something went wrong, let's umount like in fuse_mount_sys.
+
693 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
694 res = -1;
+
695 }
+
696 } else if (res == -2) {
+
697 if (mo->fusermount_opts &&
+
698 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
699 goto out;
+
700
+
701 if (mo->subtype) {
+
702 char *tmp_opts = NULL;
+
703
+
704 res = -1;
+
705 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
706 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
707 free(tmp_opts);
+
708 goto out;
+
709 }
+
710
+
711 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
712 free(tmp_opts);
+
713 if (res == -1)
+
714 res = fuse_mount_fusermount(mountpoint, mo,
+
715 mnt_opts, 0);
+
716 } else {
+
717 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
718 }
+
719 }
+
720out:
+
721 free(mnt_opts);
+
722 return res;
+
723}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2mount__bsd_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..15f0249 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2mount__bsd_8c_source.html @@ -0,0 +1,333 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15#include "util.h"
+
16
+
17#include <sys/param.h>
+
18#include "fuse_mount_compat.h"
+
19
+
20#include <sys/wait.h>
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <unistd.h>
+
24#include <stddef.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <string.h>
+
28
+
29#define FUSERMOUNT_PROG "mount_fusefs"
+
30#define FUSE_DEV_TRUNK "/dev/fuse"
+
31
+
32enum {
+
33 KEY_RO,
+
34 KEY_KERN
+
35};
+
36
+
37struct mount_opts {
+
38 int allow_other;
+
39 char *kernel_opts;
+
40 unsigned max_read;
+
41};
+
42
+
43#define FUSE_DUAL_OPT_KEY(templ, key) \
+
44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
45
+
46static const struct fuse_opt fuse_mount_opts[] = {
+
47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
49 FUSE_OPT_KEY("-r", KEY_RO),
+
50 /* standard FreeBSD mount options */
+
51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
75 /* options supported under both Linux and FBSD */
+
76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
78 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
79 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
80 /* FBSD FUSE specific mount options */
+
81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
84#if __FreeBSD_version >= 1200519
+
85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
86#endif
+
87 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
88 /*
+
89 * Linux specific mount options, but let just the mount util
+
90 * handle them
+
91 */
+
92 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
94};
+
95
+
96void fuse_mount_version(void)
+
97{
+
98 system(FUSERMOUNT_PROG " --version");
+
99}
+
100
+
101unsigned get_max_read(struct mount_opts *o)
+
102{
+
103 return o->max_read;
+
104}
+
105
+
106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
107 struct fuse_args *outargs)
+
108{
+
109 (void) outargs;
+
110 struct mount_opts *mo = data;
+
111
+
112 switch (key) {
+
113 case KEY_RO:
+
114 arg = "ro";
+
115 /* fall through */
+
116
+
117 case KEY_KERN:
+
118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
119 }
+
120
+
121 /* Pass through unknown options */
+
122 return 1;
+
123}
+
124
+
125void fuse_kern_unmount(const char *mountpoint, int fd)
+
126{
+
127 if (close(fd) < 0)
+
128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
+
129 if (unmount(mountpoint, MNT_FORCE) < 0)
+
130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
+
131 mountpoint, strerror(errno));
+
132}
+
133
+
134static int fuse_mount_core(const char *mountpoint, const char *opts)
+
135{
+
136 const char *mountprog = FUSERMOUNT_PROG;
+
137 long fd;
+
138 char *fdnam, *dev;
+
139 pid_t pid, cpid;
+
140 int status;
+
141 int err;
+
142
+
143 fdnam = getenv("FUSE_DEV_FD");
+
144
+
145 if (fdnam) {
+
146 err = libfuse_strtol(fdnam, &fd);
+
147 if (err || fd < 0) {
+
148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
149 return -1;
+
150 }
+
151
+
152 goto mount;
+
153 }
+
154
+
155 dev = getenv("FUSE_DEV_NAME");
+
156
+
157 if (! dev)
+
158 dev = (char *)FUSE_DEV_TRUNK;
+
159
+
160 if ((fd = open(dev, O_RDWR)) < 0) {
+
161 perror("fuse: failed to open fuse device");
+
162 return -1;
+
163 }
+
164
+
165mount:
+
166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
167 goto out;
+
168
+
169 pid = fork();
+
170 cpid = pid;
+
171
+
172 if (pid == -1) {
+
173 perror("fuse: fork() failed");
+
174 close(fd);
+
175 return -1;
+
176 }
+
177
+
178 if (pid == 0) {
+
179 pid = fork();
+
180
+
181 if (pid == -1) {
+
182 perror("fuse: fork() failed");
+
183 close(fd);
+
184 _exit(EXIT_FAILURE);
+
185 }
+
186
+
187 if (pid == 0) {
+
188 const char *argv[32];
+
189 int a = 0;
+
190 int ret = -1;
+
191
+
192 if (! fdnam)
+
193 {
+
194 ret = asprintf(&fdnam, "%ld", fd);
+
195 if(ret == -1)
+
196 {
+
197 perror("fuse: failed to assemble mount arguments");
+
198 close(fd);
+
199 _exit(EXIT_FAILURE);
+
200 }
+
201 }
+
202
+
203 argv[a++] = mountprog;
+
204 if (opts) {
+
205 argv[a++] = "-o";
+
206 argv[a++] = opts;
+
207 }
+
208 argv[a++] = fdnam;
+
209 argv[a++] = mountpoint;
+
210 argv[a++] = NULL;
+
211 execvp(mountprog, (char **) argv);
+
212 perror("fuse: failed to exec mount program");
+
213 free(fdnam);
+
214 _exit(EXIT_FAILURE);
+
215 }
+
216
+
217 _exit(EXIT_SUCCESS);
+
218 }
+
219
+
220 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
221 perror("fuse: failed to mount file system");
+
222 if (close(fd) < 0)
+
223 perror("fuse: closing FD");
+
224 return -1;
+
225 }
+
226
+
227out:
+
228 return fd;
+
229}
+
230
+
231struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
232{
+
233 struct mount_opts *mo;
+
234
+
235 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
236 if (mo == NULL)
+
237 return NULL;
+
238
+
239 memset(mo, 0, sizeof(struct mount_opts));
+
240
+
241 if (args &&
+
242 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
243 goto err_out;
+
244
+
245 return mo;
+
246
+
247err_out:
+
248 destroy_mount_opts(mo);
+
249 return NULL;
+
250}
+
251
+
252void destroy_mount_opts(struct mount_opts *mo)
+
253{
+
254 free(mo->kernel_opts);
+
255 free(mo);
+
256}
+
257
+
258int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
259{
+
260 /* mount util should not try to spawn the daemon */
+
261 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
262 /* to notify the mount util it's called from lib */
+
263 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
264
+
265 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
266}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8c_source.html new file mode 100644 index 0000000..f274263 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8c_source.html @@ -0,0 +1,433 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78 }
+
79
+
80 return 1;
+
81}
+
82#endif /* IGNORE_MTAB */
+
83
+
84static int add_mount(const char *progname, const char *fsname,
+
85 const char *mnt, const char *type, const char *opts)
+
86{
+
87 int res;
+
88 int status;
+
89 sigset_t blockmask;
+
90 sigset_t oldmask;
+
91
+
92 sigemptyset(&blockmask);
+
93 sigaddset(&blockmask, SIGCHLD);
+
94 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
95 if (res == -1) {
+
96 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
97 return -1;
+
98 }
+
99
+
100 res = fork();
+
101 if (res == -1) {
+
102 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
103 goto out_restore;
+
104 }
+
105 if (res == 0) {
+
106 char *env = NULL;
+
107
+
108 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
109
+
110 if(setuid(geteuid()) == -1) {
+
111 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
112 res = -1;
+
113 goto out_restore;
+
114 }
+
115
+
116 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
117 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
118 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
119 progname, strerror(errno));
+
120 exit(1);
+
121 }
+
122 res = waitpid(res, &status, 0);
+
123 if (res == -1)
+
124 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
125
+
126 if (status != 0)
+
127 res = -1;
+
128
+
129 out_restore:
+
130 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
131
+
132 return res;
+
133}
+
134
+
135int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
136 const char *mnt, const char *type, const char *opts)
+
137{
+
138 if (!mtab_needs_update(mnt))
+
139 return 0;
+
140
+
141 return add_mount(progname, fsname, mnt, type, opts);
+
142}
+
143
+
144static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
145{
+
146 int res;
+
147 int status;
+
148 sigset_t blockmask;
+
149 sigset_t oldmask;
+
150
+
151 sigemptyset(&blockmask);
+
152 sigaddset(&blockmask, SIGCHLD);
+
153 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
154 if (res == -1) {
+
155 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
156 return -1;
+
157 }
+
158
+
159 res = fork();
+
160 if (res == -1) {
+
161 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
162 goto out_restore;
+
163 }
+
164 if (res == 0) {
+
165 char *env = NULL;
+
166
+
167 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
168
+
169 if(setuid(geteuid()) == -1) {
+
170 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
171 res = -1;
+
172 goto out_restore;
+
173 }
+
174
+
175 if (lazy) {
+
176 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
177 "-l", NULL, &env);
+
178 } else {
+
179 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
180 NULL, &env);
+
181 }
+
182 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
183 progname, strerror(errno));
+
184 exit(1);
+
185 }
+
186 res = waitpid(res, &status, 0);
+
187 if (res == -1)
+
188 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
189
+
190 if (status != 0) {
+
191 res = -1;
+
192 }
+
193
+
194 out_restore:
+
195 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
196 return res;
+
197
+
198}
+
199
+
200int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
201 const char *rel_mnt, int lazy)
+
202{
+
203 int res;
+
204
+
205 if (!mtab_needs_update(abs_mnt)) {
+
206 res = umount2(rel_mnt, lazy ? 2 : 0);
+
207 if (res == -1)
+
208 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
209 progname, abs_mnt, strerror(errno));
+
210 return res;
+
211 }
+
212
+
213 return exec_umount(progname, rel_mnt, lazy);
+
214}
+
215
+
216static int remove_mount(const char *progname, const char *mnt)
+
217{
+
218 int res;
+
219 int status;
+
220 sigset_t blockmask;
+
221 sigset_t oldmask;
+
222
+
223 sigemptyset(&blockmask);
+
224 sigaddset(&blockmask, SIGCHLD);
+
225 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
226 if (res == -1) {
+
227 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
228 return -1;
+
229 }
+
230
+
231 res = fork();
+
232 if (res == -1) {
+
233 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
234 goto out_restore;
+
235 }
+
236 if (res == 0) {
+
237 char *env = NULL;
+
238
+
239 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
240
+
241 if(setuid(geteuid()) == -1) {
+
242 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
243 res = -1;
+
244 goto out_restore;
+
245 }
+
246
+
247 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
248 "--fake", mnt, NULL, &env);
+
249 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
250 progname, strerror(errno));
+
251 exit(1);
+
252 }
+
253 res = waitpid(res, &status, 0);
+
254 if (res == -1)
+
255 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
256
+
257 if (status != 0)
+
258 res = -1;
+
259
+
260 out_restore:
+
261 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
262 return res;
+
263}
+
264
+
265int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
266{
+
267 if (!mtab_needs_update(mnt))
+
268 return 0;
+
269
+
270 return remove_mount(progname, mnt);
+
271}
+
272
+
273char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
274{
+
275 char buf[PATH_MAX];
+
276 char *copy;
+
277 char *dst;
+
278 char *end;
+
279 char *lastcomp;
+
280 const char *toresolv;
+
281
+
282 if (!orig[0]) {
+
283 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
284 orig);
+
285 return NULL;
+
286 }
+
287
+
288 copy = strdup(orig);
+
289 if (copy == NULL) {
+
290 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
291 return NULL;
+
292 }
+
293
+
294 toresolv = copy;
+
295 lastcomp = NULL;
+
296 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
297 if (end[0] != '/') {
+
298 char *tmp;
+
299 end[1] = '\0';
+
300 tmp = strrchr(copy, '/');
+
301 if (tmp == NULL) {
+
302 lastcomp = copy;
+
303 toresolv = ".";
+
304 } else {
+
305 lastcomp = tmp + 1;
+
306 if (tmp == copy)
+
307 toresolv = "/";
+
308 }
+
309 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
310 lastcomp = NULL;
+
311 toresolv = copy;
+
312 }
+
313 else if (tmp)
+
314 tmp[0] = '\0';
+
315 }
+
316 if (realpath(toresolv, buf) == NULL) {
+
317 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
318 strerror(errno));
+
319 free(copy);
+
320 return NULL;
+
321 }
+
322 if (lastcomp == NULL)
+
323 dst = strdup(buf);
+
324 else {
+
325 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
326 if (dst) {
+
327 unsigned buflen = strlen(buf);
+
328 if (buflen && buf[buflen-1] == '/')
+
329 sprintf(dst, "%s%s", buf, lastcomp);
+
330 else
+
331 sprintf(dst, "%s/%s", buf, lastcomp);
+
332 }
+
333 }
+
334 free(copy);
+
335 if (dst == NULL)
+
336 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
337 return dst;
+
338}
+
339
+
340int fuse_mnt_check_fuseblk(void)
+
341{
+
342 char buf[256];
+
343 FILE *f = fopen("/proc/filesystems", "r");
+
344 if (!f)
+
345 return 1;
+
346
+
347 while (fgets(buf, sizeof(buf), f))
+
348 if (strstr(buf, "fuseblk\n")) {
+
349 fclose(f);
+
350 return 1;
+
351 }
+
352
+
353 fclose(f);
+
354 return 0;
+
355}
+
356
+
357int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
358{
+
359 int fd = -1;
+
360 int len = 0;
+
361
+
362 if (mountpoint == NULL) {
+
363 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
364 return -1;
+
365 }
+
366
+
367 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
368 len == strlen(mountpoint)) {
+
369 return fd;
+
370 }
+
371
+
372 return -1;
+
373}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8h_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8h_source.html new file mode 100644 index 0000000..9ab5831 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2util_8c_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2util_8c_source.html new file mode 100644 index 0000000..9df1ef0 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2util_8c_source.html @@ -0,0 +1,87 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1#include <stdlib.h>
+
2#include <errno.h>
+
3
+
4#include "util.h"
+
5
+
6int libfuse_strtol(const char *str, long *res)
+
7{
+
8 char *endptr;
+
9 int base = 10;
+
10 long val;
+
11
+
12 errno = 0;
+
13
+
14 if (!str)
+
15 return -EINVAL;
+
16
+
17 val = strtol(str, &endptr, base);
+
18
+
19 if (errno)
+
20 return -errno;
+
21
+
22 if (endptr == str || *endptr != '\0')
+
23 return -EINVAL;
+
24
+
25 *res = val;
+
26 return 0;
+
27}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2lib_2util_8h_source.html b/doc/html/fuse-3_817_81_8dir_2lib_2util_8h_source.html new file mode 100644 index 0000000..2a12405 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2lib_2util_8h_source.html @@ -0,0 +1,84 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#ifndef FUSE_UTIL_H_
+
2#define FUSE_UTIL_H_
+
3
+
4#include <stdint.h>
+
5
+
6#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
7
+
8int libfuse_strtol(const char *str, long *res);
+
9
+
13static inline uint32_t fuse_lower_32_bits(uint64_t nr)
+
14{
+
15 return (uint32_t)(nr & 0xffffffff);
+
16}
+
17
+
21static inline uint64_t fuse_higher_32_bits(uint64_t nr)
+
22{
+
23 return nr & ~0xffffffffULL;
+
24}
+
25
+
26#ifndef FUSE_VAR_UNUSED
+
27#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var)
+
28#endif
+
29
+
30#endif
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2readdir__inode_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..e3ddea8 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/readdir_inode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
readdir_inode.c
+
+
+
1/*
+
2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
+
3 * Skips '.' and '..' because readdir is not required to return them and
+
4 * some of our examples don't. However if they are returned, their d_type
+
5 * should be valid.
+
6 */
+
7
+
8#include <stdio.h>
+
9#include <string.h>
+
10#include <sys/types.h>
+
11#include <dirent.h>
+
12#include <errno.h>
+
13
+
14int main(int argc, char* argv[])
+
15{
+
16 DIR* dirp;
+
17 struct dirent* dent;
+
18
+
19 if (argc != 2) {
+
20 fprintf(stderr, "Usage: readdir_inode dir\n");
+
21 return 1;
+
22 }
+
23
+
24 dirp = opendir(argv[1]);
+
25 if (dirp == NULL) {
+
26 perror("failed to open directory");
+
27 return 2;
+
28 }
+
29
+
30 errno = 0;
+
31 dent = readdir(dirp);
+
32 while (dent != NULL) {
+
33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
+
34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
+
35 (int)dent->d_type, dent->d_name);
+
36 if ((long long)dent->d_ino < 0)
+
37 fprintf(stderr,"%s : bad d_ino %llu\n",
+
38 dent->d_name, (unsigned long long)dent->d_ino);
+
39 if ((dent->d_type < 1) || (dent->d_type > 15))
+
40 fprintf(stderr,"%s : bad d_type %d\n",
+
41 dent->d_name, (int)dent->d_type);
+
42 } else {
+
43 if (dent->d_type != DT_DIR)
+
44 fprintf(stderr,"%s : bad d_type %d\n",
+
45 dent->d_name, (int)dent->d_type);
+
46 }
+
47 dent = readdir(dirp);
+
48 }
+
49 if (errno != 0) {
+
50 perror("failed to read directory entry");
+
51 return 3;
+
52 }
+
53
+
54 closedir(dirp);
+
55
+
56 return 0;
+
57}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2release__unlink__race_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..dc27c63 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/release_unlink_race.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
release_unlink_race.c
+
+
+
1/*
+
2 This program can be distributed under the terms of the GNU GPLv2.
+
3 See the file COPYING.
+
4*/
+
5
+
6#define FUSE_USE_VERSION 31
+
7
+
8#define _GNU_SOURCE
+
9
+
10#include <fuse.h>
+
11
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16
+
17static void *xmp_init(struct fuse_conn_info *conn,
+
18 struct fuse_config *cfg)
+
19{
+
20 (void) conn;
+
21
+
22 cfg->use_ino = 1;
+
23 cfg->nullpath_ok = 1;
+
24 cfg->entry_timeout = 0;
+
25 cfg->attr_timeout = 0;
+
26 cfg->negative_timeout = 0;
+
27
+
28 return NULL;
+
29}
+
30
+
31static int xmp_getattr(const char *path, struct stat *stbuf,
+
32 struct fuse_file_info *fi)
+
33{
+
34 int res;
+
35
+
36 (void) path;
+
37
+
38 if(fi)
+
39 res = fstat(fi->fh, stbuf);
+
40 else
+
41 res = lstat(path, stbuf);
+
42 if (res == -1)
+
43 return -errno;
+
44
+
45 return 0;
+
46}
+
47
+
48static int xmp_unlink(const char *path)
+
49{
+
50 int res;
+
51
+
52 res = unlink(path);
+
53 if (res == -1)
+
54 return -errno;
+
55
+
56 return 0;
+
57}
+
58
+
59static int xmp_rename(const char *from, const char *to, unsigned int flags)
+
60{
+
61 int res;
+
62
+
63 if (flags)
+
64 return -EINVAL;
+
65
+
66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
67
+
68 res = rename(from, to);
+
69 if (res == -1)
+
70 return -errno;
+
71
+
72 return 0;
+
73}
+
74
+
75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+
76{
+
77 int fd;
+
78
+
79 fd = open(path, fi->flags, mode);
+
80 if (fd == -1)
+
81 return -errno;
+
82
+
83 fi->fh = fd;
+
84 return 0;
+
85}
+
86
+
87static int xmp_release(const char *path, struct fuse_file_info *fi)
+
88{
+
89 (void) path;
+
90
+
91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
+
92
+
93 close(fi->fh);
+
94
+
95 return 0;
+
96}
+
97
+
98static const struct fuse_operations xmp_oper = {
+
99 .init = xmp_init,
+
100 .getattr = xmp_getattr,
+
101 .unlink = xmp_unlink,
+
102 .rename = xmp_rename,
+
103 .create = xmp_create,
+
104 .release = xmp_release,
+
105};
+
106
+
107int main(int argc, char *argv[])
+
108{
+
109 umask(0);
+
110 return fuse_main(argc, argv, &xmp_oper, NULL);
+
111}
+ +
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2stracedecode_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2stracedecode_8c_source.html new file mode 100644 index 0000000..64eb315 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/stracedecode.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
stracedecode.c
+
+
+
1#include <stdio.h>
+
2#include <string.h>
+
3#include "fuse_kernel.h"
+
4
+
5static struct {
+
6 const char *name;
+
7} fuse_ll_ops[] = {
+
8 [FUSE_LOOKUP] = { "LOOKUP" },
+
9 [FUSE_FORGET] = { "FORGET" },
+
10 [FUSE_GETATTR] = { "GETATTR" },
+
11 [FUSE_SETATTR] = { "SETATTR" },
+
12 [FUSE_READLINK] = { "READLINK" },
+
13 [FUSE_SYMLINK] = { "SYMLINK" },
+
14 [FUSE_MKNOD] = { "MKNOD" },
+
15 [FUSE_MKDIR] = { "MKDIR" },
+
16 [FUSE_UNLINK] = { "UNLINK" },
+
17 [FUSE_RMDIR] = { "RMDIR" },
+
18 [FUSE_RENAME] = { "RENAME" },
+
19 [FUSE_LINK] = { "LINK" },
+
20 [FUSE_OPEN] = { "OPEN" },
+
21 [FUSE_READ] = { "READ" },
+
22 [FUSE_WRITE] = { "WRITE" },
+
23 [FUSE_STATFS] = { "STATFS" },
+
24 [FUSE_RELEASE] = { "RELEASE" },
+
25 [FUSE_FSYNC] = { "FSYNC" },
+
26 [FUSE_SETXATTR] = { "SETXATTR" },
+
27 [FUSE_GETXATTR] = { "GETXATTR" },
+
28 [FUSE_LISTXATTR] = { "LISTXATTR" },
+
29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
+
30 [FUSE_FLUSH] = { "FLUSH" },
+
31 [FUSE_INIT] = { "INIT" },
+
32 [FUSE_OPENDIR] = { "OPENDIR" },
+
33 [FUSE_READDIR] = { "READDIR" },
+
34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
+
35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
+
36 [FUSE_GETLK] = { "GETLK" },
+
37 [FUSE_SETLK] = { "SETLK" },
+
38 [FUSE_SETLKW] = { "SETLKW" },
+
39 [FUSE_ACCESS] = { "ACCESS" },
+
40 [FUSE_CREATE] = { "CREATE" },
+
41 [FUSE_TMPFILE] = { "TMPFILE" },
+
42 [FUSE_INTERRUPT] = { "INTERRUPT" },
+
43 [FUSE_BMAP] = { "BMAP" },
+
44 [FUSE_DESTROY] = { "DESTROY" },
+
45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
+
46};
+
47
+
48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
49
+
50static const char *opname(enum fuse_opcode opcode)
+
51{
+
52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
53 return "???";
+
54 else
+
55 return fuse_ll_ops[opcode].name;
+
56}
+
57
+
58
+
59static void process_buf(int dir, char *buf, int len)
+
60{
+
61 static unsigned long long prevuniq = -1;
+
62 static int prevopcode;
+
63
+
64 if (!dir) {
+
65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
+
66 buf += sizeof(struct fuse_in_header);
+
67
+
68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
+
69 (unsigned long long) in->unique,
+
70 opname((enum fuse_opcode) in->opcode), in->opcode,
+
71 (unsigned long) in->nodeid, in->len, len);
+
72
+
73 switch (in->opcode) {
+
74 case FUSE_READ: {
+
75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
+
76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
+
77 arg->fh, arg->offset, arg->size, arg->read_flags,
+
78 arg->lock_owner, arg->flags);
+
79 break;
+
80 }
+
81 case FUSE_WRITE: {
+
82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
+
83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
+
84 arg->fh, arg->offset, arg->size, arg->write_flags,
+
85 arg->lock_owner, arg->flags);
+
86 break;
+
87 }
+
88 }
+
89 prevuniq = in->unique;
+
90 prevopcode = in->opcode;
+
91 } else {
+
92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
+
93 buf += sizeof(struct fuse_out_header);
+
94
+
95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
+
96 (unsigned long long) out->unique, out->error,
+
97 strerror(-out->error), out->len, len);
+
98
+
99 if (out->unique == prevuniq) {
+
100 switch (prevopcode) {
+
101 case FUSE_GETATTR: {
+
102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
+
103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
104 arg->attr_valid, arg->attr_valid_nsec,
+
105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
106 break;
+
107 }
+
108 case FUSE_LOOKUP: {
+
109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
+
110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
+
111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
+
112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
+
113 break;
+
114 }
+
115 }
+
116 }
+
117 }
+
118
+
119}
+
120
+
121int main(void)
+
122{
+
123 FILE *in = stdin;
+
124 while (1) {
+
125 int dir;
+
126 int res;
+
127 char buf[1048576];
+
128 unsigned len = 0;
+
129
+
130 memset(buf, 0, sizeof(buf));
+
131 while (1) {
+
132 char str[32];
+
133
+
134 res = fscanf(in, "%30s", str);
+
135 if (res != 1 && feof(in))
+
136 return 0;
+
137
+
138 if (res == 0)
+
139 continue;
+
140
+
141 if (strncmp(str, "read(", 5) == 0) {
+
142 dir = 0;
+
143 break;
+
144 } else if (strncmp(str, "writev(", 7) == 0) {
+
145 dir = 1;
+
146 break;
+
147 }
+
148 }
+
149
+
150 while (1) {
+
151 int c = getc(in);
+
152 if (c == '"') {
+
153 while (1) {
+
154 int val;
+
155
+
156 c = getc(in);
+
157 if (c == EOF) {
+
158 fprintf(stderr, "eof in string\n");
+
159 break;
+
160 }
+
161 if (c == '\n') {
+
162 fprintf(stderr, "eol in string\n");
+
163 break;
+
164 }
+
165 if (c == '"')
+
166 break;
+
167 if (c != '\\') {
+
168 val = c;
+
169 } else {
+
170 c = getc(in);
+
171 switch (c) {
+
172 case 'n': val = '\n'; break;
+
173 case 'r': val = '\r'; break;
+
174 case 't': val = '\t'; break;
+
175 case '"': val = '"'; break;
+
176 case '\\': val = '\\'; break;
+
177 case 'x':
+
178 res = scanf("%x", &val);
+
179 if (res != 1) {
+
180 fprintf(stderr, "parse error\n");
+
181 continue;
+
182 }
+
183 break;
+
184 default:
+
185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
+
186 continue;
+
187 }
+
188 }
+
189 buf[len++] = val;
+
190 }
+
191 }
+
192 if (c == '\n')
+
193 break;
+
194 }
+
195 process_buf(dir, buf, len);
+
196 memset(buf, 0, len);
+
197 len = 0;
+
198 }
+
199}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2test__setattr_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2test__setattr_8c_source.html new file mode 100644 index 0000000..2024c62 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/test_setattr.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_setattr.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <pthread.h>
+
26
+
27#ifndef __linux__
+
28#include <limits.h>
+
29#else
+
30#include <linux/limits.h>
+
31#endif
+
32
+
33#define FILE_INO 2
+
34#define FILE_NAME "truncate_me"
+
35
+
36static int got_fh;
+
37static mode_t file_mode = S_IFREG | 0644;
+
38
+
39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
40 stbuf->st_ino = ino;
+
41 if (ino == FUSE_ROOT_ID) {
+
42 stbuf->st_mode = S_IFDIR | 0755;
+
43 stbuf->st_nlink = 1;
+
44 }
+
45
+
46 else if (ino == FILE_INO) {
+
47 stbuf->st_mode = file_mode;
+
48 stbuf->st_nlink = 1;
+
49 stbuf->st_size = 0;
+
50 }
+
51
+
52 else
+
53 return -1;
+
54
+
55 return 0;
+
56}
+
57
+
58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
59 const char *name) {
+
60 struct fuse_entry_param e;
+
61 memset(&e, 0, sizeof(e));
+
62
+
63 if (parent != FUSE_ROOT_ID)
+
64 goto err_out;
+
65 else if (strcmp(name, FILE_NAME) == 0)
+
66 e.ino = FILE_INO;
+
67 else
+
68 goto err_out;
+
69
+
70 if (tfs_stat(e.ino, &e.attr) != 0)
+
71 goto err_out;
+
72 fuse_reply_entry(req, &e);
+
73 return;
+
74
+
75err_out:
+
76 fuse_reply_err(req, ENOENT);
+
77}
+
78
+
79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
80 struct fuse_file_info *fi) {
+
81 struct stat stbuf;
+
82
+
83 (void) fi;
+
84
+
85 memset(&stbuf, 0, sizeof(stbuf));
+
86 if (tfs_stat(ino, &stbuf) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else
+
89 fuse_reply_attr(req, &stbuf, 5);
+
90}
+
91
+
92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
93 struct fuse_file_info *fi) {
+
94 if (ino == FUSE_ROOT_ID)
+
95 fuse_reply_err(req, EISDIR);
+
96 else {
+
97 assert(ino == FILE_INO);
+
98 fi->fh = FILE_INO;
+
99 fuse_reply_open(req, fi);
+
100 }
+
101}
+
102
+
103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
104 int to_set, struct fuse_file_info *fi) {
+
105 if(ino != FILE_INO ||
+
106 !(to_set & FUSE_SET_ATTR_MODE)) {
+
107 fuse_reply_err(req, EINVAL);
+
108 return;
+
109 }
+
110
+
111 if(fi == NULL)
+
112 fprintf(stderr, "setattr with fi == NULL\n");
+
113 else if (fi->fh != FILE_INO)
+
114 fprintf(stderr, "setattr with wrong fi->fh\n");
+
115 else {
+
116 fprintf(stderr, "setattr ok\n");
+
117 got_fh = 1;
+
118 file_mode = attr->st_mode;
+
119 }
+
120
+
121 tfs_getattr(req, ino, fi);
+
122}
+
123
+
124static struct fuse_lowlevel_ops tfs_oper = {
+
125 .lookup = tfs_lookup,
+
126 .getattr = tfs_getattr,
+
127 .open = tfs_open,
+
128 .setattr = tfs_setattr,
+
129};
+
130
+
131static void* run_fs(void *data) {
+
132 struct fuse_session *se = (struct fuse_session*) data;
+
133 assert(fuse_session_loop(se) == 0);
+
134 return NULL;
+
135}
+
136
+
137static void test_fs(char *mountpoint) {
+
138 char fname[PATH_MAX];
+
139 int fd;
+
140
+
141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
142 mountpoint) > 0);
+
143 fd = open(fname, O_WRONLY);
+
144 if (fd == -1) {
+
145 perror(fname);
+
146 assert(0);
+
147 }
+
148
+
149 assert(fchmod(fd, 0600) == 0);
+
150 close(fd);
+
151}
+
152
+
153int main(int argc, char *argv[]) {
+
154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
155 struct fuse_session *se;
+
156 struct fuse_cmdline_opts fuse_opts;
+
157 pthread_t fs_thread;
+
158
+
159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
160#ifndef __FreeBSD__
+
161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
162#endif
+
163 se = fuse_session_new(&args, &tfs_oper,
+
164 sizeof(tfs_oper), NULL);
+
165 assert (se != NULL);
+
166 assert(fuse_set_signal_handlers(se) == 0);
+
167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
168
+
169 /* Start file-system thread */
+
170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
171
+
172 /* Do test */
+
173 test_fs(fuse_opts.mountpoint);
+
174
+
175 /* Stop file system */
+
176 assert(pthread_cancel(fs_thread) == 0);
+
177
+ +
179 assert(got_fh == 1);
+ + +
182
+
183 printf("Test completed successfully.\n");
+
184 return 0;
+
185}
+
186
+
187
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
+
fuse_ino_t ino
+ + + +
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2test__syscalls_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..4d30c1a --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2test__syscalls_8c_source.html @@ -0,0 +1,2258 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/test_syscalls.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_syscalls.c
+
+
+
1#define _GNU_SOURCE
+
2#include "fuse_config.h"
+
3
+
4#include <stdio.h>
+
5#include <stdlib.h>
+
6#include <stdarg.h>
+
7#include <string.h>
+
8#include <unistd.h>
+
9#include <fcntl.h>
+
10#include <dirent.h>
+
11#include <utime.h>
+
12#include <errno.h>
+
13#include <assert.h>
+
14#include <sys/socket.h>
+
15#include <sys/types.h>
+
16#include <sys/stat.h>
+
17#include <sys/un.h>
+
18
+
19#ifndef ALLPERMS
+
20# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
+
21#endif
+
22
+
23
+
24static const char *basepath;
+
25static const char *basepath_r;
+
26static char testfile[1024];
+
27static char testfile2[1024];
+
28static char testdir[1024];
+
29static char testdir2[1024];
+
30static char testsock[1024];
+
31static char subfile[1280];
+
32
+
33static char testfile_r[1024];
+
34static char testfile2_r[1024];
+
35static char testdir_r[1024];
+
36static char testdir2_r[1024];
+
37static char subfile_r[1280];
+
38
+
39static char testname[256];
+
40static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
+
41static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
+
42static const char *testdir_files[] = { "f1", "f2", NULL};
+
43static long seekdir_offsets[4];
+
44static char zerodata[4096];
+
45static int testdatalen = sizeof(testdata) - 1;
+
46static int testdata2len = sizeof(testdata2) - 1;
+
47static unsigned int testnum = 0;
+
48static unsigned int select_test = 0;
+
49static unsigned int skip_test = 0;
+
50static unsigned int unlinked_test = 0;
+
51
+
52#define MAX_ENTRIES 1024
+
53#define MAX_TESTS 100
+
54
+
55static struct test {
+
56 int fd;
+
57 struct stat stat;
+
58} tests[MAX_TESTS];
+
59
+
60static void test_perror(const char *func, const char *msg)
+
61{
+
62 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
+
63 strerror(errno));
+
64}
+
65
+
66static void test_error(const char *func, const char *msg, ...)
+
67 __attribute__ ((format (printf, 2, 3)));
+
68
+
69static void __start_test(const char *fmt, ...)
+
70 __attribute__ ((format (printf, 1, 2)));
+
71
+
72static void test_error(const char *func, const char *msg, ...)
+
73{
+
74 va_list ap;
+
75 fprintf(stderr, "%s %s() - ", testname, func);
+
76 va_start(ap, msg);
+
77 vfprintf(stderr, msg, ap);
+
78 va_end(ap);
+
79 fprintf(stderr, "\n");
+
80}
+
81
+
82static int is_dot_or_dotdot(const char *name) {
+
83 return name[0] == '.' &&
+
84 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
+
85}
+
86
+
87static void success(void)
+
88{
+
89 fprintf(stderr, "%s OK\n", testname);
+
90}
+
91
+
92#define this_test (&tests[testnum-1])
+
93#define next_test (&tests[testnum])
+
94
+
95static void __start_test(const char *fmt, ...)
+
96{
+
97 unsigned int n;
+
98 va_list ap;
+
99 n = sprintf(testname, "%3i [", testnum);
+
100 va_start(ap, fmt);
+
101 n += vsprintf(testname + n, fmt, ap);
+
102 va_end(ap);
+
103 sprintf(testname + n, "]");
+
104 // Use dedicated testfile per test
+
105 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
+
106 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
+
107 if (testnum > MAX_TESTS) {
+
108 fprintf(stderr, "%s - too many tests\n", testname);
+
109 exit(1);
+
110 }
+
111 this_test->fd = -1;
+
112}
+
113
+
114#define start_test(msg, args...) { \
+
115 testnum++; \
+
116 if ((select_test && testnum != select_test) || \
+
117 (testnum == skip_test)) { \
+
118 return 0; \
+
119 } \
+
120 __start_test(msg, ##args); \
+
121}
+
122
+
123#define PERROR(msg) test_perror(__FUNCTION__, msg)
+
124#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
+
125
+
126#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
127
+
128static int st_check_size(struct stat *st, int len)
+
129{
+
130 if (st->st_size != len) {
+
131 ERROR("length %u instead of %u", (int) st->st_size,
+
132 (int) len);
+
133 return -1;
+
134 }
+
135 return 0;
+
136}
+
137
+
138static int check_size(const char *path, int len)
+
139{
+
140 struct stat stbuf;
+
141 int res = stat(path, &stbuf);
+
142 if (res == -1) {
+
143 PERROR("stat");
+
144 return -1;
+
145 }
+
146 return st_check_size(&stbuf, len);
+
147}
+
148
+
149static int check_testfile_size(const char *path, int len)
+
150{
+
151 this_test->stat.st_size = len;
+
152 return check_size(path, len);
+
153}
+
154
+
155static int st_check_type(struct stat *st, mode_t type)
+
156{
+
157 if ((st->st_mode & S_IFMT) != type) {
+
158 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
+
159 return -1;
+
160 }
+
161 return 0;
+
162}
+
163
+
164static int check_type(const char *path, mode_t type)
+
165{
+
166 struct stat stbuf;
+
167 int res = lstat(path, &stbuf);
+
168 if (res == -1) {
+
169 PERROR("lstat");
+
170 return -1;
+
171 }
+
172 return st_check_type(&stbuf, type);
+
173}
+
174
+
175static int st_check_mode(struct stat *st, mode_t mode)
+
176{
+
177 if ((st->st_mode & ALLPERMS) != mode) {
+
178 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
+
179 mode);
+
180 return -1;
+
181 }
+
182 return 0;
+
183}
+
184
+
185static int check_mode(const char *path, mode_t mode)
+
186{
+
187 struct stat stbuf;
+
188 int res = lstat(path, &stbuf);
+
189 if (res == -1) {
+
190 PERROR("lstat");
+
191 return -1;
+
192 }
+
193 return st_check_mode(&stbuf, mode);
+
194}
+
195
+
196static int check_testfile_mode(const char *path, mode_t mode)
+
197{
+
198 this_test->stat.st_mode &= ~ALLPERMS;
+
199 this_test->stat.st_mode |= mode;
+
200 return check_mode(path, mode);
+
201}
+
202
+
203static int check_times(const char *path, time_t atime, time_t mtime)
+
204{
+
205 int err = 0;
+
206 struct stat stbuf;
+
207 int res = lstat(path, &stbuf);
+
208 if (res == -1) {
+
209 PERROR("lstat");
+
210 return -1;
+
211 }
+
212 if (stbuf.st_atime != atime) {
+
213 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
214 err--;
+
215 }
+
216 if (stbuf.st_mtime != mtime) {
+
217 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
218 err--;
+
219 }
+
220 if (err)
+
221 return -1;
+
222
+
223 return 0;
+
224}
+
225
+
226#if 0
+
227static int fcheck_times(int fd, time_t atime, time_t mtime)
+
228{
+
229 int err = 0;
+
230 struct stat stbuf;
+
231 int res = fstat(fd, &stbuf);
+
232 if (res == -1) {
+
233 PERROR("fstat");
+
234 return -1;
+
235 }
+
236 if (stbuf.st_atime != atime) {
+
237 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+
238 err--;
+
239 }
+
240 if (stbuf.st_mtime != mtime) {
+
241 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+
242 err--;
+
243 }
+
244 if (err)
+
245 return -1;
+
246
+
247 return 0;
+
248}
+
249#endif
+
250
+
251static int st_check_nlink(struct stat *st, nlink_t nlink)
+
252{
+
253 if (st->st_nlink != nlink) {
+
254 ERROR("nlink %li instead of %li", (long) st->st_nlink,
+
255 (long) nlink);
+
256 return -1;
+
257 }
+
258 return 0;
+
259}
+
260
+
261static int check_nlink(const char *path, nlink_t nlink)
+
262{
+
263 struct stat stbuf;
+
264 int res = lstat(path, &stbuf);
+
265 if (res == -1) {
+
266 PERROR("lstat");
+
267 return -1;
+
268 }
+
269 return st_check_nlink(&stbuf, nlink);
+
270}
+
271
+
272static int fcheck_stat(int fd, int flags, struct stat *st)
+
273{
+
274 struct stat stbuf;
+
275 int res = fstat(fd, &stbuf);
+
276 if (res == -1) {
+
277 if (flags & O_PATH) {
+
278 // With O_PATH fd, the server does not have to keep
+
279 // the inode alive so FUSE inode may be stale or bad
+
280 if (errno == ESTALE || errno == EIO ||
+
281 errno == ENOENT || errno == EBADF)
+
282 return 0;
+
283 }
+
284 PERROR("fstat");
+
285 return -1;
+
286 }
+
287
+
288 int err = 0;
+
289 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
+
290 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
+
291 err += st_check_size(&stbuf, st->st_size);
+
292 err += st_check_nlink(&stbuf, st->st_nlink);
+
293
+
294 return err;
+
295}
+
296
+
297static int check_nonexist(const char *path)
+
298{
+
299 struct stat stbuf;
+
300 int res = lstat(path, &stbuf);
+
301 if (res == 0) {
+
302 ERROR("file should not exist");
+
303 return -1;
+
304 }
+
305 if (errno != ENOENT) {
+
306 ERROR("file should not exist: %s", strerror(errno));
+
307 return -1;
+
308 }
+
309 return 0;
+
310}
+
311
+
312static int check_buffer(const char *buf, const char *data, unsigned len)
+
313{
+
314 if (memcmp(buf, data, len) != 0) {
+
315 ERROR("data mismatch");
+
316 return -1;
+
317 }
+
318 return 0;
+
319}
+
320
+
321static int check_data(const char *path, const char *data, int offset,
+
322 unsigned len)
+
323{
+
324 char buf[4096];
+
325 int res;
+
326 int fd = open(path, O_RDONLY);
+
327 if (fd == -1) {
+
328 PERROR("open");
+
329 return -1;
+
330 }
+
331 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
332 PERROR("lseek");
+
333 close(fd);
+
334 return -1;
+
335 }
+
336 while (len) {
+
337 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
338 res = read(fd, buf, rdlen);
+
339 if (res == -1) {
+
340 PERROR("read");
+
341 close(fd);
+
342 return -1;
+
343 }
+
344 if (res != rdlen) {
+
345 ERROR("short read: %u instead of %u", res, rdlen);
+
346 close(fd);
+
347 return -1;
+
348 }
+
349 if (check_buffer(buf, data, rdlen) != 0) {
+
350 close(fd);
+
351 return -1;
+
352 }
+
353 data += rdlen;
+
354 len -= rdlen;
+
355 }
+
356 res = close(fd);
+
357 if (res == -1) {
+
358 PERROR("close");
+
359 return -1;
+
360 }
+
361 return 0;
+
362}
+
363
+
364static int fcheck_data(int fd, const char *data, int offset,
+
365 unsigned len)
+
366{
+
367 char buf[4096];
+
368 int res;
+
369 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+
370 PERROR("lseek");
+
371 return -1;
+
372 }
+
373 while (len) {
+
374 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+
375 res = read(fd, buf, rdlen);
+
376 if (res == -1) {
+
377 PERROR("read");
+
378 return -1;
+
379 }
+
380 if (res != rdlen) {
+
381 ERROR("short read: %u instead of %u", res, rdlen);
+
382 return -1;
+
383 }
+
384 if (check_buffer(buf, data, rdlen) != 0) {
+
385 return -1;
+
386 }
+
387 data += rdlen;
+
388 len -= rdlen;
+
389 }
+
390 return 0;
+
391}
+
392
+
393static int check_dir_contents(const char *path, const char **contents)
+
394{
+
395 int i;
+
396 int res;
+
397 int err = 0;
+
398 int found[MAX_ENTRIES];
+
399 const char *cont[MAX_ENTRIES];
+
400 DIR *dp;
+
401
+
402 for (i = 0; contents[i]; i++) {
+
403 assert(i < MAX_ENTRIES - 3);
+
404 found[i] = 0;
+
405 cont[i] = contents[i];
+
406 }
+
407 cont[i] = NULL;
+
408
+
409 dp = opendir(path);
+
410 if (dp == NULL) {
+
411 PERROR("opendir");
+
412 return -1;
+
413 }
+
414 memset(found, 0, sizeof(found));
+
415 while(1) {
+
416 struct dirent *de;
+
417 errno = 0;
+
418 de = readdir(dp);
+
419 if (de == NULL) {
+
420 if (errno) {
+
421 PERROR("readdir");
+
422 closedir(dp);
+
423 return -1;
+
424 }
+
425 break;
+
426 }
+
427 if (is_dot_or_dotdot(de->d_name))
+
428 continue;
+
429 for (i = 0; cont[i] != NULL; i++) {
+
430 assert(i < MAX_ENTRIES);
+
431 if (strcmp(cont[i], de->d_name) == 0) {
+
432 if (found[i]) {
+
433 ERROR("duplicate entry <%s>",
+
434 de->d_name);
+
435 err--;
+
436 } else
+
437 found[i] = 1;
+
438 break;
+
439 }
+
440 }
+
441 if (!cont[i]) {
+
442 ERROR("unexpected entry <%s>", de->d_name);
+
443 err --;
+
444 }
+
445 }
+
446 for (i = 0; cont[i] != NULL; i++) {
+
447 if (!found[i]) {
+
448 ERROR("missing entry <%s>", cont[i]);
+
449 err--;
+
450 }
+
451 }
+
452 res = closedir(dp);
+
453 if (res == -1) {
+
454 PERROR("closedir");
+
455 return -1;
+
456 }
+
457 if (err)
+
458 return -1;
+
459
+
460 return 0;
+
461}
+
462
+
463static int create_file(const char *path, const char *data, int len)
+
464{
+
465 int res;
+
466 int fd;
+
467
+
468 unlink(path);
+
469 fd = creat(path, 0644);
+
470 if (fd == -1) {
+
471 PERROR("creat");
+
472 return -1;
+
473 }
+
474 if (len) {
+
475 res = write(fd, data, len);
+
476 if (res == -1) {
+
477 PERROR("write");
+
478 close(fd);
+
479 return -1;
+
480 }
+
481 if (res != len) {
+
482 ERROR("write is short: %u instead of %u", res, len);
+
483 close(fd);
+
484 return -1;
+
485 }
+
486 }
+
487 res = close(fd);
+
488 if (res == -1) {
+
489 PERROR("close");
+
490 return -1;
+
491 }
+
492 res = check_type(path, S_IFREG);
+
493 if (res == -1)
+
494 return -1;
+
495 res = check_mode(path, 0644);
+
496 if (res == -1)
+
497 return -1;
+
498 res = check_nlink(path, 1);
+
499 if (res == -1)
+
500 return -1;
+
501 res = check_size(path, len);
+
502 if (res == -1)
+
503 return -1;
+
504
+
505 if (len) {
+
506 res = check_data(path, data, 0, len);
+
507 if (res == -1)
+
508 return -1;
+
509 }
+
510
+
511 return 0;
+
512}
+
513
+
514static int create_path_fd(const char *path, const char *data, int len)
+
515{
+
516 int path_fd;
+
517 int res;
+
518
+
519 res = create_file(path, data, len);
+
520 if (res == -1)
+
521 return -1;
+
522
+
523 path_fd = open(path, O_PATH);
+
524 if (path_fd == -1)
+
525 PERROR("open(O_PATH)");
+
526
+
527 return path_fd;
+
528}
+
529
+
530// Can be called once per test
+
531static int create_testfile(const char *path, const char *data, int len)
+
532{
+
533 struct test *t = this_test;
+
534 struct stat *st = &t->stat;
+
535 int res, fd;
+
536
+
537 if (t->fd > 0) {
+
538 ERROR("testfile already created");
+
539 return -1;
+
540 }
+
541
+
542 fd = create_path_fd(path, data, len);
+
543 if (fd == -1)
+
544 return -1;
+
545
+
546 t->fd = fd;
+
547
+
548 res = fstat(fd, st);
+
549 if (res == -1) {
+
550 PERROR("fstat");
+
551 return -1;
+
552 }
+
553
+
554 return 0;
+
555}
+
556
+
557static int check_unlinked_testfile(int fd)
+
558{
+
559 struct stat *st = &this_test->stat;
+
560
+
561 st->st_nlink = 0;
+
562 return fcheck_stat(fd, O_PATH, st);
+
563}
+
564
+
565// Check recorded testfiles after all tests completed
+
566static int check_unlinked_testfiles(void)
+
567{
+
568 int fd;
+
569 int res, err = 0;
+
570 int num = testnum;
+
571
+
572 if (!unlinked_test)
+
573 return 0;
+
574
+
575 testnum = 0;
+
576 while (testnum < num) {
+
577 fd = next_test->fd;
+
578 start_test("check_unlinked_testfile");
+
579 if (fd == -1)
+
580 continue;
+
581
+
582 err += check_unlinked_testfile(fd);
+
583 res = close(fd);
+
584 if (res == -1) {
+
585 PERROR("close(test_fd)");
+
586 err--;
+
587 }
+
588 }
+
589
+
590 if (err) {
+
591 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
+
592 return 1;
+
593 }
+
594
+
595 return err;
+
596}
+
597
+
598static int cleanup_dir(const char *path, const char **dir_files, int quiet)
+
599{
+
600 int i;
+
601 int err = 0;
+
602
+
603 for (i = 0; dir_files[i]; i++) {
+
604 int res;
+
605 char fpath[1280];
+
606 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
607 res = unlink(fpath);
+
608 if (res == -1 && !quiet) {
+
609 PERROR("unlink");
+
610 err --;
+
611 }
+
612 }
+
613 if (err)
+
614 return -1;
+
615
+
616 return 0;
+
617}
+
618
+
619static int create_dir(const char *path, const char **dir_files)
+
620{
+
621 int res;
+
622 int i;
+
623
+
624 rmdir(path);
+
625 res = mkdir(path, 0755);
+
626 if (res == -1) {
+
627 PERROR("mkdir");
+
628 return -1;
+
629 }
+
630 res = check_type(path, S_IFDIR);
+
631 if (res == -1)
+
632 return -1;
+
633 res = check_mode(path, 0755);
+
634 if (res == -1)
+
635 return -1;
+
636
+
637 for (i = 0; dir_files[i]; i++) {
+
638 char fpath[1280];
+
639 sprintf(fpath, "%s/%s", path, dir_files[i]);
+
640 res = create_file(fpath, "", 0);
+
641 if (res == -1) {
+
642 cleanup_dir(path, dir_files, 1);
+
643 return -1;
+
644 }
+
645 }
+
646 res = check_dir_contents(path, dir_files);
+
647 if (res == -1) {
+
648 cleanup_dir(path, dir_files, 1);
+
649 return -1;
+
650 }
+
651
+
652 return 0;
+
653}
+
654
+
655static int test_truncate(int len)
+
656{
+
657 const char *data = testdata;
+
658 int datalen = testdatalen;
+
659 int res;
+
660
+
661 start_test("truncate(%u)", (int) len);
+
662 res = create_testfile(testfile, data, datalen);
+
663 if (res == -1)
+
664 return -1;
+
665
+
666 res = truncate(testfile, len);
+
667 if (res == -1) {
+
668 PERROR("truncate");
+
669 return -1;
+
670 }
+
671 res = check_testfile_size(testfile, len);
+
672 if (res == -1)
+
673 return -1;
+
674
+
675 if (len > 0) {
+
676 if (len <= datalen) {
+
677 res = check_data(testfile, data, 0, len);
+
678 if (res == -1)
+
679 return -1;
+
680 } else {
+
681 res = check_data(testfile, data, 0, datalen);
+
682 if (res == -1)
+
683 return -1;
+
684 res = check_data(testfile, zerodata, datalen,
+
685 len - datalen);
+
686 if (res == -1)
+
687 return -1;
+
688 }
+
689 }
+
690 res = unlink(testfile);
+
691 if (res == -1) {
+
692 PERROR("unlink");
+
693 return -1;
+
694 }
+
695 res = check_nonexist(testfile);
+
696 if (res == -1)
+
697 return -1;
+
698
+
699 success();
+
700 return 0;
+
701}
+
702
+
703static int test_ftruncate(int len, int mode)
+
704{
+
705 const char *data = testdata;
+
706 int datalen = testdatalen;
+
707 int res;
+
708 int fd;
+
709
+
710 start_test("ftruncate(%u) mode: 0%03o", len, mode);
+
711 res = create_testfile(testfile, data, datalen);
+
712 if (res == -1)
+
713 return -1;
+
714
+
715 fd = open(testfile, O_WRONLY);
+
716 if (fd == -1) {
+
717 PERROR("open");
+
718 return -1;
+
719 }
+
720
+
721 res = fchmod(fd, mode);
+
722 if (res == -1) {
+
723 PERROR("fchmod");
+
724 close(fd);
+
725 return -1;
+
726 }
+
727 res = check_testfile_mode(testfile, mode);
+
728 if (res == -1) {
+
729 close(fd);
+
730 return -1;
+
731 }
+
732 res = ftruncate(fd, len);
+
733 if (res == -1) {
+
734 PERROR("ftruncate");
+
735 close(fd);
+
736 return -1;
+
737 }
+
738 close(fd);
+
739 res = check_testfile_size(testfile, len);
+
740 if (res == -1)
+
741 return -1;
+
742
+
743 if (len > 0) {
+
744 if (len <= datalen) {
+
745 res = check_data(testfile, data, 0, len);
+
746 if (res == -1)
+
747 return -1;
+
748 } else {
+
749 res = check_data(testfile, data, 0, datalen);
+
750 if (res == -1)
+
751 return -1;
+
752 res = check_data(testfile, zerodata, datalen,
+
753 len - datalen);
+
754 if (res == -1)
+
755 return -1;
+
756 }
+
757 }
+
758 res = unlink(testfile);
+
759 if (res == -1) {
+
760 PERROR("unlink");
+
761 return -1;
+
762 }
+
763 res = check_nonexist(testfile);
+
764 if (res == -1)
+
765 return -1;
+
766
+
767 success();
+
768 return 0;
+
769}
+
770
+
771static int test_seekdir(void)
+
772{
+
773 int i;
+
774 int res;
+
775 DIR *dp;
+
776 struct dirent *de = NULL;
+
777
+
778 start_test("seekdir");
+
779 res = create_dir(testdir, testdir_files);
+
780 if (res == -1)
+
781 return res;
+
782
+
783 dp = opendir(testdir);
+
784 if (dp == NULL) {
+
785 PERROR("opendir");
+
786 return -1;
+
787 }
+
788
+
789 /* Remember dir offsets */
+
790 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
+
791 seekdir_offsets[i] = telldir(dp);
+
792 errno = 0;
+
793 de = readdir(dp);
+
794 if (de == NULL) {
+
795 if (errno) {
+
796 PERROR("readdir");
+
797 goto fail;
+
798 }
+
799 break;
+
800 }
+
801 }
+
802
+
803 /* Walk until the end of directory */
+
804 while (de)
+
805 de = readdir(dp);
+
806
+
807 /* Start from the last valid dir offset and seek backwards */
+
808 for (i--; i >= 0; i--) {
+
809 seekdir(dp, seekdir_offsets[i]);
+
810 de = readdir(dp);
+
811 if (de == NULL) {
+
812 ERROR("Unexpected end of directory after seekdir()");
+
813 goto fail;
+
814 }
+
815 }
+
816
+
817 closedir(dp);
+
818 res = cleanup_dir(testdir, testdir_files, 0);
+
819 if (!res)
+
820 success();
+
821 return res;
+
822fail:
+
823 closedir(dp);
+
824 cleanup_dir(testdir, testdir_files, 1);
+
825 return -1;
+
826}
+
827
+
828#ifdef HAVE_COPY_FILE_RANGE
+
829static int test_copy_file_range(void)
+
830{
+
831 const char *data = testdata;
+
832 int datalen = testdatalen;
+
833 int err = 0;
+
834 int res;
+
835 int fd_in, fd_out;
+
836 off_t pos_in = 0, pos_out = 0;
+
837
+
838 start_test("copy_file_range");
+
839 unlink(testfile);
+
840 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
+
841 if (fd_in == -1) {
+
842 PERROR("creat");
+
843 return -1;
+
844 }
+
845 res = write(fd_in, data, datalen);
+
846 if (res == -1) {
+
847 PERROR("write");
+
848 close(fd_in);
+
849 return -1;
+
850 }
+
851 if (res != datalen) {
+
852 ERROR("write is short: %u instead of %u", res, datalen);
+
853 close(fd_in);
+
854 return -1;
+
855 }
+
856
+
857 unlink(testfile2);
+
858 fd_out = creat(testfile2, 0644);
+
859 if (fd_out == -1) {
+
860 PERROR("creat");
+
861 close(fd_in);
+
862 return -1;
+
863 }
+
864 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
+
865 if (res == -1) {
+
866 PERROR("copy_file_range");
+
867 close(fd_in);
+
868 close(fd_out);
+
869 return -1;
+
870 }
+
871 if (res != datalen) {
+
872 ERROR("copy is short: %u instead of %u", res, datalen);
+
873 close(fd_in);
+
874 close(fd_out);
+
875 return -1;
+
876 }
+
877
+
878 res = close(fd_in);
+
879 if (res == -1) {
+
880 PERROR("close");
+
881 close(fd_out);
+
882 return -1;
+
883 }
+
884 res = close(fd_out);
+
885 if (res == -1) {
+
886 PERROR("close");
+
887 return -1;
+
888 }
+
889
+
890 err = check_data(testfile2, data, 0, datalen);
+
891
+
892 res = unlink(testfile);
+
893 if (res == -1) {
+
894 PERROR("unlink");
+
895 return -1;
+
896 }
+
897 res = check_nonexist(testfile);
+
898 if (res == -1)
+
899 return -1;
+
900 if (err)
+
901 return -1;
+
902
+
903 res = unlink(testfile2);
+
904 if (res == -1) {
+
905 PERROR("unlink");
+
906 return -1;
+
907 }
+
908 res = check_nonexist(testfile2);
+
909 if (res == -1)
+
910 return -1;
+
911 if (err)
+
912 return -1;
+
913
+
914 success();
+
915 return 0;
+
916}
+
917#else
+
918static int test_copy_file_range(void)
+
919{
+
920 return 0;
+
921}
+
922#endif
+
923
+
924static int test_utime(void)
+
925{
+
926 struct utimbuf utm;
+
927 time_t atime = 987631200;
+
928 time_t mtime = 123116400;
+
929 int res;
+
930
+
931 start_test("utime");
+
932 res = create_testfile(testfile, NULL, 0);
+
933 if (res == -1)
+
934 return -1;
+
935
+
936 utm.actime = atime;
+
937 utm.modtime = mtime;
+
938 res = utime(testfile, &utm);
+
939 if (res == -1) {
+
940 PERROR("utime");
+
941 return -1;
+
942 }
+
943 res = check_times(testfile, atime, mtime);
+
944 if (res == -1) {
+
945 return -1;
+
946 }
+
947 res = unlink(testfile);
+
948 if (res == -1) {
+
949 PERROR("unlink");
+
950 return -1;
+
951 }
+
952 res = check_nonexist(testfile);
+
953 if (res == -1)
+
954 return -1;
+
955
+
956 success();
+
957 return 0;
+
958}
+
959
+
960static int test_create(void)
+
961{
+
962 const char *data = testdata;
+
963 int datalen = testdatalen;
+
964 int err = 0;
+
965 int res;
+
966 int fd;
+
967
+
968 start_test("create");
+
969 unlink(testfile);
+
970 fd = creat(testfile, 0644);
+
971 if (fd == -1) {
+
972 PERROR("creat");
+
973 return -1;
+
974 }
+
975 res = write(fd, data, datalen);
+
976 if (res == -1) {
+
977 PERROR("write");
+
978 close(fd);
+
979 return -1;
+
980 }
+
981 if (res != datalen) {
+
982 ERROR("write is short: %u instead of %u", res, datalen);
+
983 close(fd);
+
984 return -1;
+
985 }
+
986 res = close(fd);
+
987 if (res == -1) {
+
988 PERROR("close");
+
989 return -1;
+
990 }
+
991 res = check_type(testfile, S_IFREG);
+
992 if (res == -1)
+
993 return -1;
+
994 err += check_mode(testfile, 0644);
+
995 err += check_nlink(testfile, 1);
+
996 err += check_size(testfile, datalen);
+
997 err += check_data(testfile, data, 0, datalen);
+
998 res = unlink(testfile);
+
999 if (res == -1) {
+
1000 PERROR("unlink");
+
1001 return -1;
+
1002 }
+
1003 res = check_nonexist(testfile);
+
1004 if (res == -1)
+
1005 return -1;
+
1006 if (err)
+
1007 return -1;
+
1008
+
1009 success();
+
1010 return 0;
+
1011}
+
1012
+
1013static int test_create_unlink(void)
+
1014{
+
1015 const char *data = testdata;
+
1016 int datalen = testdatalen;
+
1017 int err = 0;
+
1018 int res;
+
1019 int fd;
+
1020
+
1021 start_test("create+unlink");
+
1022 unlink(testfile);
+
1023 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
+
1024 if (fd == -1) {
+
1025 PERROR("creat");
+
1026 return -1;
+
1027 }
+
1028 res = unlink(testfile);
+
1029 if (res == -1) {
+
1030 PERROR("unlink");
+
1031 close(fd);
+
1032 return -1;
+
1033 }
+
1034 res = check_nonexist(testfile);
+
1035 if (res == -1) {
+
1036 close(fd);
+
1037 return -1;
+
1038 }
+
1039 res = write(fd, data, datalen);
+
1040 if (res == -1) {
+
1041 PERROR("write");
+
1042 close(fd);
+
1043 return -1;
+
1044 }
+
1045 if (res != datalen) {
+
1046 ERROR("write is short: %u instead of %u", res, datalen);
+
1047 close(fd);
+
1048 return -1;
+
1049 }
+
1050 struct stat st = {
+
1051 .st_mode = S_IFREG | 0644,
+
1052 .st_size = datalen,
+
1053 };
+
1054 err = fcheck_stat(fd, O_RDWR, &st);
+
1055 err += fcheck_data(fd, data, 0, datalen);
+
1056 res = close(fd);
+
1057 if (res == -1) {
+
1058 PERROR("close");
+
1059 err--;
+
1060 }
+
1061 if (err)
+
1062 return -1;
+
1063
+
1064 success();
+
1065 return 0;
+
1066}
+
1067
+
1068static int test_mknod(void)
+
1069{
+
1070 int err = 0;
+
1071 int res;
+
1072
+
1073 start_test("mknod");
+
1074 unlink(testfile);
+
1075 res = mknod(testfile, 0644, 0);
+
1076 if (res == -1) {
+
1077 PERROR("mknod");
+
1078 return -1;
+
1079 }
+
1080 res = check_type(testfile, S_IFREG);
+
1081 if (res == -1)
+
1082 return -1;
+
1083 err += check_mode(testfile, 0644);
+
1084 err += check_nlink(testfile, 1);
+
1085 err += check_size(testfile, 0);
+
1086 res = unlink(testfile);
+
1087 if (res == -1) {
+
1088 PERROR("unlink");
+
1089 return -1;
+
1090 }
+
1091 res = check_nonexist(testfile);
+
1092 if (res == -1)
+
1093 return -1;
+
1094 if (err)
+
1095 return -1;
+
1096
+
1097 success();
+
1098 return 0;
+
1099}
+
1100
+
1101#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
+
1102
+
1103static int do_test_open(int exist, int flags, const char *flags_str, int mode)
+
1104{
+
1105 char buf[4096];
+
1106 const char *data = testdata;
+
1107 int datalen = testdatalen;
+
1108 unsigned currlen = 0;
+
1109 int err = 0;
+
1110 int res;
+
1111 int fd;
+
1112 off_t off;
+
1113
+
1114 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
+
1115 unlink(testfile);
+
1116 if (exist) {
+
1117 res = create_file(testfile_r, testdata2, testdata2len);
+
1118 if (res == -1)
+
1119 return -1;
+
1120
+
1121 currlen = testdata2len;
+
1122 }
+
1123
+
1124 fd = open(testfile, flags, mode);
+
1125 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
+
1126 if (fd != -1) {
+
1127 ERROR("open should have failed");
+
1128 close(fd);
+
1129 return -1;
+
1130 } else if (errno == EEXIST)
+
1131 goto succ;
+
1132 }
+
1133 if (!(flags & O_CREAT) && !exist) {
+
1134 if (fd != -1) {
+
1135 ERROR("open should have failed");
+
1136 close(fd);
+
1137 return -1;
+
1138 } else if (errno == ENOENT)
+
1139 goto succ;
+
1140 }
+
1141 if (fd == -1) {
+
1142 PERROR("open");
+
1143 return -1;
+
1144 }
+
1145
+
1146 if (flags & O_TRUNC)
+
1147 currlen = 0;
+
1148
+
1149 err += check_type(testfile, S_IFREG);
+
1150 if (exist)
+
1151 err += check_mode(testfile, 0644);
+
1152 else
+
1153 err += check_mode(testfile, mode);
+
1154 err += check_nlink(testfile, 1);
+
1155 err += check_size(testfile, currlen);
+
1156 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
+
1157 err += check_data(testfile, testdata2, 0, testdata2len);
+
1158
+
1159 res = write(fd, data, datalen);
+
1160 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1161 if (res == -1) {
+
1162 PERROR("write");
+
1163 err --;
+
1164 } else if (res != datalen) {
+
1165 ERROR("write is short: %u instead of %u", res, datalen);
+
1166 err --;
+
1167 } else {
+
1168 if (datalen > (int) currlen)
+
1169 currlen = datalen;
+
1170
+
1171 err += check_size(testfile, currlen);
+
1172
+
1173 if (mode & S_IRUSR) {
+
1174 err += check_data(testfile, data, 0, datalen);
+
1175 if (exist && !(flags & O_TRUNC) &&
+
1176 testdata2len > datalen)
+
1177 err += check_data(testfile,
+
1178 testdata2 + datalen,
+
1179 datalen,
+
1180 testdata2len - datalen);
+
1181 }
+
1182 }
+
1183 } else {
+
1184 if (res != -1) {
+
1185 ERROR("write should have failed");
+
1186 err --;
+
1187 } else if (errno != EBADF) {
+
1188 PERROR("write");
+
1189 err --;
+
1190 }
+
1191 }
+
1192 off = lseek(fd, SEEK_SET, 0);
+
1193 if (off == (off_t) -1) {
+
1194 PERROR("lseek");
+
1195 err--;
+
1196 } else if (off != 0) {
+
1197 ERROR("offset should have returned 0");
+
1198 err --;
+
1199 }
+
1200 res = read(fd, buf, sizeof(buf));
+
1201 if ((flags & O_ACCMODE) != O_WRONLY) {
+
1202 if (res == -1) {
+
1203 PERROR("read");
+
1204 err--;
+
1205 } else {
+
1206 int readsize =
+
1207 currlen < sizeof(buf) ? currlen : sizeof(buf);
+
1208 if (res != readsize) {
+
1209 ERROR("read is short: %i instead of %u",
+
1210 res, readsize);
+
1211 err--;
+
1212 } else {
+
1213 if ((flags & O_ACCMODE) != O_RDONLY) {
+
1214 err += check_buffer(buf, data, datalen);
+
1215 if (exist && !(flags & O_TRUNC) &&
+
1216 testdata2len > datalen)
+
1217 err += check_buffer(buf + datalen,
+
1218 testdata2 + datalen,
+
1219 testdata2len - datalen);
+
1220 } else if (exist)
+
1221 err += check_buffer(buf, testdata2,
+
1222 testdata2len);
+
1223 }
+
1224 }
+
1225 } else {
+
1226 if (res != -1) {
+
1227 ERROR("read should have failed");
+
1228 err --;
+
1229 } else if (errno != EBADF) {
+
1230 PERROR("read");
+
1231 err --;
+
1232 }
+
1233 }
+
1234
+
1235 res = close(fd);
+
1236 if (res == -1) {
+
1237 PERROR("close");
+
1238 return -1;
+
1239 }
+
1240 res = unlink(testfile);
+
1241 if (res == -1) {
+
1242 PERROR("unlink");
+
1243 return -1;
+
1244 }
+
1245 res = check_nonexist(testfile);
+
1246 if (res == -1)
+
1247 return -1;
+
1248 res = check_nonexist(testfile_r);
+
1249 if (res == -1)
+
1250 return -1;
+
1251 if (err)
+
1252 return -1;
+
1253
+
1254succ:
+
1255 success();
+
1256 return 0;
+
1257}
+
1258
+
1259#define test_open_acc(flags, mode, err) \
+
1260 do_test_open_acc(flags, #flags, mode, err)
+
1261
+
1262static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
+
1263{
+
1264 const char *data = testdata;
+
1265 int datalen = testdatalen;
+
1266 int res;
+
1267 int fd;
+
1268
+
1269 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
+
1270 strerror(err));
+
1271 unlink(testfile);
+
1272 res = create_testfile(testfile, data, datalen);
+
1273 if (res == -1)
+
1274 return -1;
+
1275
+
1276 res = chmod(testfile, mode);
+
1277 if (res == -1) {
+
1278 PERROR("chmod");
+
1279 return -1;
+
1280 }
+
1281
+
1282 res = check_testfile_mode(testfile, mode);
+
1283 if (res == -1)
+
1284 return -1;
+
1285
+
1286 fd = open(testfile, flags);
+
1287 if (fd == -1) {
+
1288 if (err != errno) {
+
1289 PERROR("open");
+
1290 return -1;
+
1291 }
+
1292 } else {
+
1293 if (err) {
+
1294 ERROR("open should have failed");
+
1295 close(fd);
+
1296 return -1;
+
1297 }
+
1298 close(fd);
+
1299 }
+
1300
+
1301 res = unlink(testfile);
+
1302 if (res == -1) {
+
1303 PERROR("unlink");
+
1304 return -1;
+
1305 }
+
1306 res = check_nonexist(testfile);
+
1307 if (res == -1)
+
1308 return -1;
+
1309 res = check_nonexist(testfile_r);
+
1310 if (res == -1)
+
1311 return -1;
+
1312
+
1313 success();
+
1314 return 0;
+
1315}
+
1316
+
1317static int test_symlink(void)
+
1318{
+
1319 char buf[1024];
+
1320 const char *data = testdata;
+
1321 int datalen = testdatalen;
+
1322 int linklen = strlen(testfile);
+
1323 int err = 0;
+
1324 int res;
+
1325
+
1326 start_test("symlink");
+
1327 res = create_testfile(testfile, data, datalen);
+
1328 if (res == -1)
+
1329 return -1;
+
1330
+
1331 unlink(testfile2);
+
1332 res = symlink(testfile, testfile2);
+
1333 if (res == -1) {
+
1334 PERROR("symlink");
+
1335 return -1;
+
1336 }
+
1337 res = check_type(testfile2, S_IFLNK);
+
1338 if (res == -1)
+
1339 return -1;
+
1340 err += check_mode(testfile2, 0777);
+
1341 err += check_nlink(testfile2, 1);
+
1342 res = readlink(testfile2, buf, sizeof(buf));
+
1343 if (res == -1) {
+
1344 PERROR("readlink");
+
1345 err--;
+
1346 }
+
1347 if (res != linklen) {
+
1348 ERROR("short readlink: %u instead of %u", res, linklen);
+
1349 err--;
+
1350 }
+
1351 if (memcmp(buf, testfile, linklen) != 0) {
+
1352 ERROR("link mismatch");
+
1353 err--;
+
1354 }
+
1355 err += check_size(testfile2, datalen);
+
1356 err += check_data(testfile2, data, 0, datalen);
+
1357 res = unlink(testfile2);
+
1358 if (res == -1) {
+
1359 PERROR("unlink");
+
1360 return -1;
+
1361 }
+
1362 res = check_nonexist(testfile2);
+
1363 if (res == -1)
+
1364 return -1;
+
1365 if (err)
+
1366 return -1;
+
1367
+
1368 res = unlink(testfile);
+
1369 if (res == -1) {
+
1370 PERROR("unlink");
+
1371 return -1;
+
1372 }
+
1373 res = check_nonexist(testfile);
+
1374 if (res == -1)
+
1375 return -1;
+
1376
+
1377 success();
+
1378 return 0;
+
1379}
+
1380
+
1381static int test_link(void)
+
1382{
+
1383 const char *data = testdata;
+
1384 int datalen = testdatalen;
+
1385 int err = 0;
+
1386 int res;
+
1387
+
1388 start_test("link");
+
1389 res = create_testfile(testfile, data, datalen);
+
1390 if (res == -1)
+
1391 return -1;
+
1392
+
1393 unlink(testfile2);
+
1394 res = link(testfile, testfile2);
+
1395 if (res == -1) {
+
1396 PERROR("link");
+
1397 return -1;
+
1398 }
+
1399 res = check_type(testfile2, S_IFREG);
+
1400 if (res == -1)
+
1401 return -1;
+
1402 err += check_mode(testfile2, 0644);
+
1403 err += check_nlink(testfile2, 2);
+
1404 err += check_size(testfile2, datalen);
+
1405 err += check_data(testfile2, data, 0, datalen);
+
1406 res = unlink(testfile);
+
1407 if (res == -1) {
+
1408 PERROR("unlink");
+
1409 return -1;
+
1410 }
+
1411 res = check_nonexist(testfile);
+
1412 if (res == -1)
+
1413 return -1;
+
1414
+
1415 err += check_nlink(testfile2, 1);
+
1416 res = unlink(testfile2);
+
1417 if (res == -1) {
+
1418 PERROR("unlink");
+
1419 return -1;
+
1420 }
+
1421 res = check_nonexist(testfile2);
+
1422 if (res == -1)
+
1423 return -1;
+
1424 if (err)
+
1425 return -1;
+
1426
+
1427 success();
+
1428 return 0;
+
1429}
+
1430
+
1431static int test_link2(void)
+
1432{
+
1433 const char *data = testdata;
+
1434 int datalen = testdatalen;
+
1435 int err = 0;
+
1436 int res;
+
1437
+
1438 start_test("link-unlink-link");
+
1439 res = create_testfile(testfile, data, datalen);
+
1440 if (res == -1)
+
1441 return -1;
+
1442
+
1443 unlink(testfile2);
+
1444 res = link(testfile, testfile2);
+
1445 if (res == -1) {
+
1446 PERROR("link");
+
1447 return -1;
+
1448 }
+
1449 res = unlink(testfile);
+
1450 if (res == -1) {
+
1451 PERROR("unlink");
+
1452 return -1;
+
1453 }
+
1454 res = check_nonexist(testfile);
+
1455 if (res == -1)
+
1456 return -1;
+
1457 res = link(testfile2, testfile);
+
1458 if (res == -1) {
+
1459 PERROR("link");
+
1460 }
+
1461 res = check_type(testfile, S_IFREG);
+
1462 if (res == -1)
+
1463 return -1;
+
1464 err += check_mode(testfile, 0644);
+
1465 err += check_nlink(testfile, 2);
+
1466 err += check_size(testfile, datalen);
+
1467 err += check_data(testfile, data, 0, datalen);
+
1468
+
1469 res = unlink(testfile2);
+
1470 if (res == -1) {
+
1471 PERROR("unlink");
+
1472 return -1;
+
1473 }
+
1474 err += check_nlink(testfile, 1);
+
1475 res = unlink(testfile);
+
1476 if (res == -1) {
+
1477 PERROR("unlink");
+
1478 return -1;
+
1479 }
+
1480 res = check_nonexist(testfile);
+
1481 if (res == -1)
+
1482 return -1;
+
1483 if (err)
+
1484 return -1;
+
1485
+
1486 success();
+
1487 return 0;
+
1488}
+
1489
+
1490static int test_rename_file(void)
+
1491{
+
1492 const char *data = testdata;
+
1493 int datalen = testdatalen;
+
1494 int err = 0;
+
1495 int res;
+
1496
+
1497 start_test("rename file");
+
1498 res = create_testfile(testfile, data, datalen);
+
1499 if (res == -1)
+
1500 return -1;
+
1501
+
1502 unlink(testfile2);
+
1503 res = rename(testfile, testfile2);
+
1504 if (res == -1) {
+
1505 PERROR("rename");
+
1506 return -1;
+
1507 }
+
1508 res = check_nonexist(testfile);
+
1509 if (res == -1)
+
1510 return -1;
+
1511 res = check_type(testfile2, S_IFREG);
+
1512 if (res == -1)
+
1513 return -1;
+
1514 err += check_mode(testfile2, 0644);
+
1515 err += check_nlink(testfile2, 1);
+
1516 err += check_size(testfile2, datalen);
+
1517 err += check_data(testfile2, data, 0, datalen);
+
1518 res = unlink(testfile2);
+
1519 if (res == -1) {
+
1520 PERROR("unlink");
+
1521 return -1;
+
1522 }
+
1523 res = check_nonexist(testfile2);
+
1524 if (res == -1)
+
1525 return -1;
+
1526 if (err)
+
1527 return -1;
+
1528
+
1529 success();
+
1530 return 0;
+
1531}
+
1532
+
1533static int test_rename_dir(void)
+
1534{
+
1535 int err = 0;
+
1536 int res;
+
1537
+
1538 start_test("rename dir");
+
1539 res = create_dir(testdir, testdir_files);
+
1540 if (res == -1)
+
1541 return -1;
+
1542
+
1543 rmdir(testdir2);
+
1544 res = rename(testdir, testdir2);
+
1545 if (res == -1) {
+
1546 PERROR("rename");
+
1547 cleanup_dir(testdir, testdir_files, 1);
+
1548 return -1;
+
1549 }
+
1550 res = check_nonexist(testdir);
+
1551 if (res == -1) {
+
1552 cleanup_dir(testdir, testdir_files, 1);
+
1553 return -1;
+
1554 }
+
1555 res = check_type(testdir2, S_IFDIR);
+
1556 if (res == -1) {
+
1557 cleanup_dir(testdir2, testdir_files, 1);
+
1558 return -1;
+
1559 }
+
1560 err += check_mode(testdir2, 0755);
+
1561 err += check_dir_contents(testdir2, testdir_files);
+
1562 err += cleanup_dir(testdir2, testdir_files, 0);
+
1563 res = rmdir(testdir2);
+
1564 if (res == -1) {
+
1565 PERROR("rmdir");
+
1566 return -1;
+
1567 }
+
1568 res = check_nonexist(testdir2);
+
1569 if (res == -1)
+
1570 return -1;
+
1571 if (err)
+
1572 return -1;
+
1573
+
1574 success();
+
1575 return 0;
+
1576}
+
1577
+
1578static int test_rename_dir_loop(void)
+
1579{
+
1580#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
+
1581#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
+
1582
+
1583 char path[1280], path2[1280];
+
1584 int err = 0;
+
1585 int res;
+
1586
+
1587 start_test("rename dir loop");
+
1588
+
1589 res = create_dir(testdir, testdir_files);
+
1590 if (res == -1)
+
1591 return -1;
+
1592
+
1593 res = mkdir(PATH("a"), 0755);
+
1594 if (res == -1) {
+
1595 PERROR("mkdir");
+
1596 goto fail;
+
1597 }
+
1598
+
1599 res = rename(PATH("a"), PATH2("a"));
+
1600 if (res == -1) {
+
1601 PERROR("rename");
+
1602 goto fail;
+
1603 }
+
1604
+
1605 errno = 0;
+
1606 res = rename(PATH("a"), PATH2("a/b"));
+
1607 if (res == 0 || errno != EINVAL) {
+
1608 PERROR("rename");
+
1609 goto fail;
+
1610 }
+
1611
+
1612 res = mkdir(PATH("a/b"), 0755);
+
1613 if (res == -1) {
+
1614 PERROR("mkdir");
+
1615 goto fail;
+
1616 }
+
1617
+
1618 res = mkdir(PATH("a/b/c"), 0755);
+
1619 if (res == -1) {
+
1620 PERROR("mkdir");
+
1621 goto fail;
+
1622 }
+
1623
+
1624 errno = 0;
+
1625 res = rename(PATH("a"), PATH2("a/b/c"));
+
1626 if (res == 0 || errno != EINVAL) {
+
1627 PERROR("rename");
+
1628 goto fail;
+
1629 }
+
1630
+
1631 errno = 0;
+
1632 res = rename(PATH("a"), PATH2("a/b/c/a"));
+
1633 if (res == 0 || errno != EINVAL) {
+
1634 PERROR("rename");
+
1635 goto fail;
+
1636 }
+
1637
+
1638 errno = 0;
+
1639 res = rename(PATH("a/b/c"), PATH2("a"));
+
1640 if (res == 0 || errno != ENOTEMPTY) {
+
1641 PERROR("rename");
+
1642 goto fail;
+
1643 }
+
1644
+
1645 res = open(PATH("a/foo"), O_CREAT, 0644);
+
1646 if (res == -1) {
+
1647 PERROR("open");
+
1648 goto fail;
+
1649 }
+
1650 close(res);
+
1651
+
1652 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1653 if (res == -1) {
+
1654 PERROR("rename");
+
1655 goto fail;
+
1656 }
+
1657
+
1658 res = rename(PATH("a/bar"), PATH2("a/foo"));
+
1659 if (res == -1) {
+
1660 PERROR("rename");
+
1661 goto fail;
+
1662 }
+
1663
+
1664 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
+
1665 if (res == -1) {
+
1666 PERROR("rename");
+
1667 goto fail;
+
1668 }
+
1669
+
1670 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
+
1671 if (res == -1) {
+
1672 PERROR("rename");
+
1673 goto fail;
+
1674 }
+
1675
+
1676 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
+
1677 if (res == -1) {
+
1678 PERROR("rename");
+
1679 goto fail;
+
1680 }
+
1681
+
1682 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
+
1683 if (res == -1) {
+
1684 PERROR("rename");
+
1685 goto fail;
+
1686 }
+
1687
+
1688 res = open(PATH("a/bar"), O_CREAT, 0644);
+
1689 if (res == -1) {
+
1690 PERROR("open");
+
1691 goto fail;
+
1692 }
+
1693 close(res);
+
1694
+
1695 res = rename(PATH("a/foo"), PATH2("a/bar"));
+
1696 if (res == -1) {
+
1697 PERROR("rename");
+
1698 goto fail;
+
1699 }
+
1700
+
1701 unlink(PATH("a/bar"));
+
1702
+
1703 res = rename(PATH("a/b"), PATH2("a/d"));
+
1704 if (res == -1) {
+
1705 PERROR("rename");
+
1706 goto fail;
+
1707 }
+
1708
+
1709 res = rename(PATH("a/d"), PATH2("a/b"));
+
1710 if (res == -1) {
+
1711 PERROR("rename");
+
1712 goto fail;
+
1713 }
+
1714
+
1715 res = mkdir(PATH("a/d"), 0755);
+
1716 if (res == -1) {
+
1717 PERROR("mkdir");
+
1718 goto fail;
+
1719 }
+
1720
+
1721 res = rename(PATH("a/b"), PATH2("a/d"));
+
1722 if (res == -1) {
+
1723 PERROR("rename");
+
1724 goto fail;
+
1725 }
+
1726
+
1727 res = rename(PATH("a/d"), PATH2("a/b"));
+
1728 if (res == -1) {
+
1729 PERROR("rename");
+
1730 goto fail;
+
1731 }
+
1732
+
1733 res = mkdir(PATH("a/d"), 0755);
+
1734 if (res == -1) {
+
1735 PERROR("mkdir");
+
1736 goto fail;
+
1737 }
+
1738
+
1739 res = mkdir(PATH("a/d/e"), 0755);
+
1740 if (res == -1) {
+
1741 PERROR("mkdir");
+
1742 goto fail;
+
1743 }
+
1744
+
1745 errno = 0;
+
1746 res = rename(PATH("a/b"), PATH2("a/d"));
+
1747 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
+
1748 PERROR("rename");
+
1749 goto fail;
+
1750 }
+
1751
+
1752 rmdir(PATH("a/d/e"));
+
1753 rmdir(PATH("a/d"));
+
1754
+
1755 rmdir(PATH("a/b/c"));
+
1756 rmdir(PATH("a/b"));
+
1757 rmdir(PATH("a"));
+
1758
+
1759 err += cleanup_dir(testdir, testdir_files, 0);
+
1760 res = rmdir(testdir);
+
1761 if (res == -1) {
+
1762 PERROR("rmdir");
+
1763 goto fail;
+
1764 }
+
1765 res = check_nonexist(testdir);
+
1766 if (res == -1)
+
1767 return -1;
+
1768 if (err)
+
1769 return -1;
+
1770
+
1771 success();
+
1772 return 0;
+
1773
+
1774fail:
+
1775 unlink(PATH("a/bar"));
+
1776
+
1777 rmdir(PATH("a/d/e"));
+
1778 rmdir(PATH("a/d"));
+
1779
+
1780 rmdir(PATH("a/b/c"));
+
1781 rmdir(PATH("a/b"));
+
1782 rmdir(PATH("a"));
+
1783
+
1784 cleanup_dir(testdir, testdir_files, 1);
+
1785 rmdir(testdir);
+
1786
+
1787 return -1;
+
1788
+
1789#undef PATH2
+
1790#undef PATH
+
1791}
+
1792
+
1793static int test_mkfifo(void)
+
1794{
+
1795 int res;
+
1796 int err = 0;
+
1797
+
1798 start_test("mkfifo");
+
1799 unlink(testfile);
+
1800 res = mkfifo(testfile, 0644);
+
1801 if (res == -1) {
+
1802 PERROR("mkfifo");
+
1803 return -1;
+
1804 }
+
1805 res = check_type(testfile, S_IFIFO);
+
1806 if (res == -1)
+
1807 return -1;
+
1808 err += check_mode(testfile, 0644);
+
1809 err += check_nlink(testfile, 1);
+
1810 res = unlink(testfile);
+
1811 if (res == -1) {
+
1812 PERROR("unlink");
+
1813 return -1;
+
1814 }
+
1815 res = check_nonexist(testfile);
+
1816 if (res == -1)
+
1817 return -1;
+
1818 if (err)
+
1819 return -1;
+
1820
+
1821 success();
+
1822 return 0;
+
1823}
+
1824
+
1825static int test_mkdir(void)
+
1826{
+
1827 int res;
+
1828 int err = 0;
+
1829 const char *dir_contents[] = {NULL};
+
1830
+
1831 start_test("mkdir");
+
1832 rmdir(testdir);
+
1833 res = mkdir(testdir, 0755);
+
1834 if (res == -1) {
+
1835 PERROR("mkdir");
+
1836 return -1;
+
1837 }
+
1838 res = check_type(testdir, S_IFDIR);
+
1839 if (res == -1)
+
1840 return -1;
+
1841 err += check_mode(testdir, 0755);
+
1842 /* Some file systems (like btrfs) don't track link
+
1843 count for directories */
+
1844 //err += check_nlink(testdir, 2);
+
1845 err += check_dir_contents(testdir, dir_contents);
+
1846 res = rmdir(testdir);
+
1847 if (res == -1) {
+
1848 PERROR("rmdir");
+
1849 return -1;
+
1850 }
+
1851 res = check_nonexist(testdir);
+
1852 if (res == -1)
+
1853 return -1;
+
1854 if (err)
+
1855 return -1;
+
1856
+
1857 success();
+
1858 return 0;
+
1859}
+
1860
+
1861static int test_socket(void)
+
1862{
+
1863 struct sockaddr_un su;
+
1864 int fd;
+
1865 int res;
+
1866 int err = 0;
+
1867 const size_t test_sock_len = strlen(testsock) + 1;
+
1868
+
1869 start_test("socket");
+
1870 if (test_sock_len > sizeof(su.sun_path)) {
+
1871 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
+
1872 strlen(testsock) + 1 - sizeof(su.sun_path));
+
1873 return -1;
+
1874 }
+
1875 unlink(testsock);
+
1876 fd = socket(AF_UNIX, SOCK_STREAM, 0);
+
1877 if (fd < 0) {
+
1878 PERROR("socket");
+
1879 return -1;
+
1880 }
+
1881 su.sun_family = AF_UNIX;
+
1882
+
1883 strncpy(su.sun_path, testsock, test_sock_len);
+
1884 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
+
1885 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
+
1886 if (res == -1) {
+
1887 PERROR("bind");
+
1888 return -1;
+
1889 }
+
1890
+
1891 res = check_type(testsock, S_IFSOCK);
+
1892 if (res == -1) {
+
1893 close(fd);
+
1894 return -1;
+
1895 }
+
1896 err += check_nlink(testsock, 1);
+
1897 close(fd);
+
1898 res = unlink(testsock);
+
1899 if (res == -1) {
+
1900 PERROR("unlink");
+
1901 return -1;
+
1902 }
+
1903 res = check_nonexist(testsock);
+
1904 if (res == -1)
+
1905 return -1;
+
1906 if (err)
+
1907 return -1;
+
1908
+
1909 success();
+
1910 return 0;
+
1911}
+
1912
+
1913#define test_create_ro_dir(flags) \
+
1914 do_test_create_ro_dir(flags, #flags)
+
1915
+
1916static int do_test_create_ro_dir(int flags, const char *flags_str)
+
1917{
+
1918 int res;
+
1919 int err = 0;
+
1920 int fd;
+
1921
+
1922 start_test("open(%s) in read-only directory", flags_str);
+
1923 rmdir(testdir);
+
1924 res = mkdir(testdir, 0555);
+
1925 if (res == -1) {
+
1926 PERROR("mkdir");
+
1927 return -1;
+
1928 }
+
1929 fd = open(subfile, flags, 0644);
+
1930 if (fd != -1) {
+
1931 close(fd);
+
1932 unlink(subfile);
+
1933 ERROR("open should have failed");
+
1934 err--;
+
1935 } else {
+
1936 res = check_nonexist(subfile);
+
1937 if (res == -1)
+
1938 err--;
+
1939 }
+
1940 unlink(subfile);
+
1941 res = rmdir(testdir);
+
1942 if (res == -1) {
+
1943 PERROR("rmdir");
+
1944 return -1;
+
1945 }
+
1946 res = check_nonexist(testdir);
+
1947 if (res == -1)
+
1948 return -1;
+
1949 if (err)
+
1950 return -1;
+
1951
+
1952 success();
+
1953 return 0;
+
1954}
+
1955
+
1956#ifndef __FreeBSD__
+
1957/* this tests open with O_TMPFILE
+
1958 note that this will only work with the fuse low level api
+
1959 you will get ENOTSUP with the high level api */
+
1960static int test_create_tmpfile(void)
+
1961{
+
1962 rmdir(testdir);
+
1963 int res = mkdir(testdir, 0777);
+
1964 if (res)
+
1965 return -1;
+
1966
+
1967 start_test("create tmpfile");
+
1968
+
1969 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
1970 if(fd == -1) {
+
1971 if (errno == ENOTSUP) {
+
1972 /* don't bother if we're working on an old kernel
+
1973 or on the high level API */
+
1974 return 0;
+
1975 }
+
1976
+
1977 PERROR("open O_TMPFILE | O_RDWR");
+
1978 return -1;
+
1979 }
+
1980 close(fd);
+
1981
+
1982 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
+
1983 if(fd == -1){
+
1984 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
+
1985 return -1;
+
1986 };
+
1987 close(fd);
+
1988
+
1989 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
+
1990 if (fd != -1) {
+
1991 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
+
1992 return -1;
+
1993 }
+
1994
+
1995 success();
+
1996 return 0;
+
1997}
+
1998
+
1999static int test_create_and_link_tmpfile(void)
+
2000{
+
2001 /* skip this test for now since the github runner will fail in the linkat call below */
+
2002 return 0;
+
2003
+
2004 rmdir(testdir);
+
2005 unlink(testfile);
+
2006
+
2007 int res = mkdir(testdir, 0777);
+
2008 if (res)
+
2009 return -1;
+
2010
+
2011 start_test("create and link tmpfile");
+
2012
+
2013 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
+
2014 if(fd == -1) {
+
2015 if (errno == ENOTSUP) {
+
2016 /* don't bother if we're working on an old kernel
+
2017 or on the high level API */
+
2018 return 0;
+
2019 }
+
2020 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
+
2021 return -1;
+
2022 }
+
2023
+
2024 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2025 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
+
2026 return -1;
+
2027 }
+
2028 close(fd);
+
2029
+
2030 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
2031 if(fd == -1) {
+
2032 PERROR("open O_TMPFILE");
+
2033 return -1;
+
2034 }
+
2035
+
2036 if (check_nonexist(testfile)) {
+
2037 return -1;
+
2038 }
+
2039
+
2040 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
+
2041 PERROR("linkat tempfile");
+
2042 return -1;
+
2043 }
+
2044 close(fd);
+
2045
+
2046 if (check_nlink(testfile, 1)) {
+
2047 return -1;
+
2048 }
+
2049 unlink(testfile);
+
2050
+
2051 success();
+
2052 return 0;
+
2053}
+
2054#endif
+
2055
+
2056int main(int argc, char *argv[])
+
2057{
+
2058 int err = 0;
+
2059 int a;
+
2060 int is_root;
+
2061
+
2062 umask(0);
+
2063 if (argc < 2 || argc > 4) {
+
2064 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
+
2065 return 1;
+
2066 }
+
2067 basepath = argv[1];
+
2068 basepath_r = basepath;
+
2069 for (a = 2; a < argc; a++) {
+
2070 char *endptr;
+
2071 char *arg = argv[a];
+
2072 if (arg[0] == ':') {
+
2073 basepath_r = arg + 1;
+
2074 } else {
+
2075 if (arg[0] == '-') {
+
2076 arg++;
+
2077 if (arg[0] == 'u') {
+
2078 unlinked_test = 1;
+
2079 endptr = arg + 1;
+
2080 } else {
+
2081 skip_test = strtoul(arg, &endptr, 10);
+
2082 }
+
2083 } else {
+
2084 select_test = strtoul(arg, &endptr, 10);
+
2085 }
+
2086 if (arg[0] == '\0' || *endptr != '\0') {
+
2087 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
+
2088 return 1;
+
2089 }
+
2090 }
+
2091 }
+
2092 assert(strlen(basepath) < 512);
+
2093 assert(strlen(basepath_r) < 512);
+
2094 if (basepath[0] != '/') {
+
2095 fprintf(stderr, "testdir must be an absolute path\n");
+
2096 return 1;
+
2097 }
+
2098
+
2099 sprintf(testfile, "%s/testfile", basepath);
+
2100 sprintf(testfile2, "%s/testfile2", basepath);
+
2101 sprintf(testdir, "%s/testdir", basepath);
+
2102 sprintf(testdir2, "%s/testdir2", basepath);
+
2103 sprintf(subfile, "%s/subfile", testdir2);
+
2104 sprintf(testsock, "%s/testsock", basepath);
+
2105
+
2106 sprintf(testfile_r, "%s/testfile", basepath_r);
+
2107 sprintf(testfile2_r, "%s/testfile2", basepath_r);
+
2108 sprintf(testdir_r, "%s/testdir", basepath_r);
+
2109 sprintf(testdir2_r, "%s/testdir2", basepath_r);
+
2110 sprintf(subfile_r, "%s/subfile", testdir2_r);
+
2111
+
2112 is_root = (geteuid() == 0);
+
2113
+
2114 err += test_create();
+
2115 err += test_create_unlink();
+
2116 err += test_symlink();
+
2117 err += test_link();
+
2118 err += test_link2();
+
2119 err += test_mknod();
+
2120 err += test_mkfifo();
+
2121 err += test_mkdir();
+
2122 err += test_rename_file();
+
2123 err += test_rename_dir();
+
2124 err += test_rename_dir_loop();
+
2125 err += test_seekdir();
+
2126 err += test_socket();
+
2127 err += test_utime();
+
2128 err += test_truncate(0);
+
2129 err += test_truncate(testdatalen / 2);
+
2130 err += test_truncate(testdatalen);
+
2131 err += test_truncate(testdatalen + 100);
+
2132 err += test_ftruncate(0, 0600);
+
2133 err += test_ftruncate(testdatalen / 2, 0600);
+
2134 err += test_ftruncate(testdatalen, 0600);
+
2135 err += test_ftruncate(testdatalen + 100, 0600);
+
2136 err += test_ftruncate(0, 0400);
+
2137 err += test_ftruncate(0, 0200);
+
2138 err += test_ftruncate(0, 0000);
+
2139 err += test_open(0, O_RDONLY, 0);
+
2140 err += test_open(1, O_RDONLY, 0);
+
2141 err += test_open(1, O_RDWR, 0);
+
2142 err += test_open(1, O_WRONLY, 0);
+
2143 err += test_open(0, O_RDWR | O_CREAT, 0600);
+
2144 err += test_open(1, O_RDWR | O_CREAT, 0600);
+
2145 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2146 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
2147 err += test_open(0, O_RDONLY | O_CREAT, 0600);
+
2148 err += test_open(0, O_RDONLY | O_CREAT, 0400);
+
2149 err += test_open(0, O_RDONLY | O_CREAT, 0200);
+
2150 err += test_open(0, O_RDONLY | O_CREAT, 0000);
+
2151 err += test_open(0, O_WRONLY | O_CREAT, 0600);
+
2152 err += test_open(0, O_WRONLY | O_CREAT, 0400);
+
2153 err += test_open(0, O_WRONLY | O_CREAT, 0200);
+
2154 err += test_open(0, O_WRONLY | O_CREAT, 0000);
+
2155 err += test_open(0, O_RDWR | O_CREAT, 0400);
+
2156 err += test_open(0, O_RDWR | O_CREAT, 0200);
+
2157 err += test_open(0, O_RDWR | O_CREAT, 0000);
+
2158 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2159 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
+
2160 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2161 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
+
2162 err += test_open_acc(O_RDONLY, 0600, 0);
+
2163 err += test_open_acc(O_WRONLY, 0600, 0);
+
2164 err += test_open_acc(O_RDWR, 0600, 0);
+
2165 err += test_open_acc(O_RDONLY, 0400, 0);
+
2166 err += test_open_acc(O_WRONLY, 0200, 0);
+
2167 if(!is_root) {
+
2168 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
+
2169 err += test_open_acc(O_WRONLY, 0400, EACCES);
+
2170 err += test_open_acc(O_RDWR, 0400, EACCES);
+
2171 err += test_open_acc(O_RDONLY, 0200, EACCES);
+
2172 err += test_open_acc(O_RDWR, 0200, EACCES);
+
2173 err += test_open_acc(O_RDONLY, 0000, EACCES);
+
2174 err += test_open_acc(O_WRONLY, 0000, EACCES);
+
2175 err += test_open_acc(O_RDWR, 0000, EACCES);
+
2176 }
+
2177 err += test_create_ro_dir(O_CREAT);
+
2178 err += test_create_ro_dir(O_CREAT | O_EXCL);
+
2179 err += test_create_ro_dir(O_CREAT | O_WRONLY);
+
2180 err += test_create_ro_dir(O_CREAT | O_TRUNC);
+
2181 err += test_copy_file_range();
+
2182#ifndef __FreeBSD__
+
2183 err += test_create_tmpfile();
+
2184 err += test_create_and_link_tmpfile();
+
2185#endif
+
2186
+
2187 unlink(testfile2);
+
2188 unlink(testsock);
+
2189 rmdir(testdir);
+
2190 rmdir(testdir2);
+
2191
+
2192 if (err) {
+
2193 fprintf(stderr, "%i tests failed\n", -err);
+
2194 return 1;
+
2195 }
+
2196
+
2197 return check_unlinked_testfiles();
+
2198}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2test__want__conversion_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2test__want__conversion_8c_source.html new file mode 100644 index 0000000..1c71229 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2test__want__conversion_8c_source.html @@ -0,0 +1,225 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/test_want_conversion.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_want_conversion.c
+
+
+
1#include "util.h"
+
2#include <string.h>
+
3#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
+
4
+
5#include "fuse_i.h"
+
6#include <stdio.h>
+
7#include <assert.h>
+
8#include <inttypes.h>
+
9
+
10static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
+
11{
+
12 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 "\n", prefix,
+
13 conn->want, conn->want_ext);
+
14}
+
15
+
16static void application_init(struct fuse_conn_info *conn)
+
17{
+
18 /* Simulate application init */
+ +
20 conn->want &= ~FUSE_CAP_SPLICE_READ;
+
21}
+
22
+
23static void test_fuse_fs_init(struct fuse_conn_info *conn)
+
24{
+
25 uint64_t want_ext_default = conn->want_ext;
+
26 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
+
27 int rc;
+
28
+
29 /* High-level init */
+
30 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
+
31
+
32 conn->want = want_default;
+
33
+
34 application_init(conn);
+
35
+
36 rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
+
37 assert(rc == 0);
+
38}
+
39
+
40static void test_do_init(struct fuse_conn_info *conn)
+
41{
+
42 /* Initial setup */
+ + + +
46 conn->capable = fuse_lower_32_bits(conn->capable_ext);
+
47 conn->want_ext = conn->capable_ext;
+
48
+
49 print_conn_info("Initial state", conn);
+
50
+
51 uint64_t want_ext_default = conn->want_ext;
+
52 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
+
53 int rc;
+
54
+
55 conn->want = want_default;
+
56 conn->capable = fuse_lower_32_bits(conn->capable_ext);
+
57
+
58 test_fuse_fs_init(conn);
+
59
+
60 rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
+
61 assert(rc == 0);
+
62
+
63 /* Verify all expected flags are set */
+
64 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
+
65 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
+
66 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
+
67 assert(conn->want_ext & FUSE_CAP_POSIX_LOCKS);
+
68 assert(conn->want_ext & FUSE_CAP_FLOCK_LOCKS);
+
69 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
+
70 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
+
71 /* Verify no other flags are set */
+
72 assert(conn->want_ext ==
+ + + +
76
+
77 print_conn_info("After init", conn);
+
78}
+
79
+
80static void test_want_conversion_basic(void)
+
81{
+
82 struct fuse_conn_info conn = { 0 };
+
83
+
84 printf("\nTesting basic want conversion:\n");
+
85 test_do_init(&conn);
+
86 print_conn_info("After init", &conn);
+
87}
+
88
+
89static void test_want_conversion_conflict(void)
+
90{
+
91 struct fuse_conn_info conn = { 0 };
+
92 int rc;
+
93
+
94 printf("\nTesting want conversion conflict:\n");
+
95
+
96 /* Test conflicting values */
+
97 /* Initialize like fuse_lowlevel.c does */
+ + + +
101 conn.capable = fuse_lower_32_bits(conn.capable_ext);
+
102 conn.want_ext = conn.capable_ext;
+
103 conn.want = fuse_lower_32_bits(conn.want_ext);
+
104 print_conn_info("Test conflict initial", &conn);
+
105
+
106 /* Initialize default values like in basic test */
+
107 uint64_t want_ext_default_ll = conn.want_ext;
+
108 uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll);
+
109
+
110 /* Simulate application init modifying capabilities */
+
111 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
+
112 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
+
113
+
114 rc = convert_to_conn_want_ext(&conn, want_ext_default_ll,
+
115 want_default_ll);
+
116 assert(rc == -EINVAL);
+
117 print_conn_info("Test conflict after", &conn);
+
118
+
119 printf("Want conversion conflict test passed\n");
+
120}
+
121
+
122static void test_want_conversion_high_bits(void)
+
123{
+
124 struct fuse_conn_info conn = { 0 };
+
125 int rc;
+
126
+
127 printf("\nTesting want conversion high bits preservation:\n");
+
128
+
129 /* Test high bits preservation */
+
130 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
+
131 conn.want = fuse_lower_32_bits(conn.want_ext);
+
132 print_conn_info("Test high bits initial", &conn);
+
133
+
134 uint64_t want_ext_default_ll = conn.want_ext;
+
135 uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll);
+
136
+
137 rc = convert_to_conn_want_ext(&conn, want_ext_default_ll,
+
138 want_default_ll);
+
139 assert(rc == 0);
+
140 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
+
141 print_conn_info("Test high bits after", &conn);
+
142
+
143 printf("Want conversion high bits test passed\n");
+
144}
+
145
+
146int main(void)
+
147{
+
148 test_want_conversion_basic();
+
149 test_want_conversion_conflict();
+
150 test_want_conversion_high_bits();
+
151 return 0;
+
152}
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+ + +
uint64_t capable_ext
+
uint32_t capable
+
uint64_t want_ext
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2test__write__cache_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..7d7594e --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2test__write__cache_8c_source.html @@ -0,0 +1,402 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/test_write_cache.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
test_write_cache.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9
+
10#define FUSE_USE_VERSION 30
+
11
+
12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
+
13#include <fuse.h>
+
14
+
15#include <fuse_config.h>
+
16#include <fuse_lowlevel.h>
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <assert.h>
+
23#include <stddef.h>
+
24#include <unistd.h>
+
25#include <sys/stat.h>
+
26#include <pthread.h>
+
27#include <stdatomic.h>
+
28
+
29#ifndef __linux__
+
30#include <limits.h>
+
31#else
+
32#include <linux/limits.h>
+
33#endif
+
34
+
35#define FILE_INO 2
+
36#define FILE_NAME "write_me"
+
37
+
38/* Command line parsing */
+
39struct options {
+
40 int writeback;
+
41 int data_size;
+
42 int delay_ms;
+
43} options = {
+
44 .writeback = 0,
+
45 .data_size = 2048,
+
46 .delay_ms = 0,
+
47};
+
48
+
49#define WRITE_SYSCALLS 64
+
50
+
51#define OPTION(t, p) \
+
52 { t, offsetof(struct options, p), 1 }
+
53static const struct fuse_opt option_spec[] = {
+
54 OPTION("writeback_cache", writeback),
+
55 OPTION("--data-size=%d", data_size),
+
56 OPTION("--delay_ms=%d", delay_ms),
+ +
58};
+
59static int got_write;
+
60static atomic_int write_cnt;
+
61
+
62pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+
63pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
64static int write_start, write_done;
+
65
+
66static void tfs_init (void *userdata, struct fuse_conn_info *conn)
+
67{
+
68 (void) userdata;
+
69
+
70 if(options.writeback) {
+
71 assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE));
+
72 fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
+
73 }
+
74}
+
75
+
76static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+
77 stbuf->st_ino = ino;
+
78 if (ino == FUSE_ROOT_ID) {
+
79 stbuf->st_mode = S_IFDIR | 0755;
+
80 stbuf->st_nlink = 1;
+
81 }
+
82
+
83 else if (ino == FILE_INO) {
+
84 stbuf->st_mode = S_IFREG | 0222;
+
85 stbuf->st_nlink = 1;
+
86 stbuf->st_size = 0;
+
87 }
+
88
+
89 else
+
90 return -1;
+
91
+
92 return 0;
+
93}
+
94
+
95static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+
96 const char *name) {
+
97 struct fuse_entry_param e;
+
98 memset(&e, 0, sizeof(e));
+
99
+
100 if (parent != FUSE_ROOT_ID)
+
101 goto err_out;
+
102 else if (strcmp(name, FILE_NAME) == 0)
+
103 e.ino = FILE_INO;
+
104 else
+
105 goto err_out;
+
106
+
107 if (tfs_stat(e.ino, &e.attr) != 0)
+
108 goto err_out;
+
109 fuse_reply_entry(req, &e);
+
110 return;
+
111
+
112err_out:
+
113 fuse_reply_err(req, ENOENT);
+
114}
+
115
+
116static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+
117 struct fuse_file_info *fi) {
+
118 struct stat stbuf;
+
119
+
120 (void) fi;
+
121
+
122 memset(&stbuf, 0, sizeof(stbuf));
+
123 if (tfs_stat(ino, &stbuf) != 0)
+
124 fuse_reply_err(req, ENOENT);
+
125 else
+
126 fuse_reply_attr(req, &stbuf, 5);
+
127}
+
128
+
129static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+
130 struct fuse_file_info *fi) {
+
131 if (ino == FUSE_ROOT_ID)
+
132 fuse_reply_err(req, EISDIR);
+
133 else {
+
134 assert(ino == FILE_INO);
+
135 /* Test close(rofd) does not block waiting for pending writes */
+
136 fi->noflush = !options.writeback && options.delay_ms &&
+
137 (fi->flags & O_ACCMODE) == O_RDONLY;
+
138 fuse_reply_open(req, fi);
+
139 }
+
140}
+
141
+
142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
143 size_t size, off_t off, struct fuse_file_info *fi) {
+
144 (void) fi; (void) buf; (void) off;
+
145 size_t expected;
+
146
+
147 assert(ino == FILE_INO);
+
148 expected = options.data_size;
+
149 if(options.writeback)
+
150 expected *= 2;
+
151
+
152 write_cnt++;
+
153
+
154 if(size != expected && !options.writeback)
+
155 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
+
156 expected, size);
+
157 else
+
158 got_write = 1;
+
159
+
160 /* Simulate waiting for pending writes */
+
161 if (options.delay_ms) {
+
162 pthread_mutex_lock(&lock);
+
163 write_start = 1;
+
164 pthread_cond_signal(&cond);
+
165 pthread_mutex_unlock(&lock);
+
166
+
167 usleep(options.delay_ms * 1000);
+
168
+
169 pthread_mutex_lock(&lock);
+
170 write_done = 1;
+
171 pthread_cond_signal(&cond);
+
172 pthread_mutex_unlock(&lock);
+
173 }
+
174
+
175 fuse_reply_write(req, size);
+
176}
+
177
+
178static struct fuse_lowlevel_ops tfs_oper = {
+
179 .init = tfs_init,
+
180 .lookup = tfs_lookup,
+
181 .getattr = tfs_getattr,
+
182 .open = tfs_open,
+
183 .write = tfs_write,
+
184};
+
185
+
186static void* close_rofd(void *data) {
+
187 int rofd = (int)(long) data;
+
188
+
189 /* Wait for first write to start */
+
190 pthread_mutex_lock(&lock);
+
191 while (!write_start && !write_done)
+
192 pthread_cond_wait(&cond, &lock);
+
193 pthread_mutex_unlock(&lock);
+
194
+
195 close(rofd);
+
196 printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
197
+
198 /* First write should not have been completed */
+
199 if (write_done)
+
200 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
+
201
+
202 return NULL;
+
203}
+
204
+
205static void* run_fs(void *data) {
+
206 struct fuse_session *se = (struct fuse_session*) data;
+
207 assert(fuse_session_loop(se) == 0);
+
208 return NULL;
+
209}
+
210
+
211static void test_fs(char *mountpoint) {
+
212 char fname[PATH_MAX];
+
213 char *buf;
+
214 const size_t iosize = options.data_size;
+
215 const size_t dsize = options.data_size * WRITE_SYSCALLS;
+
216 int fd, rofd;
+
217 pthread_t rofd_thread;
+
218 off_t off = 0;
+
219
+
220 buf = malloc(dsize);
+
221 assert(buf != NULL);
+
222 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
+
223 assert(read(fd, buf, dsize) == dsize);
+
224 close(fd);
+
225
+
226 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+
227 mountpoint) > 0);
+
228 fd = open(fname, O_WRONLY);
+
229 if (fd == -1) {
+
230 perror(fname);
+
231 assert(0);
+
232 }
+
233
+
234 if (options.delay_ms) {
+
235 /* Verify that close(rofd) does not block waiting for pending writes */
+
236 rofd = open(fname, O_RDONLY);
+
237 assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0);
+
238 /* Give close_rofd time to start */
+
239 usleep(options.delay_ms * 1000);
+
240 }
+
241
+
242 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
+
243 assert(pwrite(fd, buf + off, iosize, off) == iosize);
+
244 off += iosize;
+
245 assert(off <= dsize);
+
246 }
+
247 free(buf);
+
248 close(fd);
+
249
+
250 if (options.delay_ms) {
+
251 printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done);
+
252 assert(pthread_join(rofd_thread, NULL) == 0);
+
253 }
+
254}
+
255
+
256int main(int argc, char *argv[]) {
+
257 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
258 struct fuse_session *se;
+
259 struct fuse_cmdline_opts fuse_opts;
+
260 pthread_t fs_thread;
+
261
+
262 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
+
263 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+
264#ifndef __FreeBSD__
+
265 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+
266#endif
+
267 se = fuse_session_new(&args, &tfs_oper,
+
268 sizeof(tfs_oper), NULL);
+
269 fuse_opt_free_args(&args);
+
270 assert (se != NULL);
+
271 assert(fuse_set_signal_handlers(se) == 0);
+
272 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
273
+
274 /* Start file-system thread */
+
275 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
276
+
277 /* Write test data */
+
278 test_fs(fuse_opts.mountpoint);
+
279 free(fuse_opts.mountpoint);
+
280
+
281 /* Stop file system */
+ + +
284 assert(pthread_join(fs_thread, NULL) == 0);
+
285
+
286 assert(got_write == 1);
+
287
+
288 /*
+
289 * when writeback cache is enabled, kernel side can merge requests, but
+
290 * memory pressure, system 'sync' might trigger data flushes before - flush
+
291 * might happen in between write syscalls - merging subpage writes into
+
292 * a single page and pages into large fuse requests might or might not work.
+
293 * Though we can expect that that at least some (but maybe all) write
+
294 * system calls can be merged.
+
295 */
+
296 if (options.writeback)
+
297 assert(write_cnt < WRITE_SYSCALLS);
+
298 else
+
299 assert(write_cnt == WRITE_SYSCALLS);
+
300
+ + +
303
+
304 printf("Test completed successfully.\n");
+
305 return 0;
+
306}
+
307
+
308
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_WRITEBACK_CACHE
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
uint64_t fuse_ino_t
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
#define FUSE_ROOT_ID
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
+
fuse_ino_t ino
+ +
uint32_t noflush
+ + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+ +
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2test_2wrong__command_8c_source.html b/doc/html/fuse-3_817_81_8dir_2test_2wrong__command_8c_source.html new file mode 100644 index 0000000..640158c --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/test/wrong_command.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
wrong_command.c
+
+
+
1#include <stdio.h>
+
2
+
3int main(void) {
+
4#ifdef MESON_IS_SUBPROJECT
+
5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
+
6 "If you wish to run them try:\n"
+
7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
+
8 return 77; /* report as a skipped test */
+
9#else
+
10 fprintf(stderr, "\x1B[31m\e[1m"
+
11 "This is not the command you are looking for.\n"
+
12 "You probably want to run 'python3 -m pytest test/' instead"
+
13 "\e[0m\n");
+
14 return 1;
+
15#endif
+
16}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2util_2fusermount_8c_source.html b/doc/html/fuse-3_817_81_8dir_2util_2fusermount_8c_source.html new file mode 100644 index 0000000..f8d2a10 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2util_2fusermount_8c_source.html @@ -0,0 +1,1796 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/util/fusermount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#ifdef HAVE_CLOSE_RANGE
+
40#include <linux/close_range.h>
+
41#endif
+
42
+
43#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
44
+
45#define FUSE_DEV "/dev/fuse"
+
46
+
47static const char *progname;
+
48
+
49static int user_allow_other = 0;
+
50static int mount_max = 1000;
+
51
+
52static int auto_unmount = 0;
+
53
+
54#ifdef GETMNTENT_NEEDS_UNESCAPING
+
55// Older versions of musl libc don't unescape entries in /etc/mtab
+
56
+
57// unescapes octal sequences like \040 in-place
+
58// That's ok, because unescaping can not extend the length of the string.
+
59static void unescape(char *buf) {
+
60 char *src = buf;
+
61 char *dest = buf;
+
62 while (1) {
+
63 char *next_src = strchrnul(src, '\\');
+
64 int offset = next_src - src;
+
65 memmove(dest, src, offset);
+
66 src = next_src;
+
67 dest += offset;
+
68
+
69 if(*src == '\0') {
+
70 *dest = *src;
+
71 return;
+
72 }
+
73 src++;
+
74
+
75 if('0' <= src[0] && src[0] < '2' &&
+
76 '0' <= src[1] && src[1] < '8' &&
+
77 '0' <= src[2] && src[2] < '8') {
+
78 *dest++ = (src[0] - '0') << 6
+
79 | (src[1] - '0') << 3
+
80 | (src[2] - '0') << 0;
+
81 src += 3;
+
82 } else if (src[0] == '\\') {
+
83 *dest++ = '\\';
+
84 src += 1;
+
85 } else {
+
86 *dest++ = '\\';
+
87 }
+
88 }
+
89}
+
90
+
91static struct mntent *GETMNTENT(FILE *stream)
+
92{
+
93 struct mntent *entp = getmntent(stream);
+
94 if(entp != NULL) {
+
95 unescape(entp->mnt_fsname);
+
96 unescape(entp->mnt_dir);
+
97 unescape(entp->mnt_type);
+
98 unescape(entp->mnt_opts);
+
99 }
+
100 return entp;
+
101}
+
102#else
+
103#define GETMNTENT getmntent
+
104#endif // GETMNTENT_NEEDS_UNESCAPING
+
105
+
106/*
+
107 * Take a ',' separated option string and extract "x-" options
+
108 */
+
109static int extract_x_options(const char *original, char **non_x_opts,
+
110 char **x_opts)
+
111{
+
112 size_t orig_len;
+
113 const char *opt, *opt_end;
+
114
+
115 orig_len = strlen(original) + 1;
+
116
+
117 *non_x_opts = calloc(1, orig_len);
+
118 *x_opts = calloc(1, orig_len);
+
119
+
120 size_t non_x_opts_len = orig_len;
+
121 size_t x_opts_len = orig_len;
+
122
+
123 if (*non_x_opts == NULL || *x_opts == NULL) {
+
124 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
125 __func__, orig_len);
+
126 return -ENOMEM;
+
127 }
+
128
+
129 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
130 char *opt_buf;
+
131
+
132 opt_end = strchr(opt, ',');
+
133 if (opt_end == NULL)
+
134 opt_end = original + orig_len;
+
135
+
136 size_t opt_len = opt_end - opt;
+
137 size_t opt_len_left = orig_len - (opt - original);
+
138 size_t buf_len;
+
139 bool is_x_opts;
+
140
+
141 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
142 buf_len = x_opts_len;
+
143 is_x_opts = true;
+
144 opt_buf = *x_opts;
+
145 } else {
+
146 buf_len = non_x_opts_len;
+
147 is_x_opts = false;
+
148 opt_buf = *non_x_opts;
+
149 }
+
150
+
151 if (buf_len < orig_len) {
+
152 strncat(opt_buf, ",", 2);
+
153 buf_len -= 1;
+
154 }
+
155
+
156 /* omits ',' */
+
157 if ((ssize_t)(buf_len - opt_len) < 0) {
+
158 /* This would be a bug */
+
159 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
160 __func__, original);
+
161 return -EIO;
+
162 }
+
163
+
164 strncat(opt_buf, opt, opt_end - opt);
+
165 buf_len -= opt_len;
+
166
+
167 if (is_x_opts)
+
168 x_opts_len = buf_len;
+
169 else
+
170 non_x_opts_len = buf_len;
+
171 }
+
172
+
173 return 0;
+
174}
+
175
+
176static const char *get_user_name(void)
+
177{
+
178 struct passwd *pw = getpwuid(getuid());
+
179 if (pw != NULL && pw->pw_name != NULL)
+
180 return pw->pw_name;
+
181 else {
+
182 fprintf(stderr, "%s: could not determine username\n", progname);
+
183 return NULL;
+
184 }
+
185}
+
186
+
187static uid_t oldfsuid;
+
188static gid_t oldfsgid;
+
189
+
190static void drop_privs(void)
+
191{
+
192 if (getuid() != 0) {
+
193 oldfsuid = setfsuid(getuid());
+
194 oldfsgid = setfsgid(getgid());
+
195 }
+
196}
+
197
+
198static void restore_privs(void)
+
199{
+
200 if (getuid() != 0) {
+
201 setfsuid(oldfsuid);
+
202 setfsgid(oldfsgid);
+
203 }
+
204}
+
205
+
206#ifndef IGNORE_MTAB
+
207/*
+
208 * Make sure that /etc/mtab is checked and updated atomically
+
209 */
+
210static int lock_umount(void)
+
211{
+
212 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
213 int mtablock;
+
214 int res;
+
215 struct stat mtab_stat;
+
216
+
217 /* /etc/mtab could be a symlink to /proc/mounts */
+
218 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
219 return -1;
+
220
+
221 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
222 if (mtablock == -1) {
+
223 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
224 progname, strerror(errno));
+
225 return -1;
+
226 }
+
227 res = lockf(mtablock, F_LOCK, 0);
+
228 if (res < 0) {
+
229 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
230 strerror(errno));
+
231 close(mtablock);
+
232 return -1;
+
233 }
+
234
+
235 return mtablock;
+
236}
+
237
+
238static void unlock_umount(int mtablock)
+
239{
+
240 if (mtablock >= 0) {
+
241 int res;
+
242
+
243 res = lockf(mtablock, F_ULOCK, 0);
+
244 if (res < 0) {
+
245 fprintf(stderr, "%s: error releasing lock: %s\n",
+
246 progname, strerror(errno));
+
247 }
+
248 close(mtablock);
+
249 }
+
250}
+
251
+
252static int add_mount(const char *source, const char *mnt, const char *type,
+
253 const char *opts)
+
254{
+
255 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
256}
+
257
+
258static int may_unmount(const char *mnt, int quiet)
+
259{
+
260 struct mntent *entp;
+
261 FILE *fp;
+
262 const char *user = NULL;
+
263 char uidstr[32];
+
264 unsigned uidlen = 0;
+
265 int found;
+
266 const char *mtab = _PATH_MOUNTED;
+
267
+
268 user = get_user_name();
+
269 if (user == NULL)
+
270 return -1;
+
271
+
272 fp = setmntent(mtab, "r");
+
273 if (fp == NULL) {
+
274 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
275 strerror(errno));
+
276 return -1;
+
277 }
+
278
+
279 uidlen = sprintf(uidstr, "%u", getuid());
+
280
+
281 found = 0;
+
282 while ((entp = GETMNTENT(fp)) != NULL) {
+
283 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
284 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
285 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
286 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
287 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
288 char *p = strstr(entp->mnt_opts, "user=");
+
289 if (p &&
+
290 (p == entp->mnt_opts || *(p-1) == ',') &&
+
291 strcmp(p + 5, user) == 0) {
+
292 found = 1;
+
293 break;
+
294 }
+
295 /* /etc/mtab is a link pointing to
+
296 /proc/mounts: */
+
297 else if ((p =
+
298 strstr(entp->mnt_opts, "user_id=")) &&
+
299 (p == entp->mnt_opts ||
+
300 *(p-1) == ',') &&
+
301 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
302 (*(p+8+uidlen) == ',' ||
+
303 *(p+8+uidlen) == '\0')) {
+
304 found = 1;
+
305 break;
+
306 }
+
307 }
+
308 }
+
309 endmntent(fp);
+
310
+
311 if (!found) {
+
312 if (!quiet)
+
313 fprintf(stderr,
+
314 "%s: entry for %s not found in %s\n",
+
315 progname, mnt, mtab);
+
316 return -1;
+
317 }
+
318
+
319 return 0;
+
320}
+
321#endif
+
322
+
323/*
+
324 * Check whether the file specified in "fusermount3 -u" is really a
+
325 * mountpoint and not a symlink. This is necessary otherwise the user
+
326 * could move the mountpoint away and replace it with a symlink
+
327 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
328 * unmounting that (umount(2) will follow symlinks).
+
329 *
+
330 * This is the child process running in a separate mount namespace, so
+
331 * we don't mess with the global namespace and if the process is
+
332 * killed for any reason, mounts are automatically cleaned up.
+
333 *
+
334 * First make sure nothing is propagated back into the parent
+
335 * namespace by marking all mounts "private".
+
336 *
+
337 * Then bind mount parent onto a stable base where the user can't move
+
338 * it around.
+
339 *
+
340 * Finally check /proc/mounts for an entry matching the requested
+
341 * mountpoint. If it's found then we are OK, and the user can't move
+
342 * it around within the parent directory as rename() will return
+
343 * EBUSY. Be careful to ignore any mounts that existed before the
+
344 * bind.
+
345 */
+
346static int check_is_mount_child(void *p)
+
347{
+
348 const char **a = p;
+
349 const char *last = a[0];
+
350 const char *mnt = a[1];
+
351 const char *type = a[2];
+
352 int res;
+
353 const char *procmounts = "/proc/mounts";
+
354 int found;
+
355 FILE *fp;
+
356 struct mntent *entp;
+
357 int count;
+
358
+
359 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
360 if (res == -1) {
+
361 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
362 progname, strerror(errno));
+
363 return 1;
+
364 }
+
365
+
366 fp = setmntent(procmounts, "r");
+
367 if (fp == NULL) {
+
368 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
369 procmounts, strerror(errno));
+
370 return 1;
+
371 }
+
372
+
373 count = 0;
+
374 while (GETMNTENT(fp) != NULL)
+
375 count++;
+
376 endmntent(fp);
+
377
+
378 fp = setmntent(procmounts, "r");
+
379 if (fp == NULL) {
+
380 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
381 procmounts, strerror(errno));
+
382 return 1;
+
383 }
+
384
+
385 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
386 if (res == -1) {
+
387 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
388 progname, strerror(errno));
+
389 return 1;
+
390 }
+
391
+
392 found = 0;
+
393 while ((entp = GETMNTENT(fp)) != NULL) {
+
394 if (count > 0) {
+
395 count--;
+
396 continue;
+
397 }
+
398 if (entp->mnt_dir[0] == '/' &&
+
399 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
400 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
401 found = 1;
+
402 break;
+
403 }
+
404 }
+
405 endmntent(fp);
+
406
+
407 if (!found) {
+
408 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
409 return 1;
+
410 }
+
411
+
412 return 0;
+
413}
+
414
+
415static pid_t clone_newns(void *a)
+
416{
+
417 char buf[131072];
+
418 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
419
+
420#ifdef __ia64__
+
421 extern int __clone2(int (*fn)(void *),
+
422 void *child_stack_base, size_t stack_size,
+
423 int flags, void *arg, pid_t *ptid,
+
424 void *tls, pid_t *ctid);
+
425
+
426 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
427 CLONE_NEWNS, a, NULL, NULL, NULL);
+
428#else
+
429 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
430#endif
+
431}
+
432
+
433static int check_is_mount(const char *last, const char *mnt, const char *type)
+
434{
+
435 pid_t pid, p;
+
436 int status;
+
437 const char *a[3] = { last, mnt, type };
+
438
+
439 pid = clone_newns((void *) a);
+
440 if (pid == (pid_t) -1) {
+
441 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
442 progname, strerror(errno));
+
443 return -1;
+
444 }
+
445 p = waitpid(pid, &status, __WCLONE);
+
446 if (p == (pid_t) -1) {
+
447 fprintf(stderr, "%s: waitpid failed: %s\n",
+
448 progname, strerror(errno));
+
449 return -1;
+
450 }
+
451 if (!WIFEXITED(status)) {
+
452 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
453 progname, status);
+
454 return -1;
+
455 }
+
456 if (WEXITSTATUS(status) != 0)
+
457 return -1;
+
458
+
459 return 0;
+
460}
+
461
+
462static int chdir_to_parent(char *copy, const char **lastp)
+
463{
+
464 char *tmp;
+
465 const char *parent;
+
466 char buf[65536];
+
467 int res;
+
468
+
469 tmp = strrchr(copy, '/');
+
470 if (tmp == NULL || tmp[1] == '\0') {
+
471 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
472 progname, copy);
+
473 return -1;
+
474 }
+
475 if (tmp != copy) {
+
476 *tmp = '\0';
+
477 parent = copy;
+
478 *lastp = tmp + 1;
+
479 } else if (tmp[1] != '\0') {
+
480 *lastp = tmp + 1;
+
481 parent = "/";
+
482 } else {
+
483 *lastp = ".";
+
484 parent = "/";
+
485 }
+
486
+
487 res = chdir(parent);
+
488 if (res == -1) {
+
489 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
490 progname, parent, strerror(errno));
+
491 return -1;
+
492 }
+
493
+
494 if (getcwd(buf, sizeof(buf)) == NULL) {
+
495 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
496 progname, strerror(errno));
+
497 return -1;
+
498 }
+
499 if (strcmp(buf, parent) != 0) {
+
500 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
501 parent, buf);
+
502 return -1;
+
503
+
504 }
+
505
+
506 return 0;
+
507}
+
508
+
509#ifndef IGNORE_MTAB
+
510static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
511{
+
512 int res;
+
513 char *copy;
+
514 const char *last;
+
515 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
516
+
517 if (getuid() != 0) {
+
518 res = may_unmount(mnt, quiet);
+
519 if (res == -1)
+
520 return -1;
+
521 }
+
522
+
523 copy = strdup(mnt);
+
524 if (copy == NULL) {
+
525 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
526 return -1;
+
527 }
+
528
+
529 drop_privs();
+
530 res = chdir_to_parent(copy, &last);
+
531 if (res == -1) {
+
532 restore_privs();
+
533 goto out;
+
534 }
+
535
+
536 res = umount2(last, umount_flags);
+
537 restore_privs();
+
538 if (res == -1 && !quiet) {
+
539 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
540 progname, mnt, strerror(errno));
+
541 }
+
542
+
543out:
+
544 free(copy);
+
545 if (res == -1)
+
546 return -1;
+
547
+
548 res = chdir("/");
+
549 if (res == -1) {
+
550 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
551 return -1;
+
552 }
+
553
+
554 return fuse_mnt_remove_mount(progname, mnt);
+
555}
+
556
+
557static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
558{
+
559 int res;
+
560 int mtablock = lock_umount();
+
561
+
562 res = unmount_fuse_locked(mnt, quiet, lazy);
+
563 unlock_umount(mtablock);
+
564
+
565 return res;
+
566}
+
567
+
568static int count_fuse_fs(void)
+
569{
+
570 struct mntent *entp;
+
571 int count = 0;
+
572 const char *mtab = _PATH_MOUNTED;
+
573 FILE *fp = setmntent(mtab, "r");
+
574 if (fp == NULL) {
+
575 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
576 strerror(errno));
+
577 return -1;
+
578 }
+
579 while ((entp = GETMNTENT(fp)) != NULL) {
+
580 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
581 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
582 count ++;
+
583 }
+
584 endmntent(fp);
+
585 return count;
+
586}
+
587
+
588
+
589#else /* IGNORE_MTAB */
+
590static int count_fuse_fs(void)
+
591{
+
592 return 0;
+
593}
+
594
+
595static int add_mount(const char *source, const char *mnt, const char *type,
+
596 const char *opts)
+
597{
+
598 (void) source;
+
599 (void) mnt;
+
600 (void) type;
+
601 (void) opts;
+
602 return 0;
+
603}
+
604
+
605static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
606{
+
607 (void) quiet;
+
608 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
609}
+
610#endif /* IGNORE_MTAB */
+
611
+
612static void strip_line(char *line)
+
613{
+
614 char *s = strchr(line, '#');
+
615 if (s != NULL)
+
616 s[0] = '\0';
+
617 for (s = line + strlen(line) - 1;
+
618 s >= line && isspace((unsigned char) *s); s--);
+
619 s[1] = '\0';
+
620 for (s = line; isspace((unsigned char) *s); s++);
+
621 if (s != line)
+
622 memmove(line, s, strlen(s)+1);
+
623}
+
624
+
625static void parse_line(char *line, int linenum)
+
626{
+
627 int tmp;
+
628 if (strcmp(line, "user_allow_other") == 0)
+
629 user_allow_other = 1;
+
630 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+
631 mount_max = tmp;
+
632 else if(line[0])
+
633 fprintf(stderr,
+
634 "%s: unknown parameter in %s at line %i: '%s'\n",
+
635 progname, FUSE_CONF, linenum, line);
+
636}
+
637
+
638static void read_conf(void)
+
639{
+
640 FILE *fp = fopen(FUSE_CONF, "r");
+
641 if (fp != NULL) {
+
642 int linenum = 1;
+
643 char line[256];
+
644 int isnewline = 1;
+
645 while (fgets(line, sizeof(line), fp) != NULL) {
+
646 if (isnewline) {
+
647 if (line[strlen(line)-1] == '\n') {
+
648 strip_line(line);
+
649 parse_line(line, linenum);
+
650 } else {
+
651 isnewline = 0;
+
652 }
+
653 } else if(line[strlen(line)-1] == '\n') {
+
654 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
655
+
656 isnewline = 1;
+
657 }
+
658 if (isnewline)
+
659 linenum ++;
+
660 }
+
661 if (!isnewline) {
+
662 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
663
+
664 }
+
665 if (ferror(fp)) {
+
666 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
667 exit(1);
+
668 }
+
669 fclose(fp);
+
670 } else if (errno != ENOENT) {
+
671 bool fatal = (errno != EACCES && errno != ELOOP &&
+
672 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
673 errno != EOVERFLOW);
+
674 fprintf(stderr, "%s: failed to open %s: %s\n",
+
675 progname, FUSE_CONF, strerror(errno));
+
676 if (fatal)
+
677 exit(1);
+
678 }
+
679}
+
680
+
681static int begins_with(const char *s, const char *beg)
+
682{
+
683 if (strncmp(s, beg, strlen(beg)) == 0)
+
684 return 1;
+
685 else
+
686 return 0;
+
687}
+
688
+
689struct mount_flags {
+
690 const char *opt;
+
691 unsigned long flag;
+
692 int on;
+
693 int safe;
+
694};
+
695
+
696static struct mount_flags mount_flags[] = {
+
697 {"rw", MS_RDONLY, 0, 1},
+
698 {"ro", MS_RDONLY, 1, 1},
+
699 {"suid", MS_NOSUID, 0, 0},
+
700 {"nosuid", MS_NOSUID, 1, 1},
+
701 {"dev", MS_NODEV, 0, 0},
+
702 {"nodev", MS_NODEV, 1, 1},
+
703 {"exec", MS_NOEXEC, 0, 1},
+
704 {"noexec", MS_NOEXEC, 1, 1},
+
705 {"async", MS_SYNCHRONOUS, 0, 1},
+
706 {"sync", MS_SYNCHRONOUS, 1, 1},
+
707 {"atime", MS_NOATIME, 0, 1},
+
708 {"noatime", MS_NOATIME, 1, 1},
+
709 {"diratime", MS_NODIRATIME, 0, 1},
+
710 {"nodiratime", MS_NODIRATIME, 1, 1},
+
711 {"lazytime", MS_LAZYTIME, 1, 1},
+
712 {"nolazytime", MS_LAZYTIME, 0, 1},
+
713 {"relatime", MS_RELATIME, 1, 1},
+
714 {"norelatime", MS_RELATIME, 0, 1},
+
715 {"strictatime", MS_STRICTATIME, 1, 1},
+
716 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
717 {"dirsync", MS_DIRSYNC, 1, 1},
+
718 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
719 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
720 {NULL, 0, 0, 0}
+
721};
+
722
+
723static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
724{
+
725 int i;
+
726
+
727 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
728 const char *opt = mount_flags[i].opt;
+
729 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
730 *on = mount_flags[i].on;
+
731 *flag = mount_flags[i].flag;
+
732 if (!mount_flags[i].safe && getuid() != 0) {
+
733 *flag = 0;
+
734 fprintf(stderr,
+
735 "%s: unsafe option %s ignored\n",
+
736 progname, opt);
+
737 }
+
738 return 1;
+
739 }
+
740 }
+
741 return 0;
+
742}
+
743
+
744static int add_option(char **optsp, const char *opt, unsigned expand)
+
745{
+
746 char *newopts;
+
747 if (*optsp == NULL)
+
748 newopts = strdup(opt);
+
749 else {
+
750 unsigned oldsize = strlen(*optsp);
+
751 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
752 newopts = (char *) realloc(*optsp, newsize);
+
753 if (newopts)
+
754 sprintf(newopts + oldsize, ",%s", opt);
+
755 }
+
756 if (newopts == NULL) {
+
757 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
758 return -1;
+
759 }
+
760 *optsp = newopts;
+
761 return 0;
+
762}
+
763
+
764static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
765{
+
766 int i;
+
767 int l;
+
768
+
769 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
770 return -1;
+
771
+
772 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
773 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
774 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
775 return -1;
+
776 }
+
777
+
778 if (add_option(mnt_optsp, opts, 0) == -1)
+
779 return -1;
+
780 /* remove comma from end of opts*/
+
781 l = strlen(*mnt_optsp);
+
782 if ((*mnt_optsp)[l-1] == ',')
+
783 (*mnt_optsp)[l-1] = '\0';
+
784 if (getuid() != 0) {
+
785 const char *user = get_user_name();
+
786 if (user == NULL)
+
787 return -1;
+
788
+
789 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
790 return -1;
+
791 strcat(*mnt_optsp, user);
+
792 }
+
793 return 0;
+
794}
+
795
+
796static int opt_eq(const char *s, unsigned len, const char *opt)
+
797{
+
798 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
799 return 1;
+
800 else
+
801 return 0;
+
802}
+
803
+
804static int get_string_opt(const char *s, unsigned len, const char *opt,
+
805 char **val)
+
806{
+
807 int i;
+
808 unsigned opt_len = strlen(opt);
+
809 char *d;
+
810
+
811 if (*val)
+
812 free(*val);
+
813 *val = (char *) malloc(len - opt_len + 1);
+
814 if (!*val) {
+
815 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
816 return 0;
+
817 }
+
818
+
819 d = *val;
+
820 s += opt_len;
+
821 len -= opt_len;
+
822 for (i = 0; i < len; i++) {
+
823 if (s[i] == '\\' && i + 1 < len)
+
824 i++;
+
825 *d++ = s[i];
+
826 }
+
827 *d = '\0';
+
828 return 1;
+
829}
+
830
+
831/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
832 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
833 * "group_id=1".
+
834 * This wrapper detects this case and bails out with an error.
+
835 */
+
836static int mount_notrunc(const char *source, const char *target,
+
837 const char *filesystemtype, unsigned long mountflags,
+
838 const char *data) {
+
839 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
840 fprintf(stderr, "%s: mount options too long\n", progname);
+
841 errno = EINVAL;
+
842 return -1;
+
843 }
+
844 return mount(source, target, filesystemtype, mountflags, data);
+
845}
+
846
+
847
+
848static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
849 int fd, const char *opts, const char *dev, char **sourcep,
+
850 char **mnt_optsp)
+
851{
+
852 int res;
+
853 int flags = MS_NOSUID | MS_NODEV;
+
854 char *optbuf;
+
855 char *mnt_opts = NULL;
+
856 const char *s;
+
857 char *d;
+
858 char *fsname = NULL;
+
859 char *subtype = NULL;
+
860 char *source = NULL;
+
861 char *type = NULL;
+
862 int blkdev = 0;
+
863
+
864 optbuf = (char *) malloc(strlen(opts) + 128);
+
865 if (!optbuf) {
+
866 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
867 return -1;
+
868 }
+
869
+
870 for (s = opts, d = optbuf; *s;) {
+
871 unsigned len;
+
872 const char *fsname_str = "fsname=";
+
873 const char *subtype_str = "subtype=";
+
874 bool escape_ok = begins_with(s, fsname_str) ||
+
875 begins_with(s, subtype_str);
+
876 for (len = 0; s[len]; len++) {
+
877 if (escape_ok && s[len] == '\\' && s[len + 1])
+
878 len++;
+
879 else if (s[len] == ',')
+
880 break;
+
881 }
+
882 if (begins_with(s, fsname_str)) {
+
883 if (!get_string_opt(s, len, fsname_str, &fsname))
+
884 goto err;
+
885 } else if (begins_with(s, subtype_str)) {
+
886 if (!get_string_opt(s, len, subtype_str, &subtype))
+
887 goto err;
+
888 } else if (opt_eq(s, len, "blkdev")) {
+
889 if (getuid() != 0) {
+
890 fprintf(stderr,
+
891 "%s: option blkdev is privileged\n",
+
892 progname);
+
893 goto err;
+
894 }
+
895 blkdev = 1;
+
896 } else if (opt_eq(s, len, "auto_unmount")) {
+
897 auto_unmount = 1;
+
898 } else if (!opt_eq(s, len, "nonempty") &&
+
899 !begins_with(s, "fd=") &&
+
900 !begins_with(s, "rootmode=") &&
+
901 !begins_with(s, "user_id=") &&
+
902 !begins_with(s, "group_id=")) {
+
903 int on;
+
904 int flag;
+
905 int skip_option = 0;
+
906 if (opt_eq(s, len, "large_read")) {
+
907 struct utsname utsname;
+
908 unsigned kmaj, kmin;
+
909 res = uname(&utsname);
+
910 if (res == 0 &&
+
911 sscanf(utsname.release, "%u.%u",
+
912 &kmaj, &kmin) == 2 &&
+
913 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
914 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
915 skip_option = 1;
+
916 }
+
917 }
+
918 if (getuid() != 0 && !user_allow_other &&
+
919 (opt_eq(s, len, "allow_other") ||
+
920 opt_eq(s, len, "allow_root"))) {
+
921 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
922 goto err;
+
923 }
+
924 if (!skip_option) {
+
925 if (find_mount_flag(s, len, &on, &flag)) {
+
926 if (on)
+
927 flags |= flag;
+
928 else
+
929 flags &= ~flag;
+
930 } else if (opt_eq(s, len, "default_permissions") ||
+
931 opt_eq(s, len, "allow_other") ||
+
932 begins_with(s, "max_read=") ||
+
933 begins_with(s, "blksize=")) {
+
934 memcpy(d, s, len);
+
935 d += len;
+
936 *d++ = ',';
+
937 } else {
+
938 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
939 exit(1);
+
940 }
+
941 }
+
942 }
+
943 s += len;
+
944 if (*s)
+
945 s++;
+
946 }
+
947 *d = '\0';
+
948 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
949 if (res == -1)
+
950 goto err;
+
951
+
952 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
953 fd, rootmode, getuid(), getgid());
+
954
+
955 source = malloc((fsname ? strlen(fsname) : 0) +
+
956 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
957
+
958 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
959 if (!type || !source) {
+
960 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
961 goto err;
+
962 }
+
963
+
964 if (subtype)
+
965 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
966 else
+
967 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
968
+
969 if (fsname)
+
970 strcpy(source, fsname);
+
971 else
+
972 strcpy(source, subtype ? subtype : dev);
+
973
+
974 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
975 if (res == -1 && errno == ENODEV && subtype) {
+
976 /* Probably missing subtype support */
+
977 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
978 if (fsname) {
+
979 if (!blkdev)
+
980 sprintf(source, "%s#%s", subtype, fsname);
+
981 } else {
+
982 strcpy(source, type);
+
983 }
+
984
+
985 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
986 }
+
987 if (res == -1 && errno == EINVAL) {
+
988 /* It could be an old version not supporting group_id */
+
989 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
990 fd, rootmode, getuid());
+
991 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
992 }
+
993 if (res == -1) {
+
994 int errno_save = errno;
+
995 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
996 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
997 progname);
+
998 else
+
999 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
1000 strerror(errno_save));
+
1001 goto err;
+
1002 }
+
1003 *sourcep = source;
+
1004 *typep = type;
+
1005 *mnt_optsp = mnt_opts;
+
1006 free(fsname);
+
1007 free(optbuf);
+
1008
+
1009 return 0;
+
1010
+
1011err:
+
1012 free(fsname);
+
1013 free(subtype);
+
1014 free(source);
+
1015 free(type);
+
1016 free(mnt_opts);
+
1017 free(optbuf);
+
1018 return -1;
+
1019}
+
1020
+
1021static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1022{
+
1023 int res;
+
1024 const char *mnt = *mntp;
+
1025 const char *origmnt = mnt;
+
1026 struct statfs fs_buf;
+
1027 size_t i;
+
1028
+
1029 res = lstat(mnt, stbuf);
+
1030 if (res == -1) {
+
1031 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1032 progname, mnt, strerror(errno));
+
1033 return -1;
+
1034 }
+
1035
+
1036 /* No permission checking is done for root */
+
1037 if (getuid() == 0)
+
1038 return 0;
+
1039
+
1040 if (S_ISDIR(stbuf->st_mode)) {
+
1041 res = chdir(mnt);
+
1042 if (res == -1) {
+
1043 fprintf(stderr,
+
1044 "%s: failed to chdir to mountpoint: %s\n",
+
1045 progname, strerror(errno));
+
1046 return -1;
+
1047 }
+
1048 mnt = *mntp = ".";
+
1049 res = lstat(mnt, stbuf);
+
1050 if (res == -1) {
+
1051 fprintf(stderr,
+
1052 "%s: failed to access mountpoint %s: %s\n",
+
1053 progname, origmnt, strerror(errno));
+
1054 return -1;
+
1055 }
+
1056
+
1057 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1058 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1059 progname, origmnt);
+
1060 return -1;
+
1061 }
+
1062
+
1063 res = access(mnt, W_OK);
+
1064 if (res == -1) {
+
1065 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1066 progname, origmnt);
+
1067 return -1;
+
1068 }
+
1069 } else if (S_ISREG(stbuf->st_mode)) {
+
1070 static char procfile[256];
+
1071 *mountpoint_fd = open(mnt, O_WRONLY);
+
1072 if (*mountpoint_fd == -1) {
+
1073 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1074 progname, mnt, strerror(errno));
+
1075 return -1;
+
1076 }
+
1077 res = fstat(*mountpoint_fd, stbuf);
+
1078 if (res == -1) {
+
1079 fprintf(stderr,
+
1080 "%s: failed to access mountpoint %s: %s\n",
+
1081 progname, mnt, strerror(errno));
+
1082 return -1;
+
1083 }
+
1084 if (!S_ISREG(stbuf->st_mode)) {
+
1085 fprintf(stderr,
+
1086 "%s: mountpoint %s is no longer a regular file\n",
+
1087 progname, mnt);
+
1088 return -1;
+
1089 }
+
1090
+
1091 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+
1092 *mntp = procfile;
+
1093 } else {
+
1094 fprintf(stderr,
+
1095 "%s: mountpoint %s is not a directory or a regular file\n",
+
1096 progname, mnt);
+
1097 return -1;
+
1098 }
+
1099
+
1100 /* Do not permit mounting over anything in procfs - it has a couple
+
1101 * places to which we have "write access" without being supposed to be
+
1102 * able to just put anything we want there.
+
1103 * Luckily, without allow_other, we can't get other users to actually
+
1104 * use any fake information we try to put there anyway.
+
1105 * Use a whitelist to be safe. */
+
1106 if (statfs(*mntp, &fs_buf)) {
+
1107 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1108 progname, mnt, strerror(errno));
+
1109 return -1;
+
1110 }
+
1111
+
1112 /* Define permitted filesystems for the mount target. This was
+
1113 * originally the same list as used by the ecryptfs mount helper
+
1114 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1115 * but got expanded as we found more filesystems that needed to be
+
1116 * overlaid. */
+
1117 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1118 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1119 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1120 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1121 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1122 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1123 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1124 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1125 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1126 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1127 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1128 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1129 0x01161970 /* GFS2_MAGIC */,
+
1130 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1131 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1132 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1133 0x3153464A /* JFS_SUPER_MAGIC */,
+
1134 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1135 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1136 0x0000564C /* NCP_SUPER_MAGIC */,
+
1137 0x00006969 /* NFS_SUPER_MAGIC */,
+
1138 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1139 0x5346544E /* NTFS_SB_MAGIC */,
+
1140 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1141 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1142 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1143 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
+
1144 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1145 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1146 0x73717368 /* SQUASHFS_MAGIC */,
+
1147 0x01021994 /* TMPFS_MAGIC */,
+
1148 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1149#if __SIZEOF_LONG__ > 4
+
1150 0x736675005346544e /* UFSD */,
+
1151#endif
+
1152 0x58465342 /* XFS_SB_MAGIC */,
+
1153 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1154 0x858458f6 /* RAMFS_MAGIC */,
+
1155 };
+
1156 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1157 if (f_type_whitelist[i] == fs_buf.f_type)
+
1158 return 0;
+
1159 }
+
1160
+
1161 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1162 progname, (unsigned long)fs_buf.f_type);
+
1163 return -1;
+
1164}
+
1165
+
1166static int try_open(const char *dev, char **devp, int silent)
+
1167{
+
1168 int fd = open(dev, O_RDWR);
+
1169 if (fd != -1) {
+
1170 *devp = strdup(dev);
+
1171 if (*devp == NULL) {
+
1172 fprintf(stderr, "%s: failed to allocate memory\n",
+
1173 progname);
+
1174 close(fd);
+
1175 fd = -1;
+
1176 }
+
1177 } else if (errno == ENODEV ||
+
1178 errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1179 return -2;
+
1180 else if (!silent) {
+
1181 fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
+
1182 strerror(errno));
+
1183 }
+
1184 return fd;
+
1185}
+
1186
+
1187static int try_open_fuse_device(char **devp)
+
1188{
+
1189 int fd;
+
1190
+
1191 drop_privs();
+
1192 fd = try_open(FUSE_DEV, devp, 0);
+
1193 restore_privs();
+
1194 return fd;
+
1195}
+
1196
+
1197static int open_fuse_device(char **devp)
+
1198{
+
1199 int fd = try_open_fuse_device(devp);
+
1200 if (fd >= -1)
+
1201 return fd;
+
1202
+
1203 fprintf(stderr,
+
1204 "%s: fuse device not found, try 'modprobe fuse' first\n",
+
1205 progname);
+
1206
+
1207 return -1;
+
1208}
+
1209
+
1210
+
1211static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1212{
+
1213 int res;
+
1214 int fd;
+
1215 char *dev;
+
1216 struct stat stbuf;
+
1217 char *source = NULL;
+
1218 char *mnt_opts = NULL;
+
1219 const char *real_mnt = mnt;
+
1220 int mountpoint_fd = -1;
+
1221 char *do_mount_opts = NULL;
+
1222 char *x_opts = NULL;
+
1223
+
1224 fd = open_fuse_device(&dev);
+
1225 if (fd == -1)
+
1226 return -1;
+
1227
+
1228 drop_privs();
+
1229 read_conf();
+
1230
+
1231 if (getuid() != 0 && mount_max != -1) {
+
1232 int mount_count = count_fuse_fs();
+
1233 if (mount_count >= mount_max) {
+
1234 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1235 goto fail_close_fd;
+
1236 }
+
1237 }
+
1238
+
1239 // Extract any options starting with "x-"
+
1240 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1241 if (res)
+
1242 goto fail_close_fd;
+
1243
+
1244 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1245 restore_privs();
+
1246 if (res != -1)
+
1247 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1248 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1249
+
1250 if (mountpoint_fd != -1)
+
1251 close(mountpoint_fd);
+
1252
+
1253 if (res == -1)
+
1254 goto fail_close_fd;
+
1255
+
1256 res = chdir("/");
+
1257 if (res == -1) {
+
1258 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1259 goto fail_close_fd;
+
1260 }
+
1261
+
1262 if (geteuid() == 0) {
+
1263 if (x_opts && strlen(x_opts) > 0) {
+
1264 /*
+
1265 * Add back the options starting with "x-" to opts from
+
1266 * do_mount. +2 for ',' and '\0'
+
1267 */
+
1268 size_t mnt_opts_len = strlen(mnt_opts);
+
1269 size_t x_mnt_opts_len = mnt_opts_len+
+
1270 strlen(x_opts) + 2;
+
1271 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1272
+
1273 if (mnt_opts_len) {
+
1274 strcpy(x_mnt_opts, mnt_opts);
+
1275 strncat(x_mnt_opts, ",", 2);
+
1276 }
+
1277
+
1278 strncat(x_mnt_opts, x_opts,
+
1279 x_mnt_opts_len - mnt_opts_len - 2);
+
1280
+
1281 free(mnt_opts);
+
1282 mnt_opts = x_mnt_opts;
+
1283 }
+
1284
+
1285 res = add_mount(source, mnt, *type, mnt_opts);
+
1286 if (res == -1) {
+
1287 /* Can't clean up mount in a non-racy way */
+
1288 goto fail_close_fd;
+
1289 }
+
1290 }
+
1291
+
1292out_free:
+
1293 free(source);
+
1294 free(mnt_opts);
+
1295 free(dev);
+
1296 free(x_opts);
+
1297 free(do_mount_opts);
+
1298
+
1299 return fd;
+
1300
+
1301fail_close_fd:
+
1302 close(fd);
+
1303 fd = -1;
+
1304 goto out_free;
+
1305}
+
1306
+
1307static int send_fd(int sock_fd, int fd)
+
1308{
+
1309 int retval;
+
1310 struct msghdr msg;
+
1311 struct cmsghdr *p_cmsg;
+
1312 struct iovec vec;
+
1313 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1314 int *p_fds;
+
1315 char sendchar = 0;
+
1316
+
1317 msg.msg_control = cmsgbuf;
+
1318 msg.msg_controllen = sizeof(cmsgbuf);
+
1319 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1320 p_cmsg->cmsg_level = SOL_SOCKET;
+
1321 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1322 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1323 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1324 *p_fds = fd;
+
1325 msg.msg_controllen = p_cmsg->cmsg_len;
+
1326 msg.msg_name = NULL;
+
1327 msg.msg_namelen = 0;
+
1328 msg.msg_iov = &vec;
+
1329 msg.msg_iovlen = 1;
+
1330 msg.msg_flags = 0;
+
1331 /* "To pass file descriptors or credentials you need to send/read at
+
1332 * least one byte" (man 7 unix) */
+
1333 vec.iov_base = &sendchar;
+
1334 vec.iov_len = sizeof(sendchar);
+
1335 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1336 if (retval != 1) {
+
1337 perror("sending file descriptor");
+
1338 return -1;
+
1339 }
+
1340 return 0;
+
1341}
+
1342
+
1343/* Helper for should_auto_unmount
+
1344 *
+
1345 * fusermount typically has the s-bit set - initial open of `mnt` was as root
+
1346 * and got EACCESS as 'allow_other' was not specified.
+
1347 * Try opening `mnt` again with uid and guid of the calling process.
+
1348 */
+
1349static int recheck_ENOTCONN_as_owner(const char *mnt)
+
1350{
+
1351 int pid = fork();
+
1352 if(pid == -1) {
+
1353 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1354 _exit(EXIT_FAILURE);
+
1355 } else if(pid == 0) {
+
1356 uid_t uid = getuid();
+
1357 gid_t gid = getgid();
+
1358 if(setresgid(gid, gid, gid) == -1) {
+
1359 perror("fuse: can't set resgid");
+
1360 _exit(EXIT_FAILURE);
+
1361 }
+
1362 if(setresuid(uid, uid, uid) == -1) {
+
1363 perror("fuse: can't set resuid");
+
1364 _exit(EXIT_FAILURE);
+
1365 }
+
1366
+
1367 int fd = open(mnt, O_RDONLY);
+
1368 if(fd == -1 && errno == ENOTCONN)
+
1369 _exit(EXIT_SUCCESS);
+
1370 else
+
1371 _exit(EXIT_FAILURE);
+
1372 } else {
+
1373 int status;
+
1374 int res = waitpid(pid, &status, 0);
+
1375 if (res == -1) {
+
1376 perror("fuse: waiting for child failed");
+
1377 _exit(EXIT_FAILURE);
+
1378 }
+
1379 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1380 }
+
1381}
+
1382
+
1383/* The parent fuse process has died: decide whether to auto_unmount.
+
1384 *
+
1385 * In the normal case (umount or fusermount -u), the filesystem
+
1386 * has already been unmounted. If we simply unmount again we can
+
1387 * cause problems with stacked mounts (e.g. autofs).
+
1388 *
+
1389 * So we unmount here only in abnormal case where fuse process has
+
1390 * died without unmount happening. To detect this, we first look in
+
1391 * the mount table to make sure the mountpoint is still mounted and
+
1392 * has proper type. If so, we then see if opening the mount dir is
+
1393 * returning 'Transport endpoint is not connected'.
+
1394 *
+
1395 * The order of these is important, because if autofs is in use,
+
1396 * opening the dir to check for ENOTCONN will cause a new mount
+
1397 * in the normal case where filesystem has been unmounted cleanly.
+
1398 */
+
1399static int should_auto_unmount(const char *mnt, const char *type)
+
1400{
+
1401 char *copy;
+
1402 const char *last;
+
1403 int result = 0;
+
1404 int fd;
+
1405
+
1406 copy = strdup(mnt);
+
1407 if (copy == NULL) {
+
1408 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1409 return 0;
+
1410 }
+
1411
+
1412 if (chdir_to_parent(copy, &last) == -1)
+
1413 goto out;
+
1414 if (check_is_mount(last, mnt, type) == -1)
+
1415 goto out;
+
1416
+
1417 fd = open(mnt, O_RDONLY);
+
1418
+
1419 if (fd != -1) {
+
1420 close(fd);
+
1421 } else {
+
1422 switch(errno) {
+
1423 case ENOTCONN:
+
1424 result = 1;
+
1425 break;
+
1426 case EACCES:
+
1427 result = recheck_ENOTCONN_as_owner(mnt);
+
1428 break;
+
1429 default:
+
1430 result = 0;
+
1431 break;
+
1432 }
+
1433 }
+
1434out:
+
1435 free(copy);
+
1436 return result;
+
1437}
+
1438
+
1439static void usage(void)
+
1440{
+
1441 printf("%s: [options] mountpoint\n"
+
1442 "Options:\n"
+
1443 " -h print help\n"
+
1444 " -V print version\n"
+
1445 " -o opt[,opt...] mount options\n"
+
1446 " -u unmount\n"
+
1447 " -q quiet\n"
+
1448 " -z lazy unmount\n",
+
1449 progname);
+
1450 exit(1);
+
1451}
+
1452
+
1453static void show_version(void)
+
1454{
+
1455 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1456 exit(0);
+
1457}
+
1458
+
1459static void close_range_loop(int min_fd, int max_fd, int cfd)
+
1460{
+
1461 for (int fd = min_fd; fd <= max_fd; fd++)
+
1462 if (fd != cfd)
+
1463 close(fd);
+
1464}
+
1465
+
1466/*
+
1467 * Close all inherited fds that are not needed
+
1468 * Ideally these wouldn't come up at all, applications should better
+
1469 * use FD_CLOEXEC / O_CLOEXEC
+
1470 */
+
1471static int close_inherited_fds(int cfd)
+
1472{
+
1473 int rc = -1;
+
1474 int nullfd;
+
1475
+
1476 /* We can't even report an error */
+
1477 if (cfd <= STDERR_FILENO)
+
1478 return -EINVAL;
+
1479
+
1480#ifdef HAVE_CLOSE_RANGE
+
1481 if (cfd < STDERR_FILENO + 2) {
+
1482 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
+
1483 } else {
+
1484 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
+
1485 if (rc < 0)
+
1486 goto fallback;
+
1487 }
+
1488
+
1489 /* Close high range */
+
1490 rc = close_range(cfd + 1, ~0U, 0);
+
1491#else
+
1492 goto fallback; /* make use of fallback to avoid compiler warnings */
+
1493#endif
+
1494
+
1495fallback:
+
1496 if (rc < 0) {
+
1497 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
+
1498
+
1499 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
+
1500 }
+
1501
+
1502 nullfd = open("/dev/null", O_RDWR);
+
1503 if (nullfd < 0) {
+
1504 perror("fusermount: cannot open /dev/null");
+
1505 return -errno;
+
1506 }
+
1507
+
1508 /* Redirect stdin, stdout, stderr to /dev/null */
+
1509 dup2(nullfd, STDIN_FILENO);
+
1510 dup2(nullfd, STDOUT_FILENO);
+
1511 dup2(nullfd, STDERR_FILENO);
+
1512 if (nullfd > STDERR_FILENO)
+
1513 close(nullfd);
+
1514
+
1515 return 0;
+
1516}
+
1517
+
1518int main(int argc, char *argv[])
+
1519{
+
1520 sigset_t sigset;
+
1521 int ch;
+
1522 int fd;
+
1523 int res;
+
1524 char *origmnt;
+
1525 char *mnt;
+
1526 static int unmount = 0;
+
1527 static int lazy = 0;
+
1528 static int quiet = 0;
+
1529 char *commfd = NULL;
+
1530 long cfd;
+
1531 const char *opts = "";
+
1532 const char *type = NULL;
+
1533 int setup_auto_unmount_only = 0;
+
1534
+
1535 static const struct option long_opts[] = {
+
1536 {"unmount", no_argument, NULL, 'u'},
+
1537 {"lazy", no_argument, NULL, 'z'},
+
1538 {"quiet", no_argument, NULL, 'q'},
+
1539 {"help", no_argument, NULL, 'h'},
+
1540 {"version", no_argument, NULL, 'V'},
+
1541 {"options", required_argument, NULL, 'o'},
+
1542 // Note: auto-unmount and comm-fd don't have short versions.
+
1543 // They'ne meant for internal use by mount.c
+
1544 {"auto-unmount", no_argument, NULL, 'U'},
+
1545 {"comm-fd", required_argument, NULL, 'c'},
+
1546 {0, 0, 0, 0}};
+
1547
+
1548 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1549 if (progname == NULL) {
+
1550 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1551 exit(1);
+
1552 }
+
1553
+
1554 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1555 NULL)) != -1) {
+
1556 switch (ch) {
+
1557 case 'h':
+
1558 usage();
+
1559 break;
+
1560
+
1561 case 'V':
+
1562 show_version();
+
1563 break;
+
1564
+
1565 case 'o':
+
1566 opts = optarg;
+
1567 break;
+
1568
+
1569 case 'u':
+
1570 unmount = 1;
+
1571 break;
+
1572 case 'U':
+
1573 unmount = 1;
+
1574 auto_unmount = 1;
+
1575 setup_auto_unmount_only = 1;
+
1576 break;
+
1577 case 'c':
+
1578 commfd = optarg;
+
1579 break;
+
1580 case 'z':
+
1581 lazy = 1;
+
1582 break;
+
1583
+
1584 case 'q':
+
1585 quiet = 1;
+
1586 break;
+
1587
+
1588 default:
+
1589 exit(1);
+
1590 }
+
1591 }
+
1592
+
1593 if (lazy && !unmount) {
+
1594 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1595 exit(1);
+
1596 }
+
1597
+
1598 if (optind >= argc) {
+
1599 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1600 exit(1);
+
1601 } else if (argc > optind + 1) {
+
1602 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1603 progname);
+
1604 exit(1);
+
1605 }
+
1606
+
1607 origmnt = argv[optind];
+
1608
+
1609 drop_privs();
+
1610 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1611 if (mnt != NULL) {
+
1612 res = chdir("/");
+
1613 if (res == -1) {
+
1614 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1615 goto err_out;
+
1616 }
+
1617 }
+
1618 restore_privs();
+
1619 if (mnt == NULL)
+
1620 exit(1);
+
1621
+
1622 umask(033);
+
1623 if (!setup_auto_unmount_only && unmount)
+
1624 goto do_unmount;
+
1625
+
1626 if(commfd == NULL)
+
1627 commfd = getenv(FUSE_COMMFD_ENV);
+
1628 if (commfd == NULL) {
+
1629 fprintf(stderr, "%s: old style mounting not supported\n",
+
1630 progname);
+
1631 goto err_out;
+
1632 }
+
1633
+
1634 res = libfuse_strtol(commfd, &cfd);
+
1635 if (res) {
+
1636 fprintf(stderr,
+
1637 "%s: invalid _FUSE_COMMFD: %s\n",
+
1638 progname, commfd);
+
1639 goto err_out;
+
1640
+
1641 }
+
1642
+
1643 {
+
1644 struct stat statbuf;
+
1645 fstat(cfd, &statbuf);
+
1646 if(!S_ISSOCK(statbuf.st_mode)) {
+
1647 fprintf(stderr,
+
1648 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1649 progname, cfd);
+
1650 goto err_out;
+
1651 }
+
1652 }
+
1653
+
1654 if (setup_auto_unmount_only)
+
1655 goto wait_for_auto_unmount;
+
1656
+
1657 fd = mount_fuse(mnt, opts, &type);
+
1658 if (fd == -1)
+
1659 goto err_out;
+
1660
+
1661 res = send_fd(cfd, fd);
+
1662 if (res != 0) {
+
1663 umount2(mnt, MNT_DETACH); /* lazy umount */
+
1664 goto err_out;
+
1665 }
+
1666 close(fd);
+
1667
+
1668 if (!auto_unmount) {
+
1669 free(mnt);
+
1670 free((void*) type);
+
1671 return 0;
+
1672 }
+
1673
+
1674wait_for_auto_unmount:
+
1675 /* Become a daemon and wait for the parent to exit or die.
+
1676 ie For the control socket to get closed.
+
1677 Btw, we don't want to use daemon() function here because
+
1678 it forks and messes with the file descriptors. */
+
1679
+
1680 res = close_inherited_fds(cfd);
+
1681 if (res < 0)
+
1682 exit(EXIT_FAILURE);
+
1683
+
1684 setsid();
+
1685 res = chdir("/");
+
1686 if (res == -1) {
+
1687 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1688 goto err_out;
+
1689 }
+
1690
+
1691 sigfillset(&sigset);
+
1692 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1693
+
1694 lazy = 1;
+
1695 quiet = 1;
+
1696
+
1697 while (1) {
+
1698 unsigned char buf[16];
+
1699 int n = recv(cfd, buf, sizeof(buf), 0);
+
1700 if (!n)
+
1701 break;
+
1702
+
1703 if (n < 0) {
+
1704 if (errno == EINTR)
+
1705 continue;
+
1706 break;
+
1707 }
+
1708 }
+
1709
+
1710 if (!should_auto_unmount(mnt, type)) {
+
1711 goto success_out;
+
1712 }
+
1713
+
1714do_unmount:
+
1715 if (geteuid() == 0)
+
1716 res = unmount_fuse(mnt, quiet, lazy);
+
1717 else {
+
1718 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+
1719 if (res == -1 && !quiet)
+
1720 fprintf(stderr,
+
1721 "%s: failed to unmount %s: %s\n",
+
1722 progname, mnt, strerror(errno));
+
1723 }
+
1724 if (res == -1)
+
1725 goto err_out;
+
1726
+
1727success_out:
+
1728 free((void*) type);
+
1729 free(mnt);
+
1730 return 0;
+
1731
+
1732err_out:
+
1733 free((void*) type);
+
1734 free(mnt);
+
1735 exit(1);
+
1736}
+
+ + + + diff --git a/doc/html/fuse-3_817_81_8dir_2util_2mount_8fuse_8c_source.html b/doc/html/fuse-3_817_81_8dir_2util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..6f7fd09 --- /dev/null +++ b/doc/html/fuse-3_817_81_8dir_2util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: fuse-3.17.1.dir/util/mount.fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
9#include "fuse_config.h"
+
10
+
11#include <stdio.h>
+
12#include <stdlib.h>
+
13#include <string.h>
+
14#include <unistd.h>
+
15#include <errno.h>
+
16#include <stdint.h>
+
17#include <fcntl.h>
+
18#include <pwd.h>
+
19#include <sys/wait.h>
+
20
+
21#ifdef linux
+
22#include <sys/prctl.h>
+
23#include <sys/syscall.h>
+
24#include <linux/capability.h>
+
25#include <linux/securebits.h>
+
26/* for 2.6 kernels */
+
27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
+
28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
+
29#endif
+
30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
+
31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
+
32#endif
+
33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
+
34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
+
35#endif
+
36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
+
37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
+
38#endif
+
39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
+
40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
+
41#endif
+
42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
+
43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
+
44#endif
+
45#endif
+
46/* linux < 3.5 */
+
47#ifndef PR_SET_NO_NEW_PRIVS
+
48#define PR_SET_NO_NEW_PRIVS 38
+
49#endif
+
50
+
51#include "fuse.h"
+
52
+
53static char *progname;
+
54
+
55static char *xstrdup(const char *s)
+
56{
+
57 char *t = strdup(s);
+
58 if (!t) {
+
59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
60 exit(1);
+
61 }
+
62 return t;
+
63}
+
64
+
65static void *xrealloc(void *oldptr, size_t size)
+
66{
+
67 void *ptr = realloc(oldptr, size);
+
68 if (!ptr) {
+
69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
70 exit(1);
+
71 }
+
72 return ptr;
+
73}
+
74
+
75static void add_arg(char **cmdp, const char *opt)
+
76{
+
77 size_t optlen = strlen(opt);
+
78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
+
79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
+
80 fprintf(stderr, "%s: argument too long\n", progname);
+
81 exit(1);
+
82 }
+
83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
+
84 char *s;
+
85 s = cmd + cmdlen;
+
86 if (*cmdp)
+
87 *s++ = ' ';
+
88
+
89 *s++ = '\'';
+
90 for (; *opt; opt++) {
+
91 if (*opt == '\'') {
+
92 *s++ = '\'';
+
93 *s++ = '\\';
+
94 *s++ = '\'';
+
95 *s++ = '\'';
+
96 } else
+
97 *s++ = *opt;
+
98 }
+
99 *s++ = '\'';
+
100 *s = '\0';
+
101 *cmdp = cmd;
+
102}
+
103
+
104static char *add_option(const char *opt, char *options)
+
105{
+
106 int oldlen = options ? strlen(options) : 0;
+
107
+
108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
+
109 if (!oldlen)
+
110 strcpy(options, opt);
+
111 else {
+
112 strcat(options, ",");
+
113 strcat(options, opt);
+
114 }
+
115 return options;
+
116}
+
117
+
118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
+
119 const char *options)
+
120{
+
121 int fuse_fd = -1;
+
122 int flags = -1;
+
123 int subtype_len = strlen(subtype) + 9;
+
124 char* options_copy = xrealloc(NULL, subtype_len);
+
125
+
126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
+
127 options_copy = add_option(options, options_copy);
+
128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
+
129 if (fuse_fd == -1) {
+
130 exit(1);
+
131 }
+
132
+
133 flags = fcntl(fuse_fd, F_GETFD);
+
134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
+
135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
+
136 progname, strerror(errno));
+
137 exit(1);
+
138 }
+
139
+
140 return fuse_fd;
+
141}
+
142
+
143#ifdef linux
+
144static uint64_t get_capabilities(void)
+
145{
+
146 /*
+
147 * This invokes the capset syscall directly to avoid the libcap
+
148 * dependency, which isn't really justified just for this.
+
149 */
+
150 struct __user_cap_header_struct header = {
+
151 .version = _LINUX_CAPABILITY_VERSION_3,
+
152 .pid = 0,
+
153 };
+
154 struct __user_cap_data_struct data[2];
+
155 memset(data, 0, sizeof(data));
+
156 if (syscall(SYS_capget, &header, data) == -1) {
+
157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
+
158 progname, strerror(errno));
+
159 exit(1);
+
160 }
+
161
+
162 return data[0].effective | ((uint64_t) data[1].effective << 32);
+
163}
+
164
+
165static void set_capabilities(uint64_t caps)
+
166{
+
167 /*
+
168 * This invokes the capset syscall directly to avoid the libcap
+
169 * dependency, which isn't really justified just for this.
+
170 */
+
171 struct __user_cap_header_struct header = {
+
172 .version = _LINUX_CAPABILITY_VERSION_3,
+
173 .pid = 0,
+
174 };
+
175 struct __user_cap_data_struct data[2];
+
176 memset(data, 0, sizeof(data));
+
177 data[0].effective = data[0].permitted = caps;
+
178 data[1].effective = data[1].permitted = caps >> 32;
+
179 if (syscall(SYS_capset, &header, data) == -1) {
+
180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
+
181 progname, strerror(errno));
+
182 exit(1);
+
183 }
+
184}
+
185
+
186static void drop_and_lock_capabilities(void)
+
187{
+
188 /* Set and lock securebits. */
+
189 if (prctl(PR_SET_SECUREBITS,
+
190 SECBIT_KEEP_CAPS_LOCKED |
+
191 SECBIT_NO_SETUID_FIXUP |
+
192 SECBIT_NO_SETUID_FIXUP_LOCKED |
+
193 SECBIT_NOROOT |
+
194 SECBIT_NOROOT_LOCKED) == -1) {
+
195 fprintf(stderr, "%s: Failed to set securebits %s\n",
+
196 progname, strerror(errno));
+
197 exit(1);
+
198 }
+
199
+
200 /* Clear the capability bounding set. */
+
201 int cap;
+
202 for (cap = 0; ; cap++) {
+
203 int cap_status = prctl(PR_CAPBSET_READ, cap);
+
204 if (cap_status == 0) {
+
205 continue;
+
206 }
+
207 if (cap_status == -1 && errno == EINVAL) {
+
208 break;
+
209 }
+
210
+
211 if (cap_status != 1) {
+
212 fprintf(stderr,
+
213 "%s: Failed to get capability %u: %s\n",
+
214 progname, cap, strerror(errno));
+
215 exit(1);
+
216 }
+
217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
+
218 fprintf(stderr,
+
219 "%s: Failed to drop capability %u: %s\n",
+
220 progname, cap, strerror(errno));
+
221 }
+
222 }
+
223
+
224 /* Drop capabilities. */
+
225 set_capabilities(0);
+
226
+
227 /* Prevent re-acquisition of privileges. */
+
228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
+
229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
+
230 progname, strerror(errno));
+
231 exit(1);
+
232 }
+
233}
+
234#endif
+
235
+
236int main(int argc, char *argv[])
+
237{
+
238 char *type = NULL;
+
239 char *source;
+
240 char *dup_source = NULL;
+
241 const char *mountpoint;
+
242 char *basename;
+
243 char *options = NULL;
+
244 char *command = NULL;
+
245 char *setuid_name = NULL;
+
246 int i;
+
247 int dev = 1;
+
248 int suid = 1;
+
249 int pass_fuse_fd = 0;
+
250 int fuse_fd = 0;
+
251 int drop_privileges = 0;
+
252 char *dev_fd_mountpoint = NULL;
+
253
+
254 progname = argv[0];
+
255 basename = strrchr(argv[0], '/');
+
256 if (basename)
+
257 basename++;
+
258 else
+
259 basename = argv[0];
+
260
+
261 if (strncmp(basename, "mount.fuse.", 11) == 0)
+
262 type = basename + 11;
+
263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
+
264 type = basename + 14;
+
265
+
266 if (type && !type[0])
+
267 type = NULL;
+
268
+
269 if (argc < 3) {
+
270 fprintf(stderr,
+
271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
+
272 progname, type ? "source" : "type#[source]");
+
273 exit(1);
+
274 }
+
275
+
276 source = argv[1];
+
277 if (!source[0])
+
278 source = NULL;
+
279
+
280 mountpoint = argv[2];
+
281
+
282 for (i = 3; i < argc; i++) {
+
283 if (strcmp(argv[i], "-v") == 0) {
+
284 continue;
+
285 } else if (strcmp(argv[i], "-t") == 0) {
+
286 i++;
+
287
+
288 if (i == argc) {
+
289 fprintf(stderr,
+
290 "%s: missing argument to option '-t'\n",
+
291 progname);
+
292 exit(1);
+
293 }
+
294 type = argv[i];
+
295 if (strncmp(type, "fuse.", 5) == 0)
+
296 type += 5;
+
297 else if (strncmp(type, "fuseblk.", 8) == 0)
+
298 type += 8;
+
299
+
300 if (!type[0]) {
+
301 fprintf(stderr,
+
302 "%s: empty type given as argument to option '-t'\n",
+
303 progname);
+
304 exit(1);
+
305 }
+
306 } else if (strcmp(argv[i], "-o") == 0) {
+
307 char *opts;
+
308 char *opt;
+
309 i++;
+
310 if (i == argc)
+
311 break;
+
312
+
313 opts = xstrdup(argv[i]);
+
314 opt = strtok(opts, ",");
+
315 while (opt) {
+
316 int j;
+
317 int ignore = 0;
+
318 const char *ignore_opts[] = { "",
+
319 "user",
+
320 "nofail",
+
321 "nouser",
+
322 "users",
+
323 "auto",
+
324 "noauto",
+
325 "_netdev",
+
326 NULL};
+
327 if (strncmp(opt, "setuid=", 7) == 0) {
+
328 setuid_name = xstrdup(opt + 7);
+
329 ignore = 1;
+
330 } else if (strcmp(opt,
+
331 "drop_privileges") == 0) {
+
332 pass_fuse_fd = 1;
+
333 drop_privileges = 1;
+
334 ignore = 1;
+
335 }
+
336 for (j = 0; ignore_opts[j]; j++)
+
337 if (strcmp(opt, ignore_opts[j]) == 0)
+
338 ignore = 1;
+
339
+
340 if (!ignore) {
+
341 if (strcmp(opt, "nodev") == 0)
+
342 dev = 0;
+
343 else if (strcmp(opt, "nosuid") == 0)
+
344 suid = 0;
+
345
+
346 options = add_option(opt, options);
+
347 }
+
348 opt = strtok(NULL, ",");
+
349 }
+
350 free(opts);
+
351 }
+
352 }
+
353
+
354 if (drop_privileges) {
+
355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
+
356 CAP_TO_MASK(CAP_SYS_ADMIN);
+
357 if ((get_capabilities() & required_caps) != required_caps) {
+
358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
+
359 progname, progname);
+
360 exit(1);
+
361 }
+
362 }
+
363
+
364 if (dev)
+
365 options = add_option("dev", options);
+
366 if (suid)
+
367 options = add_option("suid", options);
+
368
+
369 if (!type) {
+
370 if (source) {
+
371 dup_source = xstrdup(source);
+
372 type = dup_source;
+
373 source = strchr(type, '#');
+
374 if (source)
+
375 *source++ = '\0';
+
376 if (!type[0]) {
+
377 fprintf(stderr, "%s: empty filesystem type\n",
+
378 progname);
+
379 exit(1);
+
380 }
+
381 } else {
+
382 fprintf(stderr, "%s: empty source\n", progname);
+
383 exit(1);
+
384 }
+
385 }
+
386
+
387 if (setuid_name && setuid_name[0]) {
+
388#ifdef linux
+
389 if (drop_privileges) {
+
390 /*
+
391 * Make securebits more permissive before calling
+
392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
+
393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
+
394 * have the side effect of dropping all capabilities,
+
395 * and we need to retain CAP_SETPCAP in order to drop
+
396 * all privileges before exec().
+
397 */
+
398 if (prctl(PR_SET_SECUREBITS,
+
399 SECBIT_KEEP_CAPS |
+
400 SECBIT_NO_SETUID_FIXUP) == -1) {
+
401 fprintf(stderr,
+
402 "%s: Failed to set securebits %s\n",
+
403 progname, strerror(errno));
+
404 exit(1);
+
405 }
+
406 }
+
407#endif
+
408
+
409 struct passwd *pwd = getpwnam(setuid_name);
+
410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
+
411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
+
412 progname, setuid_name, strerror(errno));
+
413 exit(1);
+
414 }
+
415 } else if (!getenv("HOME")) {
+
416 /* Hack to make filesystems work in the boot environment */
+
417 setenv("HOME", "/root", 0);
+
418 }
+
419
+
420 if (pass_fuse_fd) {
+
421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
+
422 dev_fd_mountpoint = xrealloc(NULL, 20);
+
423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
+
424 mountpoint = dev_fd_mountpoint;
+
425 }
+
426
+
427#ifdef linux
+
428 if (drop_privileges) {
+
429 drop_and_lock_capabilities();
+
430 }
+
431#endif
+
432 add_arg(&command, type);
+
433 if (source)
+
434 add_arg(&command, source);
+
435 add_arg(&command, mountpoint);
+
436 if (options) {
+
437 add_arg(&command, "-o");
+
438 add_arg(&command, options);
+
439 }
+
440
+
441 free(options);
+
442 free(dev_fd_mountpoint);
+
443 free(dup_source);
+
444 free(setuid_name);
+
445
+
446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+
447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+
448 strerror(errno));
+
449
+
450 if (pass_fuse_fd)
+
451 close(fuse_fd);
+
452 free(command);
+
453 return 1;
+
454}
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:475
+
+ + + + diff --git a/doc/html/fuse_8c_source.html b/doc/html/fuse_8c_source.html new file mode 100644 index 0000000..b9d7644 --- /dev/null +++ b/doc/html/fuse_8c_source.html @@ -0,0 +1,5460 @@ + + + + + + + +libfuse: lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define container_of(ptr, type, member) ({ \
+
96 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
97 (type *)( (char *)__mptr - offsetof(type,member) );})
+
98
+
99#define list_entry(ptr, type, member) \
+
100 container_of(ptr, type, member)
+
101
+
102struct list_head {
+
103 struct list_head *next;
+
104 struct list_head *prev;
+
105};
+
106
+
107struct node_slab {
+
108 struct list_head list; /* must be the first member */
+
109 struct list_head freelist;
+
110 int used;
+
111};
+
112
+
113struct fuse {
+
114 struct fuse_session *se;
+
115 struct node_table name_table;
+
116 struct node_table id_table;
+
117 struct list_head lru_table;
+
118 fuse_ino_t ctr;
+
119 unsigned int generation;
+
120 unsigned int hidectr;
+
121 pthread_mutex_t lock;
+
122 struct fuse_config conf;
+
123 int intr_installed;
+
124 struct fuse_fs *fs;
+
125 struct lock_queue_element *lockq;
+
126 int pagesize;
+
127 struct list_head partial_slabs;
+
128 struct list_head full_slabs;
+
129 pthread_t prune_thread;
+
130};
+
131
+
132struct lock {
+
133 int type;
+
134 off_t start;
+
135 off_t end;
+
136 pid_t pid;
+
137 uint64_t owner;
+
138 struct lock *next;
+
139};
+
140
+
141struct node {
+
142 struct node *name_next;
+
143 struct node *id_next;
+
144 fuse_ino_t nodeid;
+
145 unsigned int generation;
+
146 int refctr;
+
147 struct node *parent;
+
148 char *name;
+
149 uint64_t nlookup;
+
150 int open_count;
+
151 struct timespec stat_updated;
+
152 struct timespec mtime;
+
153 off_t size;
+
154 struct lock *locks;
+
155 unsigned int is_hidden : 1;
+
156 unsigned int cache_valid : 1;
+
157 int treelock;
+
158 char inline_name[32];
+
159};
+
160
+
161#define TREELOCK_WRITE -1
+
162#define TREELOCK_WAIT_OFFSET INT_MIN
+
163
+
164struct node_lru {
+
165 struct node node;
+
166 struct list_head lru;
+
167 struct timespec forget_time;
+
168};
+
169
+
170struct fuse_direntry {
+
171 struct stat stat;
+
172 enum fuse_fill_dir_flags flags;
+
173 char *name;
+
174 struct fuse_direntry *next;
+
175};
+
176
+
177struct fuse_dh {
+
178 pthread_mutex_t lock;
+
179 struct fuse *fuse;
+
180 fuse_req_t req;
+
181 char *contents;
+
182 struct fuse_direntry *first;
+
183 struct fuse_direntry **last;
+
184 unsigned len;
+
185 unsigned size;
+
186 unsigned needlen;
+
187 int filled;
+
188 uint64_t fh;
+
189 int error;
+
190 fuse_ino_t nodeid;
+
191};
+
192
+
193struct fuse_context_i {
+
194 struct fuse_context ctx;
+
195 fuse_req_t req;
+
196};
+
197
+
198/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
199extern fuse_module_factory_t fuse_module_subdir_factory;
+
200#ifdef HAVE_ICONV
+
201extern fuse_module_factory_t fuse_module_iconv_factory;
+
202#endif
+
203
+
204static pthread_key_t fuse_context_key;
+
205static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
206static int fuse_context_ref;
+
207static struct fuse_module *fuse_modules = NULL;
+
208
+
209static int fuse_register_module(const char *name,
+
210 fuse_module_factory_t factory,
+
211 struct fusemod_so *so)
+
212{
+
213 struct fuse_module *mod;
+
214
+
215 mod = calloc(1, sizeof(struct fuse_module));
+
216 if (!mod) {
+
217 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
218 return -1;
+
219 }
+
220 mod->name = strdup(name);
+
221 if (!mod->name) {
+
222 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
223 free(mod);
+
224 return -1;
+
225 }
+
226 mod->factory = factory;
+
227 mod->ctr = 0;
+
228 mod->so = so;
+
229 if (mod->so)
+
230 mod->so->ctr++;
+
231 mod->next = fuse_modules;
+
232 fuse_modules = mod;
+
233
+
234 return 0;
+
235}
+
236
+
237static void fuse_unregister_module(struct fuse_module *m)
+
238{
+
239 struct fuse_module **mp;
+
240 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
241 if (*mp == m) {
+
242 *mp = (*mp)->next;
+
243 break;
+
244 }
+
245 }
+
246 free(m->name);
+
247 free(m);
+
248}
+
249
+
250static int fuse_load_so_module(const char *module)
+
251{
+
252 int ret = -1;
+
253 char *tmp;
+
254 struct fusemod_so *so;
+
255 fuse_module_factory_t *factory;
+
256
+
257 tmp = malloc(strlen(module) + 64);
+
258 if (!tmp) {
+
259 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
260 return -1;
+
261 }
+
262 sprintf(tmp, "libfusemod_%s.so", module);
+
263 so = calloc(1, sizeof(struct fusemod_so));
+
264 if (!so) {
+
265 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
266 goto out;
+
267 }
+
268
+
269 so->handle = dlopen(tmp, RTLD_NOW);
+
270 if (so->handle == NULL) {
+
271 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
272 tmp, dlerror());
+
273 goto out_free_so;
+
274 }
+
275
+
276 sprintf(tmp, "fuse_module_%s_factory", module);
+
277 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
278 if (factory == NULL) {
+
279 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
280 tmp, dlerror());
+
281 goto out_dlclose;
+
282 }
+
283 ret = fuse_register_module(module, *factory, so);
+
284 if (ret)
+
285 goto out_dlclose;
+
286
+
287out:
+
288 free(tmp);
+
289 return ret;
+
290
+
291out_dlclose:
+
292 dlclose(so->handle);
+
293out_free_so:
+
294 free(so);
+
295 goto out;
+
296}
+
297
+
298static struct fuse_module *fuse_find_module(const char *module)
+
299{
+
300 struct fuse_module *m;
+
301 for (m = fuse_modules; m; m = m->next) {
+
302 if (strcmp(module, m->name) == 0) {
+
303 m->ctr++;
+
304 break;
+
305 }
+
306 }
+
307 return m;
+
308}
+
309
+
310static struct fuse_module *fuse_get_module(const char *module)
+
311{
+
312 struct fuse_module *m;
+
313
+
314 pthread_mutex_lock(&fuse_context_lock);
+
315 m = fuse_find_module(module);
+
316 if (!m) {
+
317 int err = fuse_load_so_module(module);
+
318 if (!err)
+
319 m = fuse_find_module(module);
+
320 }
+
321 pthread_mutex_unlock(&fuse_context_lock);
+
322 return m;
+
323}
+
324
+
325static void fuse_put_module(struct fuse_module *m)
+
326{
+
327 pthread_mutex_lock(&fuse_context_lock);
+
328 if (m->so)
+
329 assert(m->ctr > 0);
+
330 /* Builtin modules may already have m->ctr == 0 */
+
331 if (m->ctr > 0)
+
332 m->ctr--;
+
333 if (!m->ctr && m->so) {
+
334 struct fusemod_so *so = m->so;
+
335 assert(so->ctr > 0);
+
336 so->ctr--;
+
337 if (!so->ctr) {
+
338 struct fuse_module **mp;
+
339 for (mp = &fuse_modules; *mp;) {
+
340 if ((*mp)->so == so)
+
341 fuse_unregister_module(*mp);
+
342 else
+
343 mp = &(*mp)->next;
+
344 }
+
345 dlclose(so->handle);
+
346 free(so);
+
347 }
+
348 } else if (!m->ctr) {
+
349 fuse_unregister_module(m);
+
350 }
+
351 pthread_mutex_unlock(&fuse_context_lock);
+
352}
+
353
+
354static void init_list_head(struct list_head *list)
+
355{
+
356 list->next = list;
+
357 list->prev = list;
+
358}
+
359
+
360static int list_empty(const struct list_head *head)
+
361{
+
362 return head->next == head;
+
363}
+
364
+
365static void list_add(struct list_head *new, struct list_head *prev,
+
366 struct list_head *next)
+
367{
+
368 next->prev = new;
+
369 new->next = next;
+
370 new->prev = prev;
+
371 prev->next = new;
+
372}
+
373
+
374static inline void list_add_head(struct list_head *new, struct list_head *head)
+
375{
+
376 list_add(new, head, head->next);
+
377}
+
378
+
379static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
380{
+
381 list_add(new, head->prev, head);
+
382}
+
383
+
384static inline void list_del(struct list_head *entry)
+
385{
+
386 struct list_head *prev = entry->prev;
+
387 struct list_head *next = entry->next;
+
388
+
389 next->prev = prev;
+
390 prev->next = next;
+
391}
+
392
+
393static inline int lru_enabled(struct fuse *f)
+
394{
+
395 return f->conf.remember > 0;
+
396}
+
397
+
398static struct node_lru *node_lru(struct node *node)
+
399{
+
400 return (struct node_lru *) node;
+
401}
+
402
+
403static size_t get_node_size(struct fuse *f)
+
404{
+
405 if (lru_enabled(f))
+
406 return sizeof(struct node_lru);
+
407 else
+
408 return sizeof(struct node);
+
409}
+
410
+
411#ifdef FUSE_NODE_SLAB
+
412static struct node_slab *list_to_slab(struct list_head *head)
+
413{
+
414 return (struct node_slab *) head;
+
415}
+
416
+
417static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
418{
+
419 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
420}
+
421
+
422static int alloc_slab(struct fuse *f)
+
423{
+
424 void *mem;
+
425 struct node_slab *slab;
+
426 char *start;
+
427 size_t num;
+
428 size_t i;
+
429 size_t node_size = get_node_size(f);
+
430
+
431 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
432 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
433
+
434 if (mem == MAP_FAILED)
+
435 return -1;
+
436
+
437 slab = mem;
+
438 init_list_head(&slab->freelist);
+
439 slab->used = 0;
+
440 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
441
+
442 start = (char *) mem + f->pagesize - num * node_size;
+
443 for (i = 0; i < num; i++) {
+
444 struct list_head *n;
+
445
+
446 n = (struct list_head *) (start + i * node_size);
+
447 list_add_tail(n, &slab->freelist);
+
448 }
+
449 list_add_tail(&slab->list, &f->partial_slabs);
+
450
+
451 return 0;
+
452}
+
453
+
454static struct node *alloc_node(struct fuse *f)
+
455{
+
456 struct node_slab *slab;
+
457 struct list_head *node;
+
458
+
459 if (list_empty(&f->partial_slabs)) {
+
460 int res = alloc_slab(f);
+
461 if (res != 0)
+
462 return NULL;
+
463 }
+
464 slab = list_to_slab(f->partial_slabs.next);
+
465 slab->used++;
+
466 node = slab->freelist.next;
+
467 list_del(node);
+
468 if (list_empty(&slab->freelist)) {
+
469 list_del(&slab->list);
+
470 list_add_tail(&slab->list, &f->full_slabs);
+
471 }
+
472 memset(node, 0, sizeof(struct node));
+
473
+
474 return (struct node *) node;
+
475}
+
476
+
477static void free_slab(struct fuse *f, struct node_slab *slab)
+
478{
+
479 int res;
+
480
+
481 list_del(&slab->list);
+
482 res = munmap(slab, f->pagesize);
+
483 if (res == -1)
+
484 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
485 slab);
+
486}
+
487
+
488static void free_node_mem(struct fuse *f, struct node *node)
+
489{
+
490 struct node_slab *slab = node_to_slab(f, node);
+
491 struct list_head *n = (struct list_head *) node;
+
492
+
493 slab->used--;
+
494 if (slab->used) {
+
495 if (list_empty(&slab->freelist)) {
+
496 list_del(&slab->list);
+
497 list_add_tail(&slab->list, &f->partial_slabs);
+
498 }
+
499 list_add_head(n, &slab->freelist);
+
500 } else {
+
501 free_slab(f, slab);
+
502 }
+
503}
+
504#else
+
505static struct node *alloc_node(struct fuse *f)
+
506{
+
507 return (struct node *) calloc(1, get_node_size(f));
+
508}
+
509
+
510static void free_node_mem(struct fuse *f, struct node *node)
+
511{
+
512 (void) f;
+
513 free(node);
+
514}
+
515#endif
+
516
+
517static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
518{
+
519 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
520 uint64_t oldhash = hash % (f->id_table.size / 2);
+
521
+
522 if (oldhash >= f->id_table.split)
+
523 return oldhash;
+
524 else
+
525 return hash;
+
526}
+
527
+
528static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
529{
+
530 size_t hash = id_hash(f, nodeid);
+
531 struct node *node;
+
532
+
533 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
534 if (node->nodeid == nodeid)
+
535 return node;
+
536
+
537 return NULL;
+
538}
+
539
+
540static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
541{
+
542 struct node *node = get_node_nocheck(f, nodeid);
+
543 if (!node) {
+
544 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
545 (unsigned long long) nodeid);
+
546 abort();
+
547 }
+
548 return node;
+
549}
+
550
+
551static void curr_time(struct timespec *now);
+
552static double diff_timespec(const struct timespec *t1,
+
553 const struct timespec *t2);
+
554
+
555static void remove_node_lru(struct node *node)
+
556{
+
557 struct node_lru *lnode = node_lru(node);
+
558 list_del(&lnode->lru);
+
559 init_list_head(&lnode->lru);
+
560}
+
561
+
562static void set_forget_time(struct fuse *f, struct node *node)
+
563{
+
564 struct node_lru *lnode = node_lru(node);
+
565
+
566 list_del(&lnode->lru);
+
567 list_add_tail(&lnode->lru, &f->lru_table);
+
568 curr_time(&lnode->forget_time);
+
569}
+
570
+
571static void free_node(struct fuse *f, struct node *node)
+
572{
+
573 if (node->name != node->inline_name)
+
574 free(node->name);
+
575 free_node_mem(f, node);
+
576}
+
577
+
578static void node_table_reduce(struct node_table *t)
+
579{
+
580 size_t newsize = t->size / 2;
+
581 void *newarray;
+
582
+
583 if (newsize < NODE_TABLE_MIN_SIZE)
+
584 return;
+
585
+
586 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
587 if (newarray != NULL)
+
588 t->array = newarray;
+
589
+
590 t->size = newsize;
+
591 t->split = t->size / 2;
+
592}
+
593
+
594static void remerge_id(struct fuse *f)
+
595{
+
596 struct node_table *t = &f->id_table;
+
597 int iter;
+
598
+
599 if (t->split == 0)
+
600 node_table_reduce(t);
+
601
+
602 for (iter = 8; t->split > 0 && iter; iter--) {
+
603 struct node **upper;
+
604
+
605 t->split--;
+
606 upper = &t->array[t->split + t->size / 2];
+
607 if (*upper) {
+
608 struct node **nodep;
+
609
+
610 for (nodep = &t->array[t->split]; *nodep;
+
611 nodep = &(*nodep)->id_next);
+
612
+
613 *nodep = *upper;
+
614 *upper = NULL;
+
615 break;
+
616 }
+
617 }
+
618}
+
619
+
620static void unhash_id(struct fuse *f, struct node *node)
+
621{
+
622 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
623
+
624 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
625 if (*nodep == node) {
+
626 *nodep = node->id_next;
+
627 f->id_table.use--;
+
628
+
629 if(f->id_table.use < f->id_table.size / 4)
+
630 remerge_id(f);
+
631 return;
+
632 }
+
633}
+
634
+
635static int node_table_resize(struct node_table *t)
+
636{
+
637 size_t newsize = t->size * 2;
+
638 void *newarray;
+
639
+
640 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
641 if (newarray == NULL)
+
642 return -1;
+
643
+
644 t->array = newarray;
+
645 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
646 t->size = newsize;
+
647 t->split = 0;
+
648
+
649 return 0;
+
650}
+
651
+
652static void rehash_id(struct fuse *f)
+
653{
+
654 struct node_table *t = &f->id_table;
+
655 struct node **nodep;
+
656 struct node **next;
+
657 size_t hash;
+
658
+
659 if (t->split == t->size / 2)
+
660 return;
+
661
+
662 hash = t->split;
+
663 t->split++;
+
664 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
665 struct node *node = *nodep;
+
666 size_t newhash = id_hash(f, node->nodeid);
+
667
+
668 if (newhash != hash) {
+
669 next = nodep;
+
670 *nodep = node->id_next;
+
671 node->id_next = t->array[newhash];
+
672 t->array[newhash] = node;
+
673 } else {
+
674 next = &node->id_next;
+
675 }
+
676 }
+
677 if (t->split == t->size / 2)
+
678 node_table_resize(t);
+
679}
+
680
+
681static void hash_id(struct fuse *f, struct node *node)
+
682{
+
683 size_t hash = id_hash(f, node->nodeid);
+
684 node->id_next = f->id_table.array[hash];
+
685 f->id_table.array[hash] = node;
+
686 f->id_table.use++;
+
687
+
688 if (f->id_table.use >= f->id_table.size / 2)
+
689 rehash_id(f);
+
690}
+
691
+
692static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
693 const char *name)
+
694{
+
695 uint64_t hash = parent;
+
696 uint64_t oldhash;
+
697
+
698 for (; *name; name++)
+
699 hash = hash * 31 + (unsigned char) *name;
+
700
+
701 hash %= f->name_table.size;
+
702 oldhash = hash % (f->name_table.size / 2);
+
703 if (oldhash >= f->name_table.split)
+
704 return oldhash;
+
705 else
+
706 return hash;
+
707}
+
708
+
709static void unref_node(struct fuse *f, struct node *node);
+
710
+
711static void remerge_name(struct fuse *f)
+
712{
+
713 struct node_table *t = &f->name_table;
+
714 int iter;
+
715
+
716 if (t->split == 0)
+
717 node_table_reduce(t);
+
718
+
719 for (iter = 8; t->split > 0 && iter; iter--) {
+
720 struct node **upper;
+
721
+
722 t->split--;
+
723 upper = &t->array[t->split + t->size / 2];
+
724 if (*upper) {
+
725 struct node **nodep;
+
726
+
727 for (nodep = &t->array[t->split]; *nodep;
+
728 nodep = &(*nodep)->name_next);
+
729
+
730 *nodep = *upper;
+
731 *upper = NULL;
+
732 break;
+
733 }
+
734 }
+
735}
+
736
+
737static void unhash_name(struct fuse *f, struct node *node)
+
738{
+
739 if (node->name) {
+
740 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
741 struct node **nodep = &f->name_table.array[hash];
+
742
+
743 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
744 if (*nodep == node) {
+
745 *nodep = node->name_next;
+
746 node->name_next = NULL;
+
747 unref_node(f, node->parent);
+
748 if (node->name != node->inline_name)
+
749 free(node->name);
+
750 node->name = NULL;
+
751 node->parent = NULL;
+
752 f->name_table.use--;
+
753
+
754 if (f->name_table.use < f->name_table.size / 4)
+
755 remerge_name(f);
+
756 return;
+
757 }
+
758 fuse_log(FUSE_LOG_ERR,
+
759 "fuse internal error: unable to unhash node: %llu\n",
+
760 (unsigned long long) node->nodeid);
+
761 abort();
+
762 }
+
763}
+
764
+
765static void rehash_name(struct fuse *f)
+
766{
+
767 struct node_table *t = &f->name_table;
+
768 struct node **nodep;
+
769 struct node **next;
+
770 size_t hash;
+
771
+
772 if (t->split == t->size / 2)
+
773 return;
+
774
+
775 hash = t->split;
+
776 t->split++;
+
777 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
778 struct node *node = *nodep;
+
779 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
780
+
781 if (newhash != hash) {
+
782 next = nodep;
+
783 *nodep = node->name_next;
+
784 node->name_next = t->array[newhash];
+
785 t->array[newhash] = node;
+
786 } else {
+
787 next = &node->name_next;
+
788 }
+
789 }
+
790 if (t->split == t->size / 2)
+
791 node_table_resize(t);
+
792}
+
793
+
794static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
795 const char *name)
+
796{
+
797 size_t hash = name_hash(f, parentid, name);
+
798 struct node *parent = get_node(f, parentid);
+
799 if (strlen(name) < sizeof(node->inline_name)) {
+
800 strcpy(node->inline_name, name);
+
801 node->name = node->inline_name;
+
802 } else {
+
803 node->name = strdup(name);
+
804 if (node->name == NULL)
+
805 return -1;
+
806 }
+
807
+
808 parent->refctr ++;
+
809 node->parent = parent;
+
810 node->name_next = f->name_table.array[hash];
+
811 f->name_table.array[hash] = node;
+
812 f->name_table.use++;
+
813
+
814 if (f->name_table.use >= f->name_table.size / 2)
+
815 rehash_name(f);
+
816
+
817 return 0;
+
818}
+
819
+
820static void delete_node(struct fuse *f, struct node *node)
+
821{
+
822 if (f->conf.debug)
+
823 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
824 (unsigned long long) node->nodeid);
+
825
+
826 assert(node->treelock == 0);
+
827 unhash_name(f, node);
+
828 if (lru_enabled(f))
+
829 remove_node_lru(node);
+
830 unhash_id(f, node);
+
831 free_node(f, node);
+
832}
+
833
+
834static void unref_node(struct fuse *f, struct node *node)
+
835{
+
836 assert(node->refctr > 0);
+
837 node->refctr --;
+
838 if (!node->refctr)
+
839 delete_node(f, node);
+
840}
+
841
+
842static fuse_ino_t next_id(struct fuse *f)
+
843{
+
844 do {
+
845 f->ctr = (f->ctr + 1) & 0xffffffff;
+
846 if (!f->ctr)
+
847 f->generation ++;
+
848 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
849 get_node_nocheck(f, f->ctr) != NULL);
+
850 return f->ctr;
+
851}
+
852
+
853static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
854 const char *name)
+
855{
+
856 size_t hash = name_hash(f, parent, name);
+
857 struct node *node;
+
858
+
859 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
860 if (node->parent->nodeid == parent &&
+
861 strcmp(node->name, name) == 0)
+
862 return node;
+
863
+
864 return NULL;
+
865}
+
866
+
867static void inc_nlookup(struct node *node)
+
868{
+
869 if (!node->nlookup)
+
870 node->refctr++;
+
871 node->nlookup++;
+
872}
+
873
+
874static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
875 const char *name)
+
876{
+
877 struct node *node;
+
878
+
879 pthread_mutex_lock(&f->lock);
+
880 if (!name)
+
881 node = get_node(f, parent);
+
882 else
+
883 node = lookup_node(f, parent, name);
+
884 if (node == NULL) {
+
885 node = alloc_node(f);
+
886 if (node == NULL)
+
887 goto out_err;
+
888
+
889 node->nodeid = next_id(f);
+
890 node->generation = f->generation;
+
891 if (f->conf.remember)
+
892 inc_nlookup(node);
+
893
+
894 if (hash_name(f, node, parent, name) == -1) {
+
895 free_node(f, node);
+
896 node = NULL;
+
897 goto out_err;
+
898 }
+
899 hash_id(f, node);
+
900 if (lru_enabled(f)) {
+
901 struct node_lru *lnode = node_lru(node);
+
902 init_list_head(&lnode->lru);
+
903 }
+
904 } else if (lru_enabled(f) && node->nlookup == 1) {
+
905 remove_node_lru(node);
+
906 }
+
907 inc_nlookup(node);
+
908out_err:
+
909 pthread_mutex_unlock(&f->lock);
+
910 return node;
+
911}
+
912
+
913static int lookup_path_in_cache(struct fuse *f,
+
914 const char *path, fuse_ino_t *inop)
+
915{
+
916 char *tmp = strdup(path);
+
917 if (!tmp)
+
918 return -ENOMEM;
+
919
+
920 pthread_mutex_lock(&f->lock);
+
921 fuse_ino_t ino = FUSE_ROOT_ID;
+
922
+
923 int err = 0;
+
924 char *save_ptr;
+
925 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
926 while (path_element != NULL) {
+
927 struct node *node = lookup_node(f, ino, path_element);
+
928 if (node == NULL) {
+
929 err = -ENOENT;
+
930 break;
+
931 }
+
932 ino = node->nodeid;
+
933 path_element = strtok_r(NULL, "/", &save_ptr);
+
934 }
+
935 pthread_mutex_unlock(&f->lock);
+
936 free(tmp);
+
937
+
938 if (!err)
+
939 *inop = ino;
+
940 return err;
+
941}
+
942
+
943static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
944{
+
945 size_t len = strlen(name);
+
946
+
947 if (s - len <= *buf) {
+
948 unsigned pathlen = *bufsize - (s - *buf);
+
949 unsigned newbufsize = *bufsize;
+
950 char *newbuf;
+
951
+
952 while (newbufsize < pathlen + len + 1) {
+
953 if (newbufsize >= 0x80000000)
+
954 newbufsize = 0xffffffff;
+
955 else
+
956 newbufsize *= 2;
+
957 }
+
958
+
959 newbuf = realloc(*buf, newbufsize);
+
960 if (newbuf == NULL)
+
961 return NULL;
+
962
+
963 *buf = newbuf;
+
964 s = newbuf + newbufsize - pathlen;
+
965 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
966 *bufsize = newbufsize;
+
967 }
+
968 s -= len;
+
969 memcpy(s, name, len);
+
970 s--;
+
971 *s = '/';
+
972
+
973 return s;
+
974}
+
975
+
976static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
977 struct node *end)
+
978{
+
979 struct node *node;
+
980
+
981 if (wnode) {
+
982 assert(wnode->treelock == TREELOCK_WRITE);
+
983 wnode->treelock = 0;
+
984 }
+
985
+
986 for (node = get_node(f, nodeid);
+
987 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
988 assert(node->treelock != 0);
+
989 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
990 assert(node->treelock != TREELOCK_WRITE);
+
991 node->treelock--;
+
992 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
993 node->treelock = 0;
+
994 }
+
995}
+
996
+
997static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
998 char **path, struct node **wnodep, bool need_lock)
+
999{
+
1000 unsigned bufsize = 256;
+
1001 char *buf;
+
1002 char *s;
+
1003 struct node *node;
+
1004 struct node *wnode = NULL;
+
1005 int err;
+
1006
+
1007 *path = NULL;
+
1008
+
1009 err = -ENOMEM;
+
1010 buf = malloc(bufsize);
+
1011 if (buf == NULL)
+
1012 goto out_err;
+
1013
+
1014 s = buf + bufsize - 1;
+
1015 *s = '\0';
+
1016
+
1017 if (name != NULL) {
+
1018 s = add_name(&buf, &bufsize, s, name);
+
1019 err = -ENOMEM;
+
1020 if (s == NULL)
+
1021 goto out_free;
+
1022 }
+
1023
+
1024 if (wnodep) {
+
1025 assert(need_lock);
+
1026 wnode = lookup_node(f, nodeid, name);
+
1027 if (wnode) {
+
1028 if (wnode->treelock != 0) {
+
1029 if (wnode->treelock > 0)
+
1030 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1031 err = -EAGAIN;
+
1032 goto out_free;
+
1033 }
+
1034 wnode->treelock = TREELOCK_WRITE;
+
1035 }
+
1036 }
+
1037
+
1038 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1039 node = node->parent) {
+
1040 err = -ESTALE;
+
1041 if (node->name == NULL || node->parent == NULL)
+
1042 goto out_unlock;
+
1043
+
1044 err = -ENOMEM;
+
1045 s = add_name(&buf, &bufsize, s, node->name);
+
1046 if (s == NULL)
+
1047 goto out_unlock;
+
1048
+
1049 if (need_lock) {
+
1050 err = -EAGAIN;
+
1051 if (node->treelock < 0)
+
1052 goto out_unlock;
+
1053
+
1054 node->treelock++;
+
1055 }
+
1056 }
+
1057
+
1058 if (s[0])
+
1059 memmove(buf, s, bufsize - (s - buf));
+
1060 else
+
1061 strcpy(buf, "/");
+
1062
+
1063 *path = buf;
+
1064 if (wnodep)
+
1065 *wnodep = wnode;
+
1066
+
1067 return 0;
+
1068
+
1069 out_unlock:
+
1070 if (need_lock)
+
1071 unlock_path(f, nodeid, wnode, node);
+
1072 out_free:
+
1073 free(buf);
+
1074
+
1075 out_err:
+
1076 return err;
+
1077}
+
1078
+
1079static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1080 fuse_ino_t nodeid2, const char *name2,
+
1081 char **path1, char **path2,
+
1082 struct node **wnode1, struct node **wnode2)
+
1083{
+
1084 int err;
+
1085
+
1086 /* FIXME: locking two paths needs deadlock checking */
+
1087 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1088 if (!err) {
+
1089 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1090 if (err) {
+
1091 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1092
+
1093 unlock_path(f, nodeid1, wn1, NULL);
+
1094 free(*path1);
+
1095 }
+
1096 }
+
1097 return err;
+
1098}
+
1099
+
1100static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1101{
+
1102 int err;
+
1103
+
1104 if (!qe->path1) {
+
1105 /* Just waiting for it to be unlocked */
+
1106 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1107 pthread_cond_signal(&qe->cond);
+
1108
+
1109 return;
+
1110 }
+
1111
+
1112 if (qe->done)
+
1113 return; // Don't try to double-lock the element
+
1114
+
1115 if (!qe->path2) {
+
1116 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1117 qe->wnode1, true);
+
1118 } else {
+
1119 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1120 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1121 qe->wnode2);
+
1122 }
+
1123
+
1124 if (err == -EAGAIN)
+
1125 return; /* keep trying */
+
1126
+
1127 qe->err = err;
+
1128 qe->done = true;
+
1129 pthread_cond_signal(&qe->cond);
+
1130}
+
1131
+
1132static void wake_up_queued(struct fuse *f)
+
1133{
+
1134 struct lock_queue_element *qe;
+
1135
+
1136 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1137 queue_element_wakeup(f, qe);
+
1138}
+
1139
+
1140static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1141 const char *name, bool wr)
+
1142{
+
1143 if (f->conf.debug) {
+
1144 struct node *wnode = NULL;
+
1145
+
1146 if (wr)
+
1147 wnode = lookup_node(f, nodeid, name);
+
1148
+
1149 if (wnode) {
+
1150 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1151 msg, (unsigned long long) wnode->nodeid);
+
1152 } else {
+
1153 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1154 msg, (unsigned long long) nodeid);
+
1155 }
+
1156 }
+
1157}
+
1158
+
1159static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1160{
+
1161 struct lock_queue_element **qp;
+
1162
+
1163 qe->done = false;
+
1164 pthread_cond_init(&qe->cond, NULL);
+
1165 qe->next = NULL;
+
1166 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1167 *qp = qe;
+
1168}
+
1169
+
1170static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1171{
+
1172 struct lock_queue_element **qp;
+
1173
+
1174 pthread_cond_destroy(&qe->cond);
+
1175 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1176 *qp = qe->next;
+
1177}
+
1178
+
1179static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1180{
+
1181 queue_path(f, qe);
+
1182
+
1183 do {
+
1184 pthread_cond_wait(&qe->cond, &f->lock);
+
1185 } while (!qe->done);
+
1186
+
1187 dequeue_path(f, qe);
+
1188
+
1189 return qe->err;
+
1190}
+
1191
+
1192static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1193 char **path, struct node **wnode)
+
1194{
+
1195 int err;
+
1196
+
1197 pthread_mutex_lock(&f->lock);
+
1198 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1199 if (err == -EAGAIN) {
+
1200 struct lock_queue_element qe = {
+
1201 .nodeid1 = nodeid,
+
1202 .name1 = name,
+
1203 .path1 = path,
+
1204 .wnode1 = wnode,
+
1205 };
+
1206 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1207 err = wait_path(f, &qe);
+
1208 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1209 }
+
1210 pthread_mutex_unlock(&f->lock);
+
1211
+
1212 return err;
+
1213}
+
1214
+
1215static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1216{
+
1217 return get_path_common(f, nodeid, NULL, path, NULL);
+
1218}
+
1219
+
1220static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1221{
+
1222 int err = 0;
+
1223
+
1224 if (f->conf.nullpath_ok) {
+
1225 *path = NULL;
+
1226 } else {
+
1227 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1228 if (err == -ESTALE)
+
1229 err = 0;
+
1230 }
+
1231
+
1232 return err;
+
1233}
+
1234
+
1235static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1236 char **path)
+
1237{
+
1238 return get_path_common(f, nodeid, name, path, NULL);
+
1239}
+
1240
+
1241static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1242 char **path, struct node **wnode)
+
1243{
+
1244 return get_path_common(f, nodeid, name, path, wnode);
+
1245}
+
1246
+
1247#if defined(__FreeBSD__)
+
1248#define CHECK_DIR_LOOP
+
1249#endif
+
1250
+
1251#if defined(CHECK_DIR_LOOP)
+
1252static int check_dir_loop(struct fuse *f,
+
1253 fuse_ino_t nodeid1, const char *name1,
+
1254 fuse_ino_t nodeid2, const char *name2)
+
1255{
+
1256 struct node *node, *node1, *node2;
+
1257 fuse_ino_t id1, id2;
+
1258
+
1259 node1 = lookup_node(f, nodeid1, name1);
+
1260 id1 = node1 ? node1->nodeid : nodeid1;
+
1261
+
1262 node2 = lookup_node(f, nodeid2, name2);
+
1263 id2 = node2 ? node2->nodeid : nodeid2;
+
1264
+
1265 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1266 node = node->parent) {
+
1267 if (node->name == NULL || node->parent == NULL)
+
1268 break;
+
1269
+
1270 if (node->nodeid != id2 && node->nodeid == id1)
+
1271 return -EINVAL;
+
1272 }
+
1273
+
1274 if (node2)
+
1275 {
+
1276 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1277 node = node->parent) {
+
1278 if (node->name == NULL || node->parent == NULL)
+
1279 break;
+
1280
+
1281 if (node->nodeid != id1 && node->nodeid == id2)
+
1282 return -ENOTEMPTY;
+
1283 }
+
1284 }
+
1285
+
1286 return 0;
+
1287}
+
1288#endif
+
1289
+
1290static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1291 fuse_ino_t nodeid2, const char *name2,
+
1292 char **path1, char **path2,
+
1293 struct node **wnode1, struct node **wnode2)
+
1294{
+
1295 int err;
+
1296
+
1297 pthread_mutex_lock(&f->lock);
+
1298
+
1299#if defined(CHECK_DIR_LOOP)
+
1300 if (name1)
+
1301 {
+
1302 // called during rename; perform dir loop check
+
1303 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1304 if (err)
+
1305 goto out_unlock;
+
1306 }
+
1307#endif
+
1308
+
1309 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1310 path1, path2, wnode1, wnode2);
+
1311 if (err == -EAGAIN) {
+
1312 struct lock_queue_element qe = {
+
1313 .nodeid1 = nodeid1,
+
1314 .name1 = name1,
+
1315 .path1 = path1,
+
1316 .wnode1 = wnode1,
+
1317 .nodeid2 = nodeid2,
+
1318 .name2 = name2,
+
1319 .path2 = path2,
+
1320 .wnode2 = wnode2,
+
1321 };
+
1322
+
1323 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1324 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1325 err = wait_path(f, &qe);
+
1326 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1327 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1328 }
+
1329
+
1330#if defined(CHECK_DIR_LOOP)
+
1331out_unlock:
+
1332#endif
+
1333 pthread_mutex_unlock(&f->lock);
+
1334
+
1335 return err;
+
1336}
+
1337
+
1338static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1339 struct node *wnode, char *path)
+
1340{
+
1341 pthread_mutex_lock(&f->lock);
+
1342 unlock_path(f, nodeid, wnode, NULL);
+
1343 if (f->lockq)
+
1344 wake_up_queued(f);
+
1345 pthread_mutex_unlock(&f->lock);
+
1346 free(path);
+
1347}
+
1348
+
1349static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1350{
+
1351 if (path)
+
1352 free_path_wrlock(f, nodeid, NULL, path);
+
1353}
+
1354
+
1355static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1356 struct node *wnode1, struct node *wnode2,
+
1357 char *path1, char *path2)
+
1358{
+
1359 pthread_mutex_lock(&f->lock);
+
1360 unlock_path(f, nodeid1, wnode1, NULL);
+
1361 unlock_path(f, nodeid2, wnode2, NULL);
+
1362 wake_up_queued(f);
+
1363 pthread_mutex_unlock(&f->lock);
+
1364 free(path1);
+
1365 free(path2);
+
1366}
+
1367
+
1368static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1369{
+
1370 struct node *node;
+
1371 if (nodeid == FUSE_ROOT_ID)
+
1372 return;
+
1373 pthread_mutex_lock(&f->lock);
+
1374 node = get_node(f, nodeid);
+
1375
+
1376 /*
+
1377 * Node may still be locked due to interrupt idiocy in open,
+
1378 * create and opendir
+
1379 */
+
1380 while (node->nlookup == nlookup && node->treelock) {
+
1381 struct lock_queue_element qe = {
+
1382 .nodeid1 = nodeid,
+
1383 };
+
1384
+
1385 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1386 queue_path(f, &qe);
+
1387
+
1388 do {
+
1389 pthread_cond_wait(&qe.cond, &f->lock);
+
1390 } while (node->nlookup == nlookup && node->treelock);
+
1391
+
1392 dequeue_path(f, &qe);
+
1393 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1394 }
+
1395
+
1396 assert(node->nlookup >= nlookup);
+
1397 node->nlookup -= nlookup;
+
1398 if (!node->nlookup) {
+
1399 unref_node(f, node);
+
1400 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1401 set_forget_time(f, node);
+
1402 }
+
1403 pthread_mutex_unlock(&f->lock);
+
1404}
+
1405
+
1406static void unlink_node(struct fuse *f, struct node *node)
+
1407{
+
1408 if (f->conf.remember) {
+
1409 assert(node->nlookup > 1);
+
1410 node->nlookup--;
+
1411 }
+
1412 unhash_name(f, node);
+
1413}
+
1414
+
1415static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1416{
+
1417 struct node *node;
+
1418
+
1419 pthread_mutex_lock(&f->lock);
+
1420 node = lookup_node(f, dir, name);
+
1421 if (node != NULL)
+
1422 unlink_node(f, node);
+
1423 pthread_mutex_unlock(&f->lock);
+
1424}
+
1425
+
1426static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1427 fuse_ino_t newdir, const char *newname, int hide)
+
1428{
+
1429 struct node *node;
+
1430 struct node *newnode;
+
1431 int err = 0;
+
1432
+
1433 pthread_mutex_lock(&f->lock);
+
1434 node = lookup_node(f, olddir, oldname);
+
1435 newnode = lookup_node(f, newdir, newname);
+
1436 if (node == NULL)
+
1437 goto out;
+
1438
+
1439 if (newnode != NULL) {
+
1440 if (hide) {
+
1441 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1442 err = -EBUSY;
+
1443 goto out;
+
1444 }
+
1445 unlink_node(f, newnode);
+
1446 }
+
1447
+
1448 unhash_name(f, node);
+
1449 if (hash_name(f, node, newdir, newname) == -1) {
+
1450 err = -ENOMEM;
+
1451 goto out;
+
1452 }
+
1453
+
1454 if (hide)
+
1455 node->is_hidden = 1;
+
1456
+
1457out:
+
1458 pthread_mutex_unlock(&f->lock);
+
1459 return err;
+
1460}
+
1461
+
1462static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1463 fuse_ino_t newdir, const char *newname)
+
1464{
+
1465 struct node *oldnode;
+
1466 struct node *newnode;
+
1467 int err;
+
1468
+
1469 pthread_mutex_lock(&f->lock);
+
1470 oldnode = lookup_node(f, olddir, oldname);
+
1471 newnode = lookup_node(f, newdir, newname);
+
1472
+
1473 if (oldnode)
+
1474 unhash_name(f, oldnode);
+
1475 if (newnode)
+
1476 unhash_name(f, newnode);
+
1477
+
1478 err = -ENOMEM;
+
1479 if (oldnode) {
+
1480 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1481 goto out;
+
1482 }
+
1483 if (newnode) {
+
1484 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1485 goto out;
+
1486 }
+
1487 err = 0;
+
1488out:
+
1489 pthread_mutex_unlock(&f->lock);
+
1490 return err;
+
1491}
+
1492
+
1493static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1494{
+
1495 if (!f->conf.use_ino)
+
1496 stbuf->st_ino = nodeid;
+
1497 if (f->conf.set_mode) {
+
1498 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1499 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1500 (0777 & ~f->conf.dmask);
+
1501 else if (f->conf.fmask)
+
1502 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1503 (0777 & ~f->conf.fmask);
+
1504 else
+
1505 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1506 (0777 & ~f->conf.umask);
+
1507 }
+
1508 if (f->conf.set_uid)
+
1509 stbuf->st_uid = f->conf.uid;
+
1510 if (f->conf.set_gid)
+
1511 stbuf->st_gid = f->conf.gid;
+
1512}
+
1513
+
1514static struct fuse *req_fuse(fuse_req_t req)
+
1515{
+
1516 return (struct fuse *) fuse_req_userdata(req);
+
1517}
+
1518
+
1519static void fuse_intr_sighandler(int sig)
+
1520{
+
1521 (void) sig;
+
1522 /* Nothing to do */
+
1523}
+
1524
+
1525struct fuse_intr_data {
+
1526 pthread_t id;
+
1527 pthread_cond_t cond;
+
1528 int finished;
+
1529};
+
1530
+
1531static void fuse_interrupt(fuse_req_t req, void *d_)
+
1532{
+
1533 struct fuse_intr_data *d = d_;
+
1534 struct fuse *f = req_fuse(req);
+
1535
+
1536 if (d->id == pthread_self())
+
1537 return;
+
1538
+
1539 pthread_mutex_lock(&f->lock);
+
1540 while (!d->finished) {
+
1541 struct timeval now;
+
1542 struct timespec timeout;
+
1543
+
1544 pthread_kill(d->id, f->conf.intr_signal);
+
1545 gettimeofday(&now, NULL);
+
1546 timeout.tv_sec = now.tv_sec + 1;
+
1547 timeout.tv_nsec = now.tv_usec * 1000;
+
1548 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1549 }
+
1550 pthread_mutex_unlock(&f->lock);
+
1551}
+
1552
+
1553static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1554 struct fuse_intr_data *d)
+
1555{
+
1556 pthread_mutex_lock(&f->lock);
+
1557 d->finished = 1;
+
1558 pthread_cond_broadcast(&d->cond);
+
1559 pthread_mutex_unlock(&f->lock);
+
1560 fuse_req_interrupt_func(req, NULL, NULL);
+
1561 pthread_cond_destroy(&d->cond);
+
1562}
+
1563
+
1564static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1565{
+
1566 d->id = pthread_self();
+
1567 pthread_cond_init(&d->cond, NULL);
+
1568 d->finished = 0;
+
1569 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1570}
+
1571
+
1572static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1573 struct fuse_intr_data *d)
+
1574{
+
1575 if (f->conf.intr)
+
1576 fuse_do_finish_interrupt(f, req, d);
+
1577}
+
1578
+
1579static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1580 struct fuse_intr_data *d)
+
1581{
+
1582 if (f->conf.intr)
+
1583 fuse_do_prepare_interrupt(req, d);
+
1584}
+
1585
+
1586static const char* file_info_string(struct fuse_file_info *fi,
+
1587 char* buf, size_t len)
+
1588{
+
1589 if(fi == NULL)
+
1590 return "NULL";
+
1591 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1592 return buf;
+
1593}
+
1594
+
1595int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1596 struct fuse_file_info *fi)
+
1597{
+
1598 fuse_get_context()->private_data = fs->user_data;
+
1599 if (fs->op.getattr) {
+
1600 if (fs->debug) {
+
1601 char buf[10];
+
1602 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1603 file_info_string(fi, buf, sizeof(buf)),
+
1604 path);
+
1605 }
+
1606 return fs->op.getattr(path, buf, fi);
+
1607 } else {
+
1608 return -ENOSYS;
+
1609 }
+
1610}
+
1611
+
1612int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1613 const char *newpath, unsigned int flags)
+
1614{
+
1615 fuse_get_context()->private_data = fs->user_data;
+
1616 if (fs->op.rename) {
+
1617 if (fs->debug)
+
1618 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1619 flags);
+
1620
+
1621 return fs->op.rename(oldpath, newpath, flags);
+
1622 } else {
+
1623 return -ENOSYS;
+
1624 }
+
1625}
+
1626
+
1627int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1628{
+
1629 fuse_get_context()->private_data = fs->user_data;
+
1630 if (fs->op.unlink) {
+
1631 if (fs->debug)
+
1632 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1633
+
1634 return fs->op.unlink(path);
+
1635 } else {
+
1636 return -ENOSYS;
+
1637 }
+
1638}
+
1639
+
1640int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1641{
+
1642 fuse_get_context()->private_data = fs->user_data;
+
1643 if (fs->op.rmdir) {
+
1644 if (fs->debug)
+
1645 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1646
+
1647 return fs->op.rmdir(path);
+
1648 } else {
+
1649 return -ENOSYS;
+
1650 }
+
1651}
+
1652
+
1653int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1654{
+
1655 fuse_get_context()->private_data = fs->user_data;
+
1656 if (fs->op.symlink) {
+
1657 if (fs->debug)
+
1658 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1659
+
1660 return fs->op.symlink(linkname, path);
+
1661 } else {
+
1662 return -ENOSYS;
+
1663 }
+
1664}
+
1665
+
1666int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1667{
+
1668 fuse_get_context()->private_data = fs->user_data;
+
1669 if (fs->op.link) {
+
1670 if (fs->debug)
+
1671 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1672
+
1673 return fs->op.link(oldpath, newpath);
+
1674 } else {
+
1675 return -ENOSYS;
+
1676 }
+
1677}
+
1678
+
1679int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1680 struct fuse_file_info *fi)
+
1681{
+
1682 fuse_get_context()->private_data = fs->user_data;
+
1683 if (fs->op.release) {
+
1684 if (fs->debug)
+
1685 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1686 fi->flush ? "+flush" : "",
+
1687 (unsigned long long) fi->fh, fi->flags);
+
1688
+
1689 return fs->op.release(path, fi);
+
1690 } else {
+
1691 return 0;
+
1692 }
+
1693}
+
1694
+
1695int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1696 struct fuse_file_info *fi)
+
1697{
+
1698 fuse_get_context()->private_data = fs->user_data;
+
1699 if (fs->op.opendir) {
+
1700 int err;
+
1701
+
1702 if (fs->debug)
+
1703 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1704 path);
+
1705
+
1706 err = fs->op.opendir(path, fi);
+
1707
+
1708 if (fs->debug && !err)
+
1709 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1710 (unsigned long long) fi->fh, fi->flags, path);
+
1711
+
1712 return err;
+
1713 } else {
+
1714 return 0;
+
1715 }
+
1716}
+
1717
+
1718int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1719 struct fuse_file_info *fi)
+
1720{
+
1721 fuse_get_context()->private_data = fs->user_data;
+
1722 if (fs->op.open) {
+
1723 int err;
+
1724
+
1725 if (fs->debug)
+
1726 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1727 path);
+
1728
+
1729 err = fs->op.open(path, fi);
+
1730
+
1731 if (fs->debug && !err)
+
1732 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1733 (unsigned long long) fi->fh, fi->flags, path);
+
1734
+
1735 return err;
+
1736 } else {
+
1737 return 0;
+
1738 }
+
1739}
+
1740
+
1741static void fuse_free_buf(struct fuse_bufvec *buf)
+
1742{
+
1743 if (buf != NULL) {
+
1744 size_t i;
+
1745
+
1746 for (i = 0; i < buf->count; i++)
+
1747 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1748 free(buf->buf[i].mem);
+
1749 free(buf);
+
1750 }
+
1751}
+
1752
+
1753int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1754 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1755 struct fuse_file_info *fi)
+
1756{
+
1757 fuse_get_context()->private_data = fs->user_data;
+
1758 if (fs->op.read || fs->op.read_buf) {
+
1759 int res;
+
1760
+
1761 if (fs->debug)
+
1762 fuse_log(FUSE_LOG_DEBUG,
+
1763 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1764 (unsigned long long) fi->fh,
+
1765 size, (unsigned long long) off, fi->flags);
+
1766
+
1767 if (fs->op.read_buf) {
+
1768 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1769 } else {
+
1770 struct fuse_bufvec *buf;
+
1771 void *mem;
+
1772
+
1773 buf = malloc(sizeof(struct fuse_bufvec));
+
1774 if (buf == NULL)
+
1775 return -ENOMEM;
+
1776
+
1777 mem = malloc(size);
+
1778 if (mem == NULL) {
+
1779 free(buf);
+
1780 return -ENOMEM;
+
1781 }
+
1782 *buf = FUSE_BUFVEC_INIT(size);
+
1783 buf->buf[0].mem = mem;
+
1784 *bufp = buf;
+
1785
+
1786 res = fs->op.read(path, mem, size, off, fi);
+
1787 if (res >= 0)
+
1788 buf->buf[0].size = res;
+
1789 }
+
1790
+
1791 if (fs->debug && res >= 0)
+
1792 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1793 (unsigned long long) fi->fh,
+
1794 fuse_buf_size(*bufp),
+
1795 (unsigned long long) off);
+
1796 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1797 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1798
+
1799 if (res < 0)
+
1800 return res;
+
1801
+
1802 return 0;
+
1803 } else {
+
1804 return -ENOSYS;
+
1805 }
+
1806}
+
1807
+
1808int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1809 off_t off, struct fuse_file_info *fi)
+
1810{
+
1811 fuse_get_context()->private_data = fs->user_data;
+
1812 if (fs->op.read || fs->op.read_buf) {
+
1813 int res;
+
1814
+
1815 if (fs->debug)
+
1816 fuse_log(FUSE_LOG_DEBUG,
+
1817 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1818 (unsigned long long) fi->fh,
+
1819 size, (unsigned long long) off, fi->flags);
+
1820
+
1821 if (fs->op.read_buf) {
+
1822 struct fuse_bufvec *buf = NULL;
+
1823
+
1824 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1825 if (res == 0) {
+
1826 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1827
+
1828 dst.buf[0].mem = mem;
+
1829 res = fuse_buf_copy(&dst, buf, 0);
+
1830 }
+
1831 fuse_free_buf(buf);
+
1832 } else {
+
1833 res = fs->op.read(path, mem, size, off, fi);
+
1834 }
+
1835
+
1836 if (fs->debug && res >= 0)
+
1837 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1838 (unsigned long long) fi->fh,
+
1839 res,
+
1840 (unsigned long long) off);
+
1841 if (res >= 0 && res > (int) size)
+
1842 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1843
+
1844 return res;
+
1845 } else {
+
1846 return -ENOSYS;
+
1847 }
+
1848}
+
1849
+
1850int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1851 struct fuse_bufvec *buf, off_t off,
+
1852 struct fuse_file_info *fi)
+
1853{
+
1854 fuse_get_context()->private_data = fs->user_data;
+
1855 if (fs->op.write_buf || fs->op.write) {
+
1856 int res;
+
1857 size_t size = fuse_buf_size(buf);
+
1858
+
1859 assert(buf->idx == 0 && buf->off == 0);
+
1860 if (fs->debug)
+
1861 fuse_log(FUSE_LOG_DEBUG,
+
1862 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1863 fi->writepage ? "page" : "",
+
1864 (unsigned long long) fi->fh,
+
1865 size,
+
1866 (unsigned long long) off,
+
1867 fi->flags);
+
1868
+
1869 if (fs->op.write_buf) {
+
1870 res = fs->op.write_buf(path, buf, off, fi);
+
1871 } else {
+
1872 void *mem = NULL;
+
1873 struct fuse_buf *flatbuf;
+
1874 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1875
+
1876 if (buf->count == 1 &&
+
1877 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1878 flatbuf = &buf->buf[0];
+
1879 } else {
+
1880 res = -ENOMEM;
+
1881 mem = malloc(size);
+
1882 if (mem == NULL)
+
1883 goto out;
+
1884
+
1885 tmp.buf[0].mem = mem;
+
1886 res = fuse_buf_copy(&tmp, buf, 0);
+
1887 if (res <= 0)
+
1888 goto out_free;
+
1889
+
1890 tmp.buf[0].size = res;
+
1891 flatbuf = &tmp.buf[0];
+
1892 }
+
1893
+
1894 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1895 off, fi);
+
1896out_free:
+
1897 free(mem);
+
1898 }
+
1899out:
+
1900 if (fs->debug && res >= 0)
+
1901 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1902 fi->writepage ? "page" : "",
+
1903 (unsigned long long) fi->fh, res,
+
1904 (unsigned long long) off);
+
1905 if (res > (int) size)
+
1906 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1907
+
1908 return res;
+
1909 } else {
+
1910 return -ENOSYS;
+
1911 }
+
1912}
+
1913
+
1914int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1915 size_t size, off_t off, struct fuse_file_info *fi)
+
1916{
+
1917 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1918
+
1919 bufv.buf[0].mem = (void *) mem;
+
1920
+
1921 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1922}
+
1923
+
1924int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1925 struct fuse_file_info *fi)
+
1926{
+
1927 fuse_get_context()->private_data = fs->user_data;
+
1928 if (fs->op.fsync) {
+
1929 if (fs->debug)
+
1930 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1931 (unsigned long long) fi->fh, datasync);
+
1932
+
1933 return fs->op.fsync(path, datasync, fi);
+
1934 } else {
+
1935 return -ENOSYS;
+
1936 }
+
1937}
+
1938
+
1939int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1940 struct fuse_file_info *fi)
+
1941{
+
1942 fuse_get_context()->private_data = fs->user_data;
+
1943 if (fs->op.fsyncdir) {
+
1944 if (fs->debug)
+
1945 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1946 (unsigned long long) fi->fh, datasync);
+
1947
+
1948 return fs->op.fsyncdir(path, datasync, fi);
+
1949 } else {
+
1950 return -ENOSYS;
+
1951 }
+
1952}
+
1953
+
1954int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1955 struct fuse_file_info *fi)
+
1956{
+
1957 fuse_get_context()->private_data = fs->user_data;
+
1958 if (fs->op.flush) {
+
1959 if (fs->debug)
+
1960 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1961 (unsigned long long) fi->fh);
+
1962
+
1963 return fs->op.flush(path, fi);
+
1964 } else {
+
1965 return -ENOSYS;
+
1966 }
+
1967}
+
1968
+
1969int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1970{
+
1971 fuse_get_context()->private_data = fs->user_data;
+
1972 if (fs->op.statfs) {
+
1973 if (fs->debug)
+
1974 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1975
+
1976 return fs->op.statfs(path, buf);
+
1977 } else {
+
1978 buf->f_namemax = 255;
+
1979 buf->f_bsize = 512;
+
1980 return 0;
+
1981 }
+
1982}
+
1983
+
1984int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1985 struct fuse_file_info *fi)
+
1986{
+
1987 fuse_get_context()->private_data = fs->user_data;
+
1988 if (fs->op.releasedir) {
+
1989 if (fs->debug)
+
1990 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1991 (unsigned long long) fi->fh, fi->flags);
+
1992
+
1993 return fs->op.releasedir(path, fi);
+
1994 } else {
+
1995 return 0;
+
1996 }
+
1997}
+
1998
+
1999int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
2000 fuse_fill_dir_t filler, off_t off,
+
2001 struct fuse_file_info *fi,
+
2002 enum fuse_readdir_flags flags)
+
2003{
+
2004 fuse_get_context()->private_data = fs->user_data;
+
2005 if (fs->op.readdir) {
+
2006 if (fs->debug) {
+
2007 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2008 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2009 (unsigned long long) fi->fh,
+
2010 (unsigned long long) off);
+
2011 }
+
2012
+
2013 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2014 } else {
+
2015 return -ENOSYS;
+
2016 }
+
2017}
+
2018
+
2019int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2020 struct fuse_file_info *fi)
+
2021{
+
2022 fuse_get_context()->private_data = fs->user_data;
+
2023 if (fs->op.create) {
+
2024 int err;
+
2025
+
2026 if (fs->debug)
+
2027 fuse_log(FUSE_LOG_DEBUG,
+
2028 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2029 fi->flags, path, mode,
+
2030 fuse_get_context()->umask);
+
2031
+
2032 err = fs->op.create(path, mode, fi);
+
2033
+
2034 if (fs->debug && !err)
+
2035 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2036 (unsigned long long) fi->fh, fi->flags, path);
+
2037
+
2038 return err;
+
2039 } else {
+
2040 return -ENOSYS;
+
2041 }
+
2042}
+
2043
+
2044int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2045 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2046{
+
2047 fuse_get_context()->private_data = fs->user_data;
+
2048 if (fs->op.lock) {
+
2049 if (fs->debug)
+
2050 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2051 (unsigned long long) fi->fh,
+
2052 (cmd == F_GETLK ? "F_GETLK" :
+
2053 (cmd == F_SETLK ? "F_SETLK" :
+
2054 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2055 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2056 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2057 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2058 "???"))),
+
2059 (unsigned long long) lock->l_start,
+
2060 (unsigned long long) lock->l_len,
+
2061 (unsigned long long) lock->l_pid);
+
2062
+
2063 return fs->op.lock(path, fi, cmd, lock);
+
2064 } else {
+
2065 return -ENOSYS;
+
2066 }
+
2067}
+
2068
+
2069int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2070 struct fuse_file_info *fi, int op)
+
2071{
+
2072 fuse_get_context()->private_data = fs->user_data;
+
2073 if (fs->op.flock) {
+
2074 if (fs->debug) {
+
2075 int xop = op & ~LOCK_NB;
+
2076
+
2077 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2078 (unsigned long long) fi->fh,
+
2079 xop == LOCK_SH ? "LOCK_SH" :
+
2080 (xop == LOCK_EX ? "LOCK_EX" :
+
2081 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2082 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2083 }
+
2084 return fs->op.flock(path, fi, op);
+
2085 } else {
+
2086 return -ENOSYS;
+
2087 }
+
2088}
+
2089
+
2090int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2091 gid_t gid, struct fuse_file_info *fi)
+
2092{
+
2093 fuse_get_context()->private_data = fs->user_data;
+
2094 if (fs->op.chown) {
+
2095 if (fs->debug) {
+
2096 char buf[10];
+
2097 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2098 file_info_string(fi, buf, sizeof(buf)),
+
2099 path, (unsigned long) uid, (unsigned long) gid);
+
2100 }
+
2101 return fs->op.chown(path, uid, gid, fi);
+
2102 } else {
+
2103 return -ENOSYS;
+
2104 }
+
2105}
+
2106
+
2107int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2108 struct fuse_file_info *fi)
+
2109{
+
2110 fuse_get_context()->private_data = fs->user_data;
+
2111 if (fs->op.truncate) {
+
2112 if (fs->debug) {
+
2113 char buf[10];
+
2114 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2115 file_info_string(fi, buf, sizeof(buf)),
+
2116 (unsigned long long) size);
+
2117 }
+
2118 return fs->op.truncate(path, size, fi);
+
2119 } else {
+
2120 return -ENOSYS;
+
2121 }
+
2122}
+
2123
+
2124int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2125 const struct timespec tv[2], struct fuse_file_info *fi)
+
2126{
+
2127 fuse_get_context()->private_data = fs->user_data;
+
2128 if (fs->op.utimens) {
+
2129 if (fs->debug) {
+
2130 char buf[10];
+
2131 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n",
+
2132 file_info_string(fi, buf, sizeof(buf)),
+
2133 path, tv[0].tv_sec, tv[0].tv_nsec,
+
2134 tv[1].tv_sec, tv[1].tv_nsec);
+
2135 }
+
2136 return fs->op.utimens(path, tv, fi);
+
2137 } else {
+
2138 return -ENOSYS;
+
2139 }
+
2140}
+
2141
+
2142int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2143{
+
2144 fuse_get_context()->private_data = fs->user_data;
+
2145 if (fs->op.access) {
+
2146 if (fs->debug)
+
2147 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2148
+
2149 return fs->op.access(path, mask);
+
2150 } else {
+
2151 return -ENOSYS;
+
2152 }
+
2153}
+
2154
+
2155int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2156 size_t len)
+
2157{
+
2158 fuse_get_context()->private_data = fs->user_data;
+
2159 if (fs->op.readlink) {
+
2160 if (fs->debug)
+
2161 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2162 (unsigned long) len);
+
2163
+
2164 return fs->op.readlink(path, buf, len);
+
2165 } else {
+
2166 return -ENOSYS;
+
2167 }
+
2168}
+
2169
+
2170int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2171 dev_t rdev)
+
2172{
+
2173 fuse_get_context()->private_data = fs->user_data;
+
2174 if (fs->op.mknod) {
+
2175 if (fs->debug)
+
2176 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2177 path, mode, (unsigned long long) rdev,
+
2178 fuse_get_context()->umask);
+
2179
+
2180 return fs->op.mknod(path, mode, rdev);
+
2181 } else {
+
2182 return -ENOSYS;
+
2183 }
+
2184}
+
2185
+
2186int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2187{
+
2188 fuse_get_context()->private_data = fs->user_data;
+
2189 if (fs->op.mkdir) {
+
2190 if (fs->debug)
+
2191 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2192 path, mode, fuse_get_context()->umask);
+
2193
+
2194 return fs->op.mkdir(path, mode);
+
2195 } else {
+
2196 return -ENOSYS;
+
2197 }
+
2198}
+
2199
+
2200int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2201 const char *value, size_t size, int flags)
+
2202{
+
2203 fuse_get_context()->private_data = fs->user_data;
+
2204 if (fs->op.setxattr) {
+
2205 if (fs->debug)
+
2206 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2207 path, name, (unsigned long) size, flags);
+
2208
+
2209 return fs->op.setxattr(path, name, value, size, flags);
+
2210 } else {
+
2211 return -ENOSYS;
+
2212 }
+
2213}
+
2214
+
2215int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2216 char *value, size_t size)
+
2217{
+
2218 fuse_get_context()->private_data = fs->user_data;
+
2219 if (fs->op.getxattr) {
+
2220 if (fs->debug)
+
2221 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2222 path, name, (unsigned long) size);
+
2223
+
2224 return fs->op.getxattr(path, name, value, size);
+
2225 } else {
+
2226 return -ENOSYS;
+
2227 }
+
2228}
+
2229
+
2230int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2231 size_t size)
+
2232{
+
2233 fuse_get_context()->private_data = fs->user_data;
+
2234 if (fs->op.listxattr) {
+
2235 if (fs->debug)
+
2236 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2237 path, (unsigned long) size);
+
2238
+
2239 return fs->op.listxattr(path, list, size);
+
2240 } else {
+
2241 return -ENOSYS;
+
2242 }
+
2243}
+
2244
+
2245int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2246 uint64_t *idx)
+
2247{
+
2248 fuse_get_context()->private_data = fs->user_data;
+
2249 if (fs->op.bmap) {
+
2250 if (fs->debug)
+
2251 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2252 path, (unsigned long) blocksize,
+
2253 (unsigned long long) *idx);
+
2254
+
2255 return fs->op.bmap(path, blocksize, idx);
+
2256 } else {
+
2257 return -ENOSYS;
+
2258 }
+
2259}
+
2260
+
2261int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2262{
+
2263 fuse_get_context()->private_data = fs->user_data;
+
2264 if (fs->op.removexattr) {
+
2265 if (fs->debug)
+
2266 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2267
+
2268 return fs->op.removexattr(path, name);
+
2269 } else {
+
2270 return -ENOSYS;
+
2271 }
+
2272}
+
2273
+
2274int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2275 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2276 void *data)
+
2277{
+
2278 fuse_get_context()->private_data = fs->user_data;
+
2279 if (fs->op.ioctl) {
+
2280 if (fs->debug)
+
2281 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2282 (unsigned long long) fi->fh, cmd, flags);
+
2283
+
2284 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2285 } else
+
2286 return -ENOSYS;
+
2287}
+
2288
+
2289int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2290 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2291 unsigned *reventsp)
+
2292{
+
2293 fuse_get_context()->private_data = fs->user_data;
+
2294 if (fs->op.poll) {
+
2295 int res;
+
2296
+
2297 if (fs->debug)
+
2298 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2299 (unsigned long long) fi->fh, ph,
+
2300 fi->poll_events);
+
2301
+
2302 res = fs->op.poll(path, fi, ph, reventsp);
+
2303
+
2304 if (fs->debug && !res)
+
2305 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2306 (unsigned long long) fi->fh, *reventsp);
+
2307
+
2308 return res;
+
2309 } else
+
2310 return -ENOSYS;
+
2311}
+
2312
+
2313int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2314 off_t offset, off_t length, struct fuse_file_info *fi)
+
2315{
+
2316 fuse_get_context()->private_data = fs->user_data;
+
2317 if (fs->op.fallocate) {
+
2318 if (fs->debug)
+
2319 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2320 path,
+
2321 mode,
+
2322 (unsigned long long) offset,
+
2323 (unsigned long long) length);
+
2324
+
2325 return fs->op.fallocate(path, mode, offset, length, fi);
+
2326 } else
+
2327 return -ENOSYS;
+
2328}
+
2329
+
2330ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2331 struct fuse_file_info *fi_in, off_t off_in,
+
2332 const char *path_out,
+
2333 struct fuse_file_info *fi_out, off_t off_out,
+
2334 size_t len, int flags)
+
2335{
+
2336 fuse_get_context()->private_data = fs->user_data;
+
2337 if (fs->op.copy_file_range) {
+
2338 if (fs->debug)
+
2339 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2340 "%s:%llu, length: %llu\n",
+
2341 path_in,
+
2342 (unsigned long long) off_in,
+
2343 path_out,
+
2344 (unsigned long long) off_out,
+
2345 (unsigned long long) len);
+
2346
+
2347 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2348 fi_out, off_out, len, flags);
+
2349 } else
+
2350 return -ENOSYS;
+
2351}
+
2352
+
2353off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2354 struct fuse_file_info *fi)
+
2355{
+
2356 fuse_get_context()->private_data = fs->user_data;
+
2357 if (fs->op.lseek) {
+
2358 if (fs->debug) {
+
2359 char buf[10];
+
2360 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2361 file_info_string(fi, buf, sizeof(buf)),
+
2362 (unsigned long long) off, whence);
+
2363 }
+
2364 return fs->op.lseek(path, off, whence, fi);
+
2365 } else {
+
2366 return -ENOSYS;
+
2367 }
+
2368}
+
2369
+
2370static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2371{
+
2372 struct node *node;
+
2373 int isopen = 0;
+
2374 pthread_mutex_lock(&f->lock);
+
2375 node = lookup_node(f, dir, name);
+
2376 if (node && node->open_count > 0)
+
2377 isopen = 1;
+
2378 pthread_mutex_unlock(&f->lock);
+
2379 return isopen;
+
2380}
+
2381
+
2382static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2383 char *newname, size_t bufsize)
+
2384{
+
2385 struct stat buf;
+
2386 struct node *node;
+
2387 struct node *newnode;
+
2388 char *newpath;
+
2389 int res;
+
2390 int failctr = 10;
+
2391
+
2392 do {
+
2393 pthread_mutex_lock(&f->lock);
+
2394 node = lookup_node(f, dir, oldname);
+
2395 if (node == NULL) {
+
2396 pthread_mutex_unlock(&f->lock);
+
2397 return NULL;
+
2398 }
+
2399 do {
+
2400 f->hidectr ++;
+
2401 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2402 (unsigned int) node->nodeid, f->hidectr);
+
2403 newnode = lookup_node(f, dir, newname);
+
2404 } while(newnode);
+
2405
+
2406 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2407 pthread_mutex_unlock(&f->lock);
+
2408 if (res)
+
2409 break;
+
2410
+
2411 memset(&buf, 0, sizeof(buf));
+
2412 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2413 if (res == -ENOENT)
+
2414 break;
+
2415 free(newpath);
+
2416 newpath = NULL;
+
2417 } while(res == 0 && --failctr);
+
2418
+
2419 return newpath;
+
2420}
+
2421
+
2422static int hide_node(struct fuse *f, const char *oldpath,
+
2423 fuse_ino_t dir, const char *oldname)
+
2424{
+
2425 char newname[64];
+
2426 char *newpath;
+
2427 int err = -EBUSY;
+
2428
+
2429 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2430 if (newpath) {
+
2431 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2432 if (!err)
+
2433 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2434 free(newpath);
+
2435 }
+
2436 return err;
+
2437}
+
2438
+
2439static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2440{
+
2441 return stbuf->st_mtime == ts->tv_sec &&
+
2442 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2443}
+
2444
+
2445#ifndef CLOCK_MONOTONIC
+
2446#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2447#endif
+
2448
+
2449static void curr_time(struct timespec *now)
+
2450{
+
2451 static clockid_t clockid = CLOCK_MONOTONIC;
+
2452 int res = clock_gettime(clockid, now);
+
2453 if (res == -1 && errno == EINVAL) {
+
2454 clockid = CLOCK_REALTIME;
+
2455 res = clock_gettime(clockid, now);
+
2456 }
+
2457 if (res == -1) {
+
2458 perror("fuse: clock_gettime");
+
2459 abort();
+
2460 }
+
2461}
+
2462
+
2463static void update_stat(struct node *node, const struct stat *stbuf)
+
2464{
+
2465 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2466 stbuf->st_size != node->size))
+
2467 node->cache_valid = 0;
+
2468 node->mtime.tv_sec = stbuf->st_mtime;
+
2469 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2470 node->size = stbuf->st_size;
+
2471 curr_time(&node->stat_updated);
+
2472}
+
2473
+
2474static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2475 struct fuse_entry_param *e)
+
2476{
+
2477 struct node *node;
+
2478
+
2479 node = find_node(f, nodeid, name);
+
2480 if (node == NULL)
+
2481 return -ENOMEM;
+
2482
+
2483 e->ino = node->nodeid;
+
2484 e->generation = node->generation;
+
2485 e->entry_timeout = f->conf.entry_timeout;
+
2486 e->attr_timeout = f->conf.attr_timeout;
+
2487 if (f->conf.auto_cache) {
+
2488 pthread_mutex_lock(&f->lock);
+
2489 update_stat(node, &e->attr);
+
2490 pthread_mutex_unlock(&f->lock);
+
2491 }
+
2492 set_stat(f, e->ino, &e->attr);
+
2493 return 0;
+
2494}
+
2495
+
2496static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2497 const char *name, const char *path,
+
2498 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2499{
+
2500 int res;
+
2501
+
2502 memset(e, 0, sizeof(struct fuse_entry_param));
+
2503 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2504 if (res == 0) {
+
2505 res = do_lookup(f, nodeid, name, e);
+
2506 if (res == 0 && f->conf.debug) {
+
2507 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2508 (unsigned long long) e->ino);
+
2509 }
+
2510 }
+
2511 return res;
+
2512}
+
2513
+
2514static struct fuse_context_i *fuse_get_context_internal(void)
+
2515{
+
2516 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2517}
+
2518
+
2519static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2520{
+
2521 struct fuse_context_i *c = fuse_get_context_internal();
+
2522 if (c == NULL) {
+
2523 c = (struct fuse_context_i *)
+
2524 calloc(1, sizeof(struct fuse_context_i));
+
2525 if (c == NULL) {
+
2526 /* This is hard to deal with properly, so just
+
2527 abort. If memory is so low that the
+
2528 context cannot be allocated, there's not
+
2529 much hope for the filesystem anyway */
+
2530 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2531 abort();
+
2532 }
+
2533 pthread_setspecific(fuse_context_key, c);
+
2534 } else {
+
2535 memset(c, 0, sizeof(*c));
+
2536 }
+
2537 c->ctx.fuse = f;
+
2538
+
2539 return c;
+
2540}
+
2541
+
2542static void fuse_freecontext(void *data)
+
2543{
+
2544 free(data);
+
2545}
+
2546
+
2547static int fuse_create_context_key(void)
+
2548{
+
2549 int err = 0;
+
2550 pthread_mutex_lock(&fuse_context_lock);
+
2551 if (!fuse_context_ref) {
+
2552 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2553 if (err) {
+
2554 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2555 strerror(err));
+
2556 pthread_mutex_unlock(&fuse_context_lock);
+
2557 return -1;
+
2558 }
+
2559 }
+
2560 fuse_context_ref++;
+
2561 pthread_mutex_unlock(&fuse_context_lock);
+
2562 return 0;
+
2563}
+
2564
+
2565static void fuse_delete_context_key(void)
+
2566{
+
2567 pthread_mutex_lock(&fuse_context_lock);
+
2568 fuse_context_ref--;
+
2569 if (!fuse_context_ref) {
+
2570 free(pthread_getspecific(fuse_context_key));
+
2571 pthread_key_delete(fuse_context_key);
+
2572 }
+
2573 pthread_mutex_unlock(&fuse_context_lock);
+
2574}
+
2575
+
2576static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2577{
+
2578 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2579 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2580 c->req = req;
+
2581 c->ctx.uid = ctx->uid;
+
2582 c->ctx.gid = ctx->gid;
+
2583 c->ctx.pid = ctx->pid;
+
2584 c->ctx.umask = ctx->umask;
+
2585 return c->ctx.fuse;
+
2586}
+
2587
+
2588static inline void reply_err(fuse_req_t req, int err)
+
2589{
+
2590 /* fuse_reply_err() uses non-negated errno values */
+
2591 fuse_reply_err(req, -err);
+
2592}
+
2593
+
2594static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2595 int err)
+
2596{
+
2597 if (!err) {
+
2598 struct fuse *f = req_fuse(req);
+
2599 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2600 /* Skip forget for negative result */
+
2601 if (e->ino != 0)
+
2602 forget_node(f, e->ino, 1);
+
2603 }
+
2604 } else
+
2605 reply_err(req, err);
+
2606}
+
2607
+
2608void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2609 struct fuse_config *cfg)
+
2610{
+
2611 fuse_get_context()->private_data = fs->user_data;
+
2612 if (!fs->op.write_buf)
+
2613 fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ);
+
2614 if (!fs->op.lock)
+
2615 fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS);
+
2616 if (!fs->op.flock)
+
2617 fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
2618 if (fs->op.init) {
+
2619 uint64_t want_ext_default = conn->want_ext;
+
2620 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
+
2621 int rc;
+
2622
+
2623 conn->want = want_default;
+
2624 fs->user_data = fs->op.init(conn, cfg);
+
2625
+
2626 rc = convert_to_conn_want_ext(conn, want_ext_default,
+
2627 want_default);
+
2628
+
2629 if (rc != 0) {
+
2630 /*
+
2631 * This is a grave developer error, but
+
2632 * we cannot return an error here, as the function
+
2633 * signature does not allow it.
+
2634 */
+
2635 fuse_log(
+
2636 FUSE_LOG_ERR,
+
2637 "fuse: Aborting due to invalid conn want flags.\n");
+
2638 _exit(EXIT_FAILURE);
+
2639 }
+
2640 }
+
2641}
+
2642
+
2643static int fuse_init_intr_signal(int signum, int *installed);
+
2644
+
2645static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2646{
+
2647 struct fuse *f = (struct fuse *) data;
+
2648
+
2649 fuse_create_context(f);
+
2650 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
+
2651 fuse_fs_init(f->fs, conn, &f->conf);
+
2652
+
2653 if (f->conf.intr) {
+
2654 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2655 &f->intr_installed) == -1)
+
2656 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2657 } else {
+
2658 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2659 conn->no_interrupt = 1;
+
2660 }
+
2661}
+
2662
+
2663void fuse_fs_destroy(struct fuse_fs *fs)
+
2664{
+
2665 fuse_get_context()->private_data = fs->user_data;
+
2666 if (fs->op.destroy)
+
2667 fs->op.destroy(fs->user_data);
+
2668}
+
2669
+
2670static void fuse_lib_destroy(void *data)
+
2671{
+
2672 struct fuse *f = (struct fuse *) data;
+
2673
+
2674 fuse_create_context(f);
+
2675 fuse_fs_destroy(f->fs);
+
2676}
+
2677
+
2678static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2679 const char *name)
+
2680{
+
2681 struct fuse *f = req_fuse_prepare(req);
+
2682 struct fuse_entry_param e;
+
2683 char *path;
+
2684 int err;
+
2685 struct node *dot = NULL;
+
2686
+
2687 if (name[0] == '.') {
+
2688 int len = strlen(name);
+
2689
+
2690 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2691 pthread_mutex_lock(&f->lock);
+
2692 if (len == 1) {
+
2693 if (f->conf.debug)
+
2694 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2695 dot = get_node_nocheck(f, parent);
+
2696 if (dot == NULL) {
+
2697 pthread_mutex_unlock(&f->lock);
+
2698 reply_entry(req, &e, -ESTALE);
+
2699 return;
+
2700 }
+
2701 dot->refctr++;
+
2702 } else {
+
2703 if (f->conf.debug)
+
2704 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2705 parent = get_node(f, parent)->parent->nodeid;
+
2706 }
+
2707 pthread_mutex_unlock(&f->lock);
+
2708 name = NULL;
+
2709 }
+
2710 }
+
2711
+
2712 err = get_path_name(f, parent, name, &path);
+
2713 if (!err) {
+
2714 struct fuse_intr_data d;
+
2715 if (f->conf.debug)
+
2716 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2717 fuse_prepare_interrupt(f, req, &d);
+
2718 err = lookup_path(f, parent, name, path, &e, NULL);
+
2719 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2720 e.ino = 0;
+
2721 e.entry_timeout = f->conf.negative_timeout;
+
2722 err = 0;
+
2723 }
+
2724 fuse_finish_interrupt(f, req, &d);
+
2725 free_path(f, parent, path);
+
2726 }
+
2727 if (dot) {
+
2728 pthread_mutex_lock(&f->lock);
+
2729 unref_node(f, dot);
+
2730 pthread_mutex_unlock(&f->lock);
+
2731 }
+
2732 reply_entry(req, &e, err);
+
2733}
+
2734
+
2735static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2736{
+
2737 if (f->conf.debug)
+
2738 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2739 (unsigned long long) nlookup);
+
2740 forget_node(f, ino, nlookup);
+
2741}
+
2742
+
2743static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2744{
+
2745 do_forget(req_fuse(req), ino, nlookup);
+
2746 fuse_reply_none(req);
+
2747}
+
2748
+
2749static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2750 struct fuse_forget_data *forgets)
+
2751{
+
2752 struct fuse *f = req_fuse(req);
+
2753 size_t i;
+
2754
+
2755 for (i = 0; i < count; i++)
+
2756 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2757
+
2758 fuse_reply_none(req);
+
2759}
+
2760
+
2761
+
2762static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2763 struct fuse_file_info *fi)
+
2764{
+
2765 struct fuse *f = req_fuse_prepare(req);
+
2766 struct stat buf;
+
2767 char *path;
+
2768 int err;
+
2769
+
2770 memset(&buf, 0, sizeof(buf));
+
2771
+
2772 if (fi != NULL)
+
2773 err = get_path_nullok(f, ino, &path);
+
2774 else
+
2775 err = get_path(f, ino, &path);
+
2776 if (!err) {
+
2777 struct fuse_intr_data d;
+
2778 fuse_prepare_interrupt(f, req, &d);
+
2779 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2780 fuse_finish_interrupt(f, req, &d);
+
2781 free_path(f, ino, path);
+
2782 }
+
2783 if (!err) {
+
2784 struct node *node;
+
2785
+
2786 pthread_mutex_lock(&f->lock);
+
2787 node = get_node(f, ino);
+
2788 if (node->is_hidden && buf.st_nlink > 0)
+
2789 buf.st_nlink--;
+
2790 if (f->conf.auto_cache)
+
2791 update_stat(node, &buf);
+
2792 pthread_mutex_unlock(&f->lock);
+
2793 set_stat(f, ino, &buf);
+
2794 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2795 } else
+
2796 reply_err(req, err);
+
2797}
+
2798
+
2799int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2800 struct fuse_file_info *fi)
+
2801{
+
2802 fuse_get_context()->private_data = fs->user_data;
+
2803 if (fs->op.chmod) {
+
2804 if (fs->debug) {
+
2805 char buf[10];
+
2806 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2807 file_info_string(fi, buf, sizeof(buf)),
+
2808 path, (unsigned long long) mode);
+
2809 }
+
2810 return fs->op.chmod(path, mode, fi);
+
2811 }
+
2812 else
+
2813 return -ENOSYS;
+
2814}
+
2815
+
2816static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2817 int valid, struct fuse_file_info *fi)
+
2818{
+
2819 struct fuse *f = req_fuse_prepare(req);
+
2820 struct stat buf;
+
2821 char *path;
+
2822 int err;
+
2823
+
2824 memset(&buf, 0, sizeof(buf));
+
2825 if (fi != NULL)
+
2826 err = get_path_nullok(f, ino, &path);
+
2827 else
+
2828 err = get_path(f, ino, &path);
+
2829 if (!err) {
+
2830 struct fuse_intr_data d;
+
2831 fuse_prepare_interrupt(f, req, &d);
+
2832 err = 0;
+
2833 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2834 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2835 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2836 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2837 attr->st_uid : (uid_t) -1;
+
2838 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2839 attr->st_gid : (gid_t) -1;
+
2840 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2841 }
+
2842 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2843 err = fuse_fs_truncate(f->fs, path,
+
2844 attr->st_size, fi);
+
2845 }
+
2846#ifdef HAVE_UTIMENSAT
+
2847 if (!err &&
+
2848 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2849 struct timespec tv[2];
+
2850
+
2851 tv[0].tv_sec = 0;
+
2852 tv[1].tv_sec = 0;
+
2853 tv[0].tv_nsec = UTIME_OMIT;
+
2854 tv[1].tv_nsec = UTIME_OMIT;
+
2855
+
2856 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2857 tv[0].tv_nsec = UTIME_NOW;
+
2858 else if (valid & FUSE_SET_ATTR_ATIME)
+
2859 tv[0] = attr->st_atim;
+
2860
+
2861 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2862 tv[1].tv_nsec = UTIME_NOW;
+
2863 else if (valid & FUSE_SET_ATTR_MTIME)
+
2864 tv[1] = attr->st_mtim;
+
2865
+
2866 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2867 } else
+
2868#endif
+
2869 if (!err &&
+
2870 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2871 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2872 struct timespec tv[2];
+
2873 tv[0].tv_sec = attr->st_atime;
+
2874 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2875 tv[1].tv_sec = attr->st_mtime;
+
2876 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2877 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2878 }
+
2879 if (!err) {
+
2880 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2881 }
+
2882 fuse_finish_interrupt(f, req, &d);
+
2883 free_path(f, ino, path);
+
2884 }
+
2885 if (!err) {
+
2886 if (f->conf.auto_cache) {
+
2887 pthread_mutex_lock(&f->lock);
+
2888 update_stat(get_node(f, ino), &buf);
+
2889 pthread_mutex_unlock(&f->lock);
+
2890 }
+
2891 set_stat(f, ino, &buf);
+
2892 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2893 } else
+
2894 reply_err(req, err);
+
2895}
+
2896
+
2897static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2898{
+
2899 struct fuse *f = req_fuse_prepare(req);
+
2900 char *path;
+
2901 int err;
+
2902
+
2903 err = get_path(f, ino, &path);
+
2904 if (!err) {
+
2905 struct fuse_intr_data d;
+
2906
+
2907 fuse_prepare_interrupt(f, req, &d);
+
2908 err = fuse_fs_access(f->fs, path, mask);
+
2909 fuse_finish_interrupt(f, req, &d);
+
2910 free_path(f, ino, path);
+
2911 }
+
2912 reply_err(req, err);
+
2913}
+
2914
+
2915static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2916{
+
2917 struct fuse *f = req_fuse_prepare(req);
+
2918 char linkname[PATH_MAX + 1];
+
2919 char *path;
+
2920 int err;
+
2921
+
2922 err = get_path(f, ino, &path);
+
2923 if (!err) {
+
2924 struct fuse_intr_data d;
+
2925 fuse_prepare_interrupt(f, req, &d);
+
2926 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2927 fuse_finish_interrupt(f, req, &d);
+
2928 free_path(f, ino, path);
+
2929 }
+
2930 if (!err) {
+
2931 linkname[PATH_MAX] = '\0';
+
2932 fuse_reply_readlink(req, linkname);
+
2933 } else
+
2934 reply_err(req, err);
+
2935}
+
2936
+
2937static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2938 mode_t mode, dev_t rdev)
+
2939{
+
2940 struct fuse *f = req_fuse_prepare(req);
+
2941 struct fuse_entry_param e;
+
2942 char *path;
+
2943 int err;
+
2944
+
2945 err = get_path_name(f, parent, name, &path);
+
2946 if (!err) {
+
2947 struct fuse_intr_data d;
+
2948
+
2949 fuse_prepare_interrupt(f, req, &d);
+
2950 err = -ENOSYS;
+
2951 if (S_ISREG(mode)) {
+
2952 struct fuse_file_info fi;
+
2953
+
2954 memset(&fi, 0, sizeof(fi));
+
2955 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2956 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2957 if (!err) {
+
2958 err = lookup_path(f, parent, name, path, &e,
+
2959 &fi);
+
2960 fuse_fs_release(f->fs, path, &fi);
+
2961 }
+
2962 }
+
2963 if (err == -ENOSYS) {
+
2964 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2965 if (!err)
+
2966 err = lookup_path(f, parent, name, path, &e,
+
2967 NULL);
+
2968 }
+
2969 fuse_finish_interrupt(f, req, &d);
+
2970 free_path(f, parent, path);
+
2971 }
+
2972 reply_entry(req, &e, err);
+
2973}
+
2974
+
2975static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2976 mode_t mode)
+
2977{
+
2978 struct fuse *f = req_fuse_prepare(req);
+
2979 struct fuse_entry_param e;
+
2980 char *path;
+
2981 int err;
+
2982
+
2983 err = get_path_name(f, parent, name, &path);
+
2984 if (!err) {
+
2985 struct fuse_intr_data d;
+
2986
+
2987 fuse_prepare_interrupt(f, req, &d);
+
2988 err = fuse_fs_mkdir(f->fs, path, mode);
+
2989 if (!err)
+
2990 err = lookup_path(f, parent, name, path, &e, NULL);
+
2991 fuse_finish_interrupt(f, req, &d);
+
2992 free_path(f, parent, path);
+
2993 }
+
2994 reply_entry(req, &e, err);
+
2995}
+
2996
+
2997static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2998 const char *name)
+
2999{
+
3000 struct fuse *f = req_fuse_prepare(req);
+
3001 struct node *wnode;
+
3002 char *path;
+
3003 int err;
+
3004
+
3005 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3006 if (!err) {
+
3007 struct fuse_intr_data d;
+
3008
+
3009 fuse_prepare_interrupt(f, req, &d);
+
3010 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
3011 err = hide_node(f, path, parent, name);
+
3012 if (!err) {
+
3013 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
3014 if (!is_open(f, parent, wnode->name)) {
+
3015 char *unlinkpath;
+
3016
+
3017 /* get the hidden file path, to unlink it */
+
3018 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
3019 err = fuse_fs_unlink(f->fs, unlinkpath);
+
3020 if (!err)
+
3021 remove_node(f, parent, wnode->name);
+
3022 free(unlinkpath);
+
3023 }
+
3024 }
+
3025 }
+
3026 } else {
+
3027 err = fuse_fs_unlink(f->fs, path);
+
3028 if (!err)
+
3029 remove_node(f, parent, name);
+
3030 }
+
3031 fuse_finish_interrupt(f, req, &d);
+
3032 free_path_wrlock(f, parent, wnode, path);
+
3033 }
+
3034 reply_err(req, err);
+
3035}
+
3036
+
3037static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3038{
+
3039 struct fuse *f = req_fuse_prepare(req);
+
3040 struct node *wnode;
+
3041 char *path;
+
3042 int err;
+
3043
+
3044 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3045 if (!err) {
+
3046 struct fuse_intr_data d;
+
3047
+
3048 fuse_prepare_interrupt(f, req, &d);
+
3049 err = fuse_fs_rmdir(f->fs, path);
+
3050 fuse_finish_interrupt(f, req, &d);
+
3051 if (!err)
+
3052 remove_node(f, parent, name);
+
3053 free_path_wrlock(f, parent, wnode, path);
+
3054 }
+
3055 reply_err(req, err);
+
3056}
+
3057
+
3058static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3059 fuse_ino_t parent, const char *name)
+
3060{
+
3061 struct fuse *f = req_fuse_prepare(req);
+
3062 struct fuse_entry_param e;
+
3063 char *path;
+
3064 int err;
+
3065
+
3066 err = get_path_name(f, parent, name, &path);
+
3067 if (!err) {
+
3068 struct fuse_intr_data d;
+
3069
+
3070 fuse_prepare_interrupt(f, req, &d);
+
3071 err = fuse_fs_symlink(f->fs, linkname, path);
+
3072 if (!err)
+
3073 err = lookup_path(f, parent, name, path, &e, NULL);
+
3074 fuse_finish_interrupt(f, req, &d);
+
3075 free_path(f, parent, path);
+
3076 }
+
3077 reply_entry(req, &e, err);
+
3078}
+
3079
+
3080static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3081 const char *oldname, fuse_ino_t newdir,
+
3082 const char *newname, unsigned int flags)
+
3083{
+
3084 struct fuse *f = req_fuse_prepare(req);
+
3085 char *oldpath;
+
3086 char *newpath;
+
3087 struct node *wnode1;
+
3088 struct node *wnode2;
+
3089 int err;
+
3090
+
3091 err = get_path2(f, olddir, oldname, newdir, newname,
+
3092 &oldpath, &newpath, &wnode1, &wnode2);
+
3093 if (!err) {
+
3094 struct fuse_intr_data d;
+
3095 err = 0;
+
3096 fuse_prepare_interrupt(f, req, &d);
+
3097 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3098 is_open(f, newdir, newname))
+
3099 err = hide_node(f, newpath, newdir, newname);
+
3100 if (!err) {
+
3101 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3102 if (!err) {
+
3103 if (flags & RENAME_EXCHANGE) {
+
3104 err = exchange_node(f, olddir, oldname,
+
3105 newdir, newname);
+
3106 } else {
+
3107 err = rename_node(f, olddir, oldname,
+
3108 newdir, newname, 0);
+
3109 }
+
3110 }
+
3111 }
+
3112 fuse_finish_interrupt(f, req, &d);
+
3113 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3114 }
+
3115 reply_err(req, err);
+
3116}
+
3117
+
3118static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3119 const char *newname)
+
3120{
+
3121 struct fuse *f = req_fuse_prepare(req);
+
3122 struct fuse_entry_param e;
+
3123 char *oldpath;
+
3124 char *newpath;
+
3125 int err;
+
3126
+
3127 err = get_path2(f, ino, NULL, newparent, newname,
+
3128 &oldpath, &newpath, NULL, NULL);
+
3129 if (!err) {
+
3130 struct fuse_intr_data d;
+
3131
+
3132 fuse_prepare_interrupt(f, req, &d);
+
3133 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3134 if (!err)
+
3135 err = lookup_path(f, newparent, newname, newpath,
+
3136 &e, NULL);
+
3137 fuse_finish_interrupt(f, req, &d);
+
3138 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3139 }
+
3140 reply_entry(req, &e, err);
+
3141}
+
3142
+
3143static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3144 struct fuse_file_info *fi)
+
3145{
+
3146 struct node *node;
+
3147 int unlink_hidden = 0;
+
3148
+
3149 fuse_fs_release(f->fs, path, fi);
+
3150
+
3151 pthread_mutex_lock(&f->lock);
+
3152 node = get_node(f, ino);
+
3153 assert(node->open_count > 0);
+
3154 --node->open_count;
+
3155 if (node->is_hidden && !node->open_count) {
+
3156 unlink_hidden = 1;
+
3157 node->is_hidden = 0;
+
3158 }
+
3159 pthread_mutex_unlock(&f->lock);
+
3160
+
3161 if(unlink_hidden) {
+
3162 if (path) {
+
3163 fuse_fs_unlink(f->fs, path);
+
3164 } else if (f->conf.nullpath_ok) {
+
3165 char *unlinkpath;
+
3166
+
3167 if (get_path(f, ino, &unlinkpath) == 0)
+
3168 fuse_fs_unlink(f->fs, unlinkpath);
+
3169
+
3170 free_path(f, ino, unlinkpath);
+
3171 }
+
3172 }
+
3173}
+
3174
+
3175static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3176 const char *name, mode_t mode,
+
3177 struct fuse_file_info *fi)
+
3178{
+
3179 struct fuse *f = req_fuse_prepare(req);
+
3180 struct fuse_intr_data d;
+
3181 struct fuse_entry_param e;
+
3182 char *path;
+
3183 int err;
+
3184
+
3185 err = get_path_name(f, parent, name, &path);
+
3186 if (!err) {
+
3187 fuse_prepare_interrupt(f, req, &d);
+
3188 err = fuse_fs_create(f->fs, path, mode, fi);
+
3189 if (!err) {
+
3190 err = lookup_path(f, parent, name, path, &e, fi);
+
3191 if (err)
+
3192 fuse_fs_release(f->fs, path, fi);
+
3193 else if (!S_ISREG(e.attr.st_mode)) {
+
3194 err = -EIO;
+
3195 fuse_fs_release(f->fs, path, fi);
+
3196 forget_node(f, e.ino, 1);
+
3197 } else {
+
3198 if (f->conf.direct_io)
+
3199 fi->direct_io = 1;
+
3200 if (f->conf.kernel_cache)
+
3201 fi->keep_cache = 1;
+
3202 if (fi->direct_io &&
+
3203 f->conf.parallel_direct_writes)
+
3204 fi->parallel_direct_writes = 1;
+
3205 }
+
3206 }
+
3207 fuse_finish_interrupt(f, req, &d);
+
3208 }
+
3209 if (!err) {
+
3210 pthread_mutex_lock(&f->lock);
+
3211 get_node(f, e.ino)->open_count++;
+
3212 pthread_mutex_unlock(&f->lock);
+
3213 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3214 /* The open syscall was interrupted, so it
+
3215 must be cancelled */
+
3216 fuse_do_release(f, e.ino, path, fi);
+
3217 forget_node(f, e.ino, 1);
+
3218 }
+
3219 } else {
+
3220 reply_err(req, err);
+
3221 }
+
3222
+
3223 free_path(f, parent, path);
+
3224}
+
3225
+
3226static double diff_timespec(const struct timespec *t1,
+
3227 const struct timespec *t2)
+
3228{
+
3229 return (t1->tv_sec - t2->tv_sec) +
+
3230 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3231}
+
3232
+
3233static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3234 struct fuse_file_info *fi)
+
3235{
+
3236 struct node *node;
+
3237
+
3238 pthread_mutex_lock(&f->lock);
+
3239 node = get_node(f, ino);
+
3240 if (node->cache_valid) {
+
3241 struct timespec now;
+
3242
+
3243 curr_time(&now);
+
3244 if (diff_timespec(&now, &node->stat_updated) >
+
3245 f->conf.ac_attr_timeout) {
+
3246 struct stat stbuf;
+
3247 int err;
+
3248 pthread_mutex_unlock(&f->lock);
+
3249 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3250 pthread_mutex_lock(&f->lock);
+
3251 if (!err)
+
3252 update_stat(node, &stbuf);
+
3253 else
+
3254 node->cache_valid = 0;
+
3255 }
+
3256 }
+
3257 if (node->cache_valid)
+
3258 fi->keep_cache = 1;
+
3259
+
3260 node->cache_valid = 1;
+
3261 pthread_mutex_unlock(&f->lock);
+
3262}
+
3263
+
3264static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3265 struct fuse_file_info *fi)
+
3266{
+
3267 struct fuse *f = req_fuse_prepare(req);
+
3268 struct fuse_intr_data d;
+
3269 char *path;
+
3270 int err;
+
3271
+
3272 err = get_path(f, ino, &path);
+
3273 if (!err) {
+
3274 fuse_prepare_interrupt(f, req, &d);
+
3275 err = fuse_fs_open(f->fs, path, fi);
+
3276 if (!err) {
+
3277 if (f->conf.direct_io)
+
3278 fi->direct_io = 1;
+
3279 if (f->conf.kernel_cache)
+
3280 fi->keep_cache = 1;
+
3281
+
3282 if (f->conf.auto_cache)
+
3283 open_auto_cache(f, ino, path, fi);
+
3284
+
3285 if (f->conf.no_rofd_flush &&
+
3286 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3287 fi->noflush = 1;
+
3288
+
3289 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3290 fi->parallel_direct_writes = 1;
+
3291
+
3292 }
+
3293 fuse_finish_interrupt(f, req, &d);
+
3294 }
+
3295 if (!err) {
+
3296 pthread_mutex_lock(&f->lock);
+
3297 get_node(f, ino)->open_count++;
+
3298 pthread_mutex_unlock(&f->lock);
+
3299 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3300 /* The open syscall was interrupted, so it
+
3301 must be cancelled */
+
3302 fuse_do_release(f, ino, path, fi);
+
3303 }
+
3304 } else
+
3305 reply_err(req, err);
+
3306
+
3307 free_path(f, ino, path);
+
3308}
+
3309
+
3310static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3311 off_t off, struct fuse_file_info *fi)
+
3312{
+
3313 struct fuse *f = req_fuse_prepare(req);
+
3314 struct fuse_bufvec *buf = NULL;
+
3315 char *path;
+
3316 int res;
+
3317
+
3318 res = get_path_nullok(f, ino, &path);
+
3319 if (res == 0) {
+
3320 struct fuse_intr_data d;
+
3321
+
3322 fuse_prepare_interrupt(f, req, &d);
+
3323 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3324 fuse_finish_interrupt(f, req, &d);
+
3325 free_path(f, ino, path);
+
3326 }
+
3327
+
3328 if (res == 0)
+ +
3330 else
+
3331 reply_err(req, res);
+
3332
+
3333 fuse_free_buf(buf);
+
3334}
+
3335
+
3336static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3337 struct fuse_bufvec *buf, off_t off,
+
3338 struct fuse_file_info *fi)
+
3339{
+
3340 struct fuse *f = req_fuse_prepare(req);
+
3341 char *path;
+
3342 int res;
+
3343
+
3344 res = get_path_nullok(f, ino, &path);
+
3345 if (res == 0) {
+
3346 struct fuse_intr_data d;
+
3347
+
3348 fuse_prepare_interrupt(f, req, &d);
+
3349 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3350 fuse_finish_interrupt(f, req, &d);
+
3351 free_path(f, ino, path);
+
3352 }
+
3353
+
3354 if (res >= 0)
+
3355 fuse_reply_write(req, res);
+
3356 else
+
3357 reply_err(req, res);
+
3358}
+
3359
+
3360static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3361 struct fuse_file_info *fi)
+
3362{
+
3363 struct fuse *f = req_fuse_prepare(req);
+
3364 char *path;
+
3365 int err;
+
3366
+
3367 err = get_path_nullok(f, ino, &path);
+
3368 if (!err) {
+
3369 struct fuse_intr_data d;
+
3370
+
3371 fuse_prepare_interrupt(f, req, &d);
+
3372 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3373 fuse_finish_interrupt(f, req, &d);
+
3374 free_path(f, ino, path);
+
3375 }
+
3376 reply_err(req, err);
+
3377}
+
3378
+
3379static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3380 struct fuse_file_info *fi)
+
3381{
+
3382 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3383 memset(fi, 0, sizeof(struct fuse_file_info));
+
3384 fi->fh = dh->fh;
+
3385 return dh;
+
3386}
+
3387
+
3388static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3389 struct fuse_file_info *llfi)
+
3390{
+
3391 struct fuse *f = req_fuse_prepare(req);
+
3392 struct fuse_intr_data d;
+
3393 struct fuse_dh *dh;
+
3394 struct fuse_file_info fi;
+
3395 char *path;
+
3396 int err;
+
3397
+
3398 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3399 if (dh == NULL) {
+
3400 reply_err(req, -ENOMEM);
+
3401 return;
+
3402 }
+
3403 memset(dh, 0, sizeof(struct fuse_dh));
+
3404 dh->fuse = f;
+
3405 dh->contents = NULL;
+
3406 dh->first = NULL;
+
3407 dh->len = 0;
+
3408 dh->filled = 0;
+
3409 dh->nodeid = ino;
+
3410 pthread_mutex_init(&dh->lock, NULL);
+
3411
+
3412 llfi->fh = (uintptr_t) dh;
+
3413
+
3414 memset(&fi, 0, sizeof(fi));
+
3415 fi.flags = llfi->flags;
+
3416
+
3417 err = get_path(f, ino, &path);
+
3418 if (!err) {
+
3419 fuse_prepare_interrupt(f, req, &d);
+
3420 err = fuse_fs_opendir(f->fs, path, &fi);
+
3421 fuse_finish_interrupt(f, req, &d);
+
3422 dh->fh = fi.fh;
+
3423 llfi->cache_readdir = fi.cache_readdir;
+
3424 llfi->keep_cache = fi.keep_cache;
+
3425 }
+
3426 if (!err) {
+
3427 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3428 /* The opendir syscall was interrupted, so it
+
3429 must be cancelled */
+
3430 fuse_fs_releasedir(f->fs, path, &fi);
+
3431 pthread_mutex_destroy(&dh->lock);
+
3432 free(dh);
+
3433 }
+
3434 } else {
+
3435 reply_err(req, err);
+
3436 pthread_mutex_destroy(&dh->lock);
+
3437 free(dh);
+
3438 }
+
3439 free_path(f, ino, path);
+
3440}
+
3441
+
3442static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3443{
+
3444 if (minsize > dh->size) {
+
3445 char *newptr;
+
3446 unsigned newsize = dh->size;
+
3447 if (!newsize)
+
3448 newsize = 1024;
+
3449 while (newsize < minsize) {
+
3450 if (newsize >= 0x80000000)
+
3451 newsize = 0xffffffff;
+
3452 else
+
3453 newsize *= 2;
+
3454 }
+
3455
+
3456 newptr = (char *) realloc(dh->contents, newsize);
+
3457 if (!newptr) {
+
3458 dh->error = -ENOMEM;
+
3459 return -1;
+
3460 }
+
3461 dh->contents = newptr;
+
3462 dh->size = newsize;
+
3463 }
+
3464 return 0;
+
3465}
+
3466
+
3467static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3468 struct stat *st, enum fuse_fill_dir_flags flags)
+
3469{
+
3470 struct fuse_direntry *de;
+
3471
+
3472 de = malloc(sizeof(struct fuse_direntry));
+
3473 if (!de) {
+
3474 dh->error = -ENOMEM;
+
3475 return -1;
+
3476 }
+
3477 de->name = strdup(name);
+
3478 if (!de->name) {
+
3479 dh->error = -ENOMEM;
+
3480 free(de);
+
3481 return -1;
+
3482 }
+
3483 de->flags = flags;
+
3484 de->stat = *st;
+
3485 de->next = NULL;
+
3486
+
3487 *dh->last = de;
+
3488 dh->last = &de->next;
+
3489
+
3490 return 0;
+
3491}
+
3492
+
3493static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3494 const char *name)
+
3495{
+
3496 struct node *node;
+
3497 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3498
+
3499 pthread_mutex_lock(&f->lock);
+
3500 node = lookup_node(f, parent, name);
+
3501 if (node)
+
3502 res = node->nodeid;
+
3503 pthread_mutex_unlock(&f->lock);
+
3504
+
3505 return res;
+
3506}
+
3507
+
3508static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3509 off_t off, enum fuse_fill_dir_flags flags)
+
3510{
+
3511 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3512 struct stat stbuf;
+
3513
+
3514 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3515 dh->error = -EIO;
+
3516 return 1;
+
3517 }
+
3518
+
3519 if (statp)
+
3520 stbuf = *statp;
+
3521 else {
+
3522 memset(&stbuf, 0, sizeof(stbuf));
+
3523 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3524 }
+
3525
+
3526 if (!dh->fuse->conf.use_ino) {
+
3527 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3528 if (dh->fuse->conf.readdir_ino) {
+
3529 stbuf.st_ino = (ino_t)
+
3530 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3531 }
+
3532 }
+
3533
+
3534 if (off) {
+
3535 size_t newlen;
+
3536
+
3537 if (dh->filled) {
+
3538 dh->error = -EIO;
+
3539 return 1;
+
3540 }
+
3541
+
3542 if (dh->first) {
+
3543 dh->error = -EIO;
+
3544 return 1;
+
3545 }
+
3546
+
3547 if (extend_contents(dh, dh->needlen) == -1)
+
3548 return 1;
+
3549
+
3550 newlen = dh->len +
+
3551 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3552 dh->needlen - dh->len, name,
+
3553 &stbuf, off);
+
3554 if (newlen > dh->needlen)
+
3555 return 1;
+
3556
+
3557 dh->len = newlen;
+
3558 } else {
+
3559 dh->filled = 1;
+
3560
+
3561 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3562 return 1;
+
3563 }
+
3564 return 0;
+
3565}
+
3566
+
3567static int is_dot_or_dotdot(const char *name)
+
3568{
+
3569 return name[0] == '.' && (name[1] == '\0' ||
+
3570 (name[1] == '.' && name[2] == '\0'));
+
3571}
+
3572
+
3573static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3574 off_t off, enum fuse_fill_dir_flags flags)
+
3575{
+
3576 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3577 struct fuse_entry_param e = {
+
3578 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3579 .ino = 0,
+
3580 };
+
3581 struct fuse *f = dh->fuse;
+
3582 int res;
+
3583
+
3584 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3585 dh->error = -EIO;
+
3586 return 1;
+
3587 }
+
3588
+
3589 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3590 e.attr = *statp;
+
3591 } else {
+
3592 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3593 if (statp) {
+
3594 e.attr.st_mode = statp->st_mode;
+
3595 if (f->conf.use_ino)
+
3596 e.attr.st_ino = statp->st_ino;
+
3597 }
+
3598 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3599 e.attr.st_ino = (ino_t)
+
3600 lookup_nodeid(f, dh->nodeid, name);
+
3601 }
+
3602 }
+
3603
+
3604 if (off) {
+
3605 size_t newlen;
+
3606
+
3607 if (dh->filled) {
+
3608 dh->error = -EIO;
+
3609 return 1;
+
3610 }
+
3611
+
3612 if (dh->first) {
+
3613 dh->error = -EIO;
+
3614 return 1;
+
3615 }
+
3616 if (extend_contents(dh, dh->needlen) == -1)
+
3617 return 1;
+
3618
+
3619 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3620 if (!is_dot_or_dotdot(name)) {
+
3621 res = do_lookup(f, dh->nodeid, name, &e);
+
3622 if (res) {
+
3623 dh->error = res;
+
3624 return 1;
+
3625 }
+
3626 }
+
3627 }
+
3628
+
3629 newlen = dh->len +
+
3630 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3631 dh->needlen - dh->len, name,
+
3632 &e, off);
+
3633 if (newlen > dh->needlen)
+
3634 return 1;
+
3635 dh->len = newlen;
+
3636 } else {
+
3637 dh->filled = 1;
+
3638
+
3639 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3640 return 1;
+
3641 }
+
3642
+
3643 return 0;
+
3644}
+
3645
+
3646static void free_direntries(struct fuse_direntry *de)
+
3647{
+
3648 while (de) {
+
3649 struct fuse_direntry *next = de->next;
+
3650 free(de->name);
+
3651 free(de);
+
3652 de = next;
+
3653 }
+
3654}
+
3655
+
3656static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3657 size_t size, off_t off, struct fuse_dh *dh,
+
3658 struct fuse_file_info *fi,
+
3659 enum fuse_readdir_flags flags)
+
3660{
+
3661 char *path;
+
3662 int err;
+
3663
+
3664 if (f->fs->op.readdir)
+
3665 err = get_path_nullok(f, ino, &path);
+
3666 else
+
3667 err = get_path(f, ino, &path);
+
3668 if (!err) {
+
3669 struct fuse_intr_data d;
+
3670 fuse_fill_dir_t filler = fill_dir;
+
3671
+
3672 if (flags & FUSE_READDIR_PLUS)
+
3673 filler = fill_dir_plus;
+
3674
+
3675 free_direntries(dh->first);
+
3676 dh->first = NULL;
+
3677 dh->last = &dh->first;
+
3678 dh->len = 0;
+
3679 dh->error = 0;
+
3680 dh->needlen = size;
+
3681 dh->filled = 0;
+
3682 dh->req = req;
+
3683 fuse_prepare_interrupt(f, req, &d);
+
3684 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3685 fuse_finish_interrupt(f, req, &d);
+
3686 dh->req = NULL;
+
3687 if (!err)
+
3688 err = dh->error;
+
3689 if (err)
+
3690 dh->filled = 0;
+
3691 free_path(f, ino, path);
+
3692 }
+
3693 return err;
+
3694}
+
3695
+
3696static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3697 off_t off, enum fuse_readdir_flags flags)
+
3698{
+
3699 off_t pos;
+
3700 struct fuse_direntry *de = dh->first;
+
3701 int res;
+
3702
+
3703 dh->len = 0;
+
3704
+
3705 if (extend_contents(dh, dh->needlen) == -1)
+
3706 return dh->error;
+
3707
+
3708 for (pos = 0; pos < off; pos++) {
+
3709 if (!de)
+
3710 break;
+
3711
+
3712 de = de->next;
+
3713 }
+
3714 while (de) {
+
3715 char *p = dh->contents + dh->len;
+
3716 unsigned rem = dh->needlen - dh->len;
+
3717 unsigned thislen;
+
3718 unsigned newlen;
+
3719 pos++;
+
3720
+
3721 if (flags & FUSE_READDIR_PLUS) {
+
3722 struct fuse_entry_param e = {
+
3723 .ino = 0,
+
3724 .attr = de->stat,
+
3725 };
+
3726
+
3727 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3728 !is_dot_or_dotdot(de->name)) {
+
3729 res = do_lookup(dh->fuse, dh->nodeid,
+
3730 de->name, &e);
+
3731 if (res) {
+
3732 dh->error = res;
+
3733 return 1;
+
3734 }
+
3735 }
+
3736
+
3737 thislen = fuse_add_direntry_plus(req, p, rem,
+
3738 de->name, &e, pos);
+
3739 } else {
+
3740 thislen = fuse_add_direntry(req, p, rem,
+
3741 de->name, &de->stat, pos);
+
3742 }
+
3743 newlen = dh->len + thislen;
+
3744 if (newlen > dh->needlen)
+
3745 break;
+
3746 dh->len = newlen;
+
3747 de = de->next;
+
3748 }
+
3749 return 0;
+
3750}
+
3751
+
3752static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3753 off_t off, struct fuse_file_info *llfi,
+
3754 enum fuse_readdir_flags flags)
+
3755{
+
3756 struct fuse *f = req_fuse_prepare(req);
+
3757 struct fuse_file_info fi;
+
3758 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3759 int err;
+
3760
+
3761 pthread_mutex_lock(&dh->lock);
+
3762 /* According to SUS, directory contents need to be refreshed on
+
3763 rewinddir() */
+
3764 if (!off)
+
3765 dh->filled = 0;
+
3766
+
3767 if (!dh->filled) {
+
3768 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3769 if (err) {
+
3770 reply_err(req, err);
+
3771 goto out;
+
3772 }
+
3773 }
+
3774 if (dh->filled) {
+
3775 dh->needlen = size;
+
3776 err = readdir_fill_from_list(req, dh, off, flags);
+
3777 if (err) {
+
3778 reply_err(req, err);
+
3779 goto out;
+
3780 }
+
3781 }
+
3782 fuse_reply_buf(req, dh->contents, dh->len);
+
3783out:
+
3784 pthread_mutex_unlock(&dh->lock);
+
3785}
+
3786
+
3787static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3788 off_t off, struct fuse_file_info *llfi)
+
3789{
+
3790 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3791}
+
3792
+
3793static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3794 off_t off, struct fuse_file_info *llfi)
+
3795{
+
3796 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3797}
+
3798
+
3799static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3800 struct fuse_file_info *llfi)
+
3801{
+
3802 struct fuse *f = req_fuse_prepare(req);
+
3803 struct fuse_intr_data d;
+
3804 struct fuse_file_info fi;
+
3805 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3806 char *path;
+
3807
+
3808 get_path_nullok(f, ino, &path);
+
3809
+
3810 fuse_prepare_interrupt(f, req, &d);
+
3811 fuse_fs_releasedir(f->fs, path, &fi);
+
3812 fuse_finish_interrupt(f, req, &d);
+
3813 free_path(f, ino, path);
+
3814
+
3815 pthread_mutex_lock(&dh->lock);
+
3816 pthread_mutex_unlock(&dh->lock);
+
3817 pthread_mutex_destroy(&dh->lock);
+
3818 free_direntries(dh->first);
+
3819 free(dh->contents);
+
3820 free(dh);
+
3821 reply_err(req, 0);
+
3822}
+
3823
+
3824static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3825 struct fuse_file_info *llfi)
+
3826{
+
3827 struct fuse *f = req_fuse_prepare(req);
+
3828 struct fuse_file_info fi;
+
3829 char *path;
+
3830 int err;
+
3831
+
3832 get_dirhandle(llfi, &fi);
+
3833
+
3834 err = get_path_nullok(f, ino, &path);
+
3835 if (!err) {
+
3836 struct fuse_intr_data d;
+
3837 fuse_prepare_interrupt(f, req, &d);
+
3838 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3839 fuse_finish_interrupt(f, req, &d);
+
3840 free_path(f, ino, path);
+
3841 }
+
3842 reply_err(req, err);
+
3843}
+
3844
+
3845static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3846{
+
3847 struct fuse *f = req_fuse_prepare(req);
+
3848 struct statvfs buf;
+
3849 char *path = NULL;
+
3850 int err = 0;
+
3851
+
3852 memset(&buf, 0, sizeof(buf));
+
3853 if (ino)
+
3854 err = get_path(f, ino, &path);
+
3855
+
3856 if (!err) {
+
3857 struct fuse_intr_data d;
+
3858 fuse_prepare_interrupt(f, req, &d);
+
3859 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3860 fuse_finish_interrupt(f, req, &d);
+
3861 free_path(f, ino, path);
+
3862 }
+
3863
+
3864 if (!err)
+
3865 fuse_reply_statfs(req, &buf);
+
3866 else
+
3867 reply_err(req, err);
+
3868}
+
3869
+
3870static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3871 const char *value, size_t size, int flags)
+
3872{
+
3873 struct fuse *f = req_fuse_prepare(req);
+
3874 char *path;
+
3875 int err;
+
3876
+
3877 err = get_path(f, ino, &path);
+
3878 if (!err) {
+
3879 struct fuse_intr_data d;
+
3880 fuse_prepare_interrupt(f, req, &d);
+
3881 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3882 fuse_finish_interrupt(f, req, &d);
+
3883 free_path(f, ino, path);
+
3884 }
+
3885 reply_err(req, err);
+
3886}
+
3887
+
3888static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3889 const char *name, char *value, size_t size)
+
3890{
+
3891 int err;
+
3892 char *path;
+
3893
+
3894 err = get_path(f, ino, &path);
+
3895 if (!err) {
+
3896 struct fuse_intr_data d;
+
3897 fuse_prepare_interrupt(f, req, &d);
+
3898 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3899 fuse_finish_interrupt(f, req, &d);
+
3900 free_path(f, ino, path);
+
3901 }
+
3902 return err;
+
3903}
+
3904
+
3905static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3906 size_t size)
+
3907{
+
3908 struct fuse *f = req_fuse_prepare(req);
+
3909 int res;
+
3910
+
3911 if (size) {
+
3912 char *value = (char *) malloc(size);
+
3913 if (value == NULL) {
+
3914 reply_err(req, -ENOMEM);
+
3915 return;
+
3916 }
+
3917 res = common_getxattr(f, req, ino, name, value, size);
+
3918 if (res > 0)
+
3919 fuse_reply_buf(req, value, res);
+
3920 else
+
3921 reply_err(req, res);
+
3922 free(value);
+
3923 } else {
+
3924 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3925 if (res >= 0)
+
3926 fuse_reply_xattr(req, res);
+
3927 else
+
3928 reply_err(req, res);
+
3929 }
+
3930}
+
3931
+
3932static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3933 char *list, size_t size)
+
3934{
+
3935 char *path;
+
3936 int err;
+
3937
+
3938 err = get_path(f, ino, &path);
+
3939 if (!err) {
+
3940 struct fuse_intr_data d;
+
3941 fuse_prepare_interrupt(f, req, &d);
+
3942 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3943 fuse_finish_interrupt(f, req, &d);
+
3944 free_path(f, ino, path);
+
3945 }
+
3946 return err;
+
3947}
+
3948
+
3949static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3950{
+
3951 struct fuse *f = req_fuse_prepare(req);
+
3952 int res;
+
3953
+
3954 if (size) {
+
3955 char *list = (char *) malloc(size);
+
3956 if (list == NULL) {
+
3957 reply_err(req, -ENOMEM);
+
3958 return;
+
3959 }
+
3960 res = common_listxattr(f, req, ino, list, size);
+
3961 if (res > 0)
+
3962 fuse_reply_buf(req, list, res);
+
3963 else
+
3964 reply_err(req, res);
+
3965 free(list);
+
3966 } else {
+
3967 res = common_listxattr(f, req, ino, NULL, 0);
+
3968 if (res >= 0)
+
3969 fuse_reply_xattr(req, res);
+
3970 else
+
3971 reply_err(req, res);
+
3972 }
+
3973}
+
3974
+
3975static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3976 const char *name)
+
3977{
+
3978 struct fuse *f = req_fuse_prepare(req);
+
3979 char *path;
+
3980 int err;
+
3981
+
3982 err = get_path(f, ino, &path);
+
3983 if (!err) {
+
3984 struct fuse_intr_data d;
+
3985 fuse_prepare_interrupt(f, req, &d);
+
3986 err = fuse_fs_removexattr(f->fs, path, name);
+
3987 fuse_finish_interrupt(f, req, &d);
+
3988 free_path(f, ino, path);
+
3989 }
+
3990 reply_err(req, err);
+
3991}
+
3992
+
3993static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3994{
+
3995 struct lock *l;
+
3996
+
3997 for (l = node->locks; l; l = l->next)
+
3998 if (l->owner != lock->owner &&
+
3999 lock->start <= l->end && l->start <= lock->end &&
+
4000 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
4001 break;
+
4002
+
4003 return l;
+
4004}
+
4005
+
4006static void delete_lock(struct lock **lockp)
+
4007{
+
4008 struct lock *l = *lockp;
+
4009 *lockp = l->next;
+
4010 free(l);
+
4011}
+
4012
+
4013static void insert_lock(struct lock **pos, struct lock *lock)
+
4014{
+
4015 lock->next = *pos;
+
4016 *pos = lock;
+
4017}
+
4018
+
4019static int locks_insert(struct node *node, struct lock *lock)
+
4020{
+
4021 struct lock **lp;
+
4022 struct lock *newl1 = NULL;
+
4023 struct lock *newl2 = NULL;
+
4024
+
4025 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4026 lock->end != OFFSET_MAX) {
+
4027 newl1 = malloc(sizeof(struct lock));
+
4028 newl2 = malloc(sizeof(struct lock));
+
4029
+
4030 if (!newl1 || !newl2) {
+
4031 free(newl1);
+
4032 free(newl2);
+
4033 return -ENOLCK;
+
4034 }
+
4035 }
+
4036
+
4037 for (lp = &node->locks; *lp;) {
+
4038 struct lock *l = *lp;
+
4039 if (l->owner != lock->owner)
+
4040 goto skip;
+
4041
+
4042 if (lock->type == l->type) {
+
4043 if (l->end < lock->start - 1)
+
4044 goto skip;
+
4045 if (lock->end < l->start - 1)
+
4046 break;
+
4047 if (l->start <= lock->start && lock->end <= l->end)
+
4048 goto out;
+
4049 if (l->start < lock->start)
+
4050 lock->start = l->start;
+
4051 if (lock->end < l->end)
+
4052 lock->end = l->end;
+
4053 goto delete;
+
4054 } else {
+
4055 if (l->end < lock->start)
+
4056 goto skip;
+
4057 if (lock->end < l->start)
+
4058 break;
+
4059 if (lock->start <= l->start && l->end <= lock->end)
+
4060 goto delete;
+
4061 if (l->end <= lock->end) {
+
4062 l->end = lock->start - 1;
+
4063 goto skip;
+
4064 }
+
4065 if (lock->start <= l->start) {
+
4066 l->start = lock->end + 1;
+
4067 break;
+
4068 }
+
4069 *newl2 = *l;
+
4070 newl2->start = lock->end + 1;
+
4071 l->end = lock->start - 1;
+
4072 insert_lock(&l->next, newl2);
+
4073 newl2 = NULL;
+
4074 }
+
4075 skip:
+
4076 lp = &l->next;
+
4077 continue;
+
4078
+
4079 delete:
+
4080 delete_lock(lp);
+
4081 }
+
4082 if (lock->type != F_UNLCK) {
+
4083 *newl1 = *lock;
+
4084 insert_lock(lp, newl1);
+
4085 newl1 = NULL;
+
4086 }
+
4087out:
+
4088 free(newl1);
+
4089 free(newl2);
+
4090 return 0;
+
4091}
+
4092
+
4093static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4094{
+
4095 memset(lock, 0, sizeof(struct lock));
+
4096 lock->type = flock->l_type;
+
4097 lock->start = flock->l_start;
+
4098 lock->end =
+
4099 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4100 lock->pid = flock->l_pid;
+
4101}
+
4102
+
4103static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4104{
+
4105 flock->l_type = lock->type;
+
4106 flock->l_start = lock->start;
+
4107 flock->l_len =
+
4108 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4109 flock->l_pid = lock->pid;
+
4110}
+
4111
+
4112static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4113 const char *path, struct fuse_file_info *fi)
+
4114{
+
4115 struct fuse_intr_data d;
+
4116 struct flock lock;
+
4117 struct lock l;
+
4118 int err;
+
4119 int errlock;
+
4120
+
4121 fuse_prepare_interrupt(f, req, &d);
+
4122 memset(&lock, 0, sizeof(lock));
+
4123 lock.l_type = F_UNLCK;
+
4124 lock.l_whence = SEEK_SET;
+
4125 err = fuse_fs_flush(f->fs, path, fi);
+
4126 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4127 fuse_finish_interrupt(f, req, &d);
+
4128
+
4129 if (errlock != -ENOSYS) {
+
4130 flock_to_lock(&lock, &l);
+
4131 l.owner = fi->lock_owner;
+
4132 pthread_mutex_lock(&f->lock);
+
4133 locks_insert(get_node(f, ino), &l);
+
4134 pthread_mutex_unlock(&f->lock);
+
4135
+
4136 /* if op.lock() is defined FLUSH is needed regardless
+
4137 of op.flush() */
+
4138 if (err == -ENOSYS)
+
4139 err = 0;
+
4140 }
+
4141 return err;
+
4142}
+
4143
+
4144static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4145 struct fuse_file_info *fi)
+
4146{
+
4147 struct fuse *f = req_fuse_prepare(req);
+
4148 struct fuse_intr_data d;
+
4149 char *path;
+
4150 int err = 0;
+
4151
+
4152 get_path_nullok(f, ino, &path);
+
4153 if (fi->flush) {
+
4154 err = fuse_flush_common(f, req, ino, path, fi);
+
4155 if (err == -ENOSYS)
+
4156 err = 0;
+
4157 }
+
4158
+
4159 fuse_prepare_interrupt(f, req, &d);
+
4160 fuse_do_release(f, ino, path, fi);
+
4161 fuse_finish_interrupt(f, req, &d);
+
4162 free_path(f, ino, path);
+
4163
+
4164 reply_err(req, err);
+
4165}
+
4166
+
4167static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4168 struct fuse_file_info *fi)
+
4169{
+
4170 struct fuse *f = req_fuse_prepare(req);
+
4171 char *path;
+
4172 int err;
+
4173
+
4174 get_path_nullok(f, ino, &path);
+
4175 err = fuse_flush_common(f, req, ino, path, fi);
+
4176 free_path(f, ino, path);
+
4177
+
4178 reply_err(req, err);
+
4179}
+
4180
+
4181static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4182 struct fuse_file_info *fi, struct flock *lock,
+
4183 int cmd)
+
4184{
+
4185 struct fuse *f = req_fuse_prepare(req);
+
4186 char *path;
+
4187 int err;
+
4188
+
4189 err = get_path_nullok(f, ino, &path);
+
4190 if (!err) {
+
4191 struct fuse_intr_data d;
+
4192 fuse_prepare_interrupt(f, req, &d);
+
4193 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4194 fuse_finish_interrupt(f, req, &d);
+
4195 free_path(f, ino, path);
+
4196 }
+
4197 return err;
+
4198}
+
4199
+
4200static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4201 struct fuse_file_info *fi, struct flock *lock)
+
4202{
+
4203 int err;
+
4204 struct lock l;
+
4205 struct lock *conflict;
+
4206 struct fuse *f = req_fuse(req);
+
4207
+
4208 flock_to_lock(lock, &l);
+
4209 l.owner = fi->lock_owner;
+
4210 pthread_mutex_lock(&f->lock);
+
4211 conflict = locks_conflict(get_node(f, ino), &l);
+
4212 if (conflict)
+
4213 lock_to_flock(conflict, lock);
+
4214 pthread_mutex_unlock(&f->lock);
+
4215 if (!conflict)
+
4216 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4217 else
+
4218 err = 0;
+
4219
+
4220 if (!err)
+
4221 fuse_reply_lock(req, lock);
+
4222 else
+
4223 reply_err(req, err);
+
4224}
+
4225
+
4226static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4227 struct fuse_file_info *fi, struct flock *lock,
+
4228 int sleep)
+
4229{
+
4230 int err = fuse_lock_common(req, ino, fi, lock,
+
4231 sleep ? F_SETLKW : F_SETLK);
+
4232 if (!err) {
+
4233 struct fuse *f = req_fuse(req);
+
4234 struct lock l;
+
4235 flock_to_lock(lock, &l);
+
4236 l.owner = fi->lock_owner;
+
4237 pthread_mutex_lock(&f->lock);
+
4238 locks_insert(get_node(f, ino), &l);
+
4239 pthread_mutex_unlock(&f->lock);
+
4240 }
+
4241 reply_err(req, err);
+
4242}
+
4243
+
4244static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4245 struct fuse_file_info *fi, int op)
+
4246{
+
4247 struct fuse *f = req_fuse_prepare(req);
+
4248 char *path;
+
4249 int err;
+
4250
+
4251 err = get_path_nullok(f, ino, &path);
+
4252 if (err == 0) {
+
4253 struct fuse_intr_data d;
+
4254 fuse_prepare_interrupt(f, req, &d);
+
4255 err = fuse_fs_flock(f->fs, path, fi, op);
+
4256 fuse_finish_interrupt(f, req, &d);
+
4257 free_path(f, ino, path);
+
4258 }
+
4259 reply_err(req, err);
+
4260}
+
4261
+
4262static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4263 uint64_t idx)
+
4264{
+
4265 struct fuse *f = req_fuse_prepare(req);
+
4266 struct fuse_intr_data d;
+
4267 char *path;
+
4268 int err;
+
4269
+
4270 err = get_path(f, ino, &path);
+
4271 if (!err) {
+
4272 fuse_prepare_interrupt(f, req, &d);
+
4273 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4274 fuse_finish_interrupt(f, req, &d);
+
4275 free_path(f, ino, path);
+
4276 }
+
4277 if (!err)
+
4278 fuse_reply_bmap(req, idx);
+
4279 else
+
4280 reply_err(req, err);
+
4281}
+
4282
+
4283static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4284 void *arg, struct fuse_file_info *llfi,
+
4285 unsigned int flags, const void *in_buf,
+
4286 size_t in_bufsz, size_t out_bufsz)
+
4287{
+
4288 struct fuse *f = req_fuse_prepare(req);
+
4289 struct fuse_intr_data d;
+
4290 struct fuse_file_info fi;
+
4291 char *path, *out_buf = NULL;
+
4292 int err;
+
4293
+
4294 err = -EPERM;
+
4295 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4296 goto err;
+
4297
+
4298 if (flags & FUSE_IOCTL_DIR)
+
4299 get_dirhandle(llfi, &fi);
+
4300 else
+
4301 fi = *llfi;
+
4302
+
4303 if (out_bufsz) {
+
4304 err = -ENOMEM;
+
4305 out_buf = malloc(out_bufsz);
+
4306 if (!out_buf)
+
4307 goto err;
+
4308 }
+
4309
+
4310 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4311 if (out_buf && in_bufsz)
+
4312 memcpy(out_buf, in_buf, in_bufsz);
+
4313
+
4314 err = get_path_nullok(f, ino, &path);
+
4315 if (err)
+
4316 goto err;
+
4317
+
4318 fuse_prepare_interrupt(f, req, &d);
+
4319
+
4320 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4321 out_buf ? out_buf : (void *)in_buf);
+
4322
+
4323 fuse_finish_interrupt(f, req, &d);
+
4324 free_path(f, ino, path);
+
4325
+
4326 if (err < 0)
+
4327 goto err;
+
4328 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4329 goto out;
+
4330err:
+
4331 reply_err(req, err);
+
4332out:
+
4333 free(out_buf);
+
4334}
+
4335
+
4336static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4337 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4338{
+
4339 struct fuse *f = req_fuse_prepare(req);
+
4340 struct fuse_intr_data d;
+
4341 char *path;
+
4342 int err;
+
4343 unsigned revents = 0;
+
4344
+
4345 err = get_path_nullok(f, ino, &path);
+
4346 if (!err) {
+
4347 fuse_prepare_interrupt(f, req, &d);
+
4348 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4349 fuse_finish_interrupt(f, req, &d);
+
4350 free_path(f, ino, path);
+
4351 }
+
4352 if (!err)
+
4353 fuse_reply_poll(req, revents);
+
4354 else
+
4355 reply_err(req, err);
+
4356}
+
4357
+
4358static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4359 off_t offset, off_t length, struct fuse_file_info *fi)
+
4360{
+
4361 struct fuse *f = req_fuse_prepare(req);
+
4362 struct fuse_intr_data d;
+
4363 char *path;
+
4364 int err;
+
4365
+
4366 err = get_path_nullok(f, ino, &path);
+
4367 if (!err) {
+
4368 fuse_prepare_interrupt(f, req, &d);
+
4369 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4370 fuse_finish_interrupt(f, req, &d);
+
4371 free_path(f, ino, path);
+
4372 }
+
4373 reply_err(req, err);
+
4374}
+
4375
+
4376static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4377 off_t off_in, struct fuse_file_info *fi_in,
+
4378 fuse_ino_t nodeid_out, off_t off_out,
+
4379 struct fuse_file_info *fi_out, size_t len,
+
4380 int flags)
+
4381{
+
4382 struct fuse *f = req_fuse_prepare(req);
+
4383 struct fuse_intr_data d;
+
4384 char *path_in, *path_out;
+
4385 int err;
+
4386 ssize_t res;
+
4387
+
4388 err = get_path_nullok(f, nodeid_in, &path_in);
+
4389 if (err) {
+
4390 reply_err(req, err);
+
4391 return;
+
4392 }
+
4393
+
4394 err = get_path_nullok(f, nodeid_out, &path_out);
+
4395 if (err) {
+
4396 free_path(f, nodeid_in, path_in);
+
4397 reply_err(req, err);
+
4398 return;
+
4399 }
+
4400
+
4401 fuse_prepare_interrupt(f, req, &d);
+
4402 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4403 fi_out, off_out, len, flags);
+
4404 fuse_finish_interrupt(f, req, &d);
+
4405
+
4406 if (res >= 0)
+
4407 fuse_reply_write(req, res);
+
4408 else
+
4409 reply_err(req, res);
+
4410
+
4411 free_path(f, nodeid_in, path_in);
+
4412 free_path(f, nodeid_out, path_out);
+
4413}
+
4414
+
4415static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4416 struct fuse_file_info *fi)
+
4417{
+
4418 struct fuse *f = req_fuse_prepare(req);
+
4419 struct fuse_intr_data d;
+
4420 char *path;
+
4421 int err;
+
4422 off_t res;
+
4423
+
4424 err = get_path(f, ino, &path);
+
4425 if (err) {
+
4426 reply_err(req, err);
+
4427 return;
+
4428 }
+
4429
+
4430 fuse_prepare_interrupt(f, req, &d);
+
4431 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4432 fuse_finish_interrupt(f, req, &d);
+
4433 free_path(f, ino, path);
+
4434 if (res >= 0)
+
4435 fuse_reply_lseek(req, res);
+
4436 else
+
4437 reply_err(req, res);
+
4438}
+
4439
+
4440static int clean_delay(struct fuse *f)
+
4441{
+
4442 /*
+
4443 * This is calculating the delay between clean runs. To
+
4444 * reduce the number of cleans we are doing them 10 times
+
4445 * within the remember window.
+
4446 */
+
4447 int min_sleep = 60;
+
4448 int max_sleep = 3600;
+
4449 int sleep_time = f->conf.remember / 10;
+
4450
+
4451 if (sleep_time > max_sleep)
+
4452 return max_sleep;
+
4453 if (sleep_time < min_sleep)
+
4454 return min_sleep;
+
4455 return sleep_time;
+
4456}
+
4457
+
+
4458int fuse_clean_cache(struct fuse *f)
+
4459{
+
4460 struct node_lru *lnode;
+
4461 struct list_head *curr, *next;
+
4462 struct node *node;
+
4463 struct timespec now;
+
4464
+
4465 pthread_mutex_lock(&f->lock);
+
4466
+
4467 curr_time(&now);
+
4468
+
4469 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4470 double age;
+
4471
+
4472 next = curr->next;
+
4473 lnode = list_entry(curr, struct node_lru, lru);
+
4474 node = &lnode->node;
+
4475
+
4476 age = diff_timespec(&now, &lnode->forget_time);
+
4477 if (age <= f->conf.remember)
+
4478 break;
+
4479
+
4480 assert(node->nlookup == 1);
+
4481
+
4482 /* Don't forget active directories */
+
4483 if (node->refctr > 1)
+
4484 continue;
+
4485
+
4486 node->nlookup = 0;
+
4487 unhash_name(f, node);
+
4488 unref_node(f, node);
+
4489 }
+
4490 pthread_mutex_unlock(&f->lock);
+
4491
+
4492 return clean_delay(f);
+
4493}
+
+
4494
+
4495static struct fuse_lowlevel_ops fuse_path_ops = {
+
4496 .init = fuse_lib_init,
+
4497 .destroy = fuse_lib_destroy,
+
4498 .lookup = fuse_lib_lookup,
+
4499 .forget = fuse_lib_forget,
+
4500 .forget_multi = fuse_lib_forget_multi,
+
4501 .getattr = fuse_lib_getattr,
+
4502 .setattr = fuse_lib_setattr,
+
4503 .access = fuse_lib_access,
+
4504 .readlink = fuse_lib_readlink,
+
4505 .mknod = fuse_lib_mknod,
+
4506 .mkdir = fuse_lib_mkdir,
+
4507 .unlink = fuse_lib_unlink,
+
4508 .rmdir = fuse_lib_rmdir,
+
4509 .symlink = fuse_lib_symlink,
+
4510 .rename = fuse_lib_rename,
+
4511 .link = fuse_lib_link,
+
4512 .create = fuse_lib_create,
+
4513 .open = fuse_lib_open,
+
4514 .read = fuse_lib_read,
+
4515 .write_buf = fuse_lib_write_buf,
+
4516 .flush = fuse_lib_flush,
+
4517 .release = fuse_lib_release,
+
4518 .fsync = fuse_lib_fsync,
+
4519 .opendir = fuse_lib_opendir,
+
4520 .readdir = fuse_lib_readdir,
+
4521 .readdirplus = fuse_lib_readdirplus,
+
4522 .releasedir = fuse_lib_releasedir,
+
4523 .fsyncdir = fuse_lib_fsyncdir,
+
4524 .statfs = fuse_lib_statfs,
+
4525 .setxattr = fuse_lib_setxattr,
+
4526 .getxattr = fuse_lib_getxattr,
+
4527 .listxattr = fuse_lib_listxattr,
+
4528 .removexattr = fuse_lib_removexattr,
+
4529 .getlk = fuse_lib_getlk,
+
4530 .setlk = fuse_lib_setlk,
+
4531 .flock = fuse_lib_flock,
+
4532 .bmap = fuse_lib_bmap,
+
4533 .ioctl = fuse_lib_ioctl,
+
4534 .poll = fuse_lib_poll,
+
4535 .fallocate = fuse_lib_fallocate,
+
4536 .copy_file_range = fuse_lib_copy_file_range,
+
4537 .lseek = fuse_lib_lseek,
+
4538};
+
4539
+
4540int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4541{
+
4542 return fuse_lowlevel_notify_poll(ph);
+
4543}
+
4544
+
+
4545struct fuse_session *fuse_get_session(struct fuse *f)
+
4546{
+
4547 return f->se;
+
4548}
+
+
4549
+
4550static int fuse_session_loop_remember(struct fuse *f)
+
4551{
+
4552 struct fuse_session *se = f->se;
+
4553 int res = 0;
+
4554 struct timespec now;
+
4555 time_t next_clean;
+
4556 struct pollfd fds = {
+
4557 .fd = se->fd,
+
4558 .events = POLLIN
+
4559 };
+
4560 struct fuse_buf fbuf = {
+
4561 .mem = NULL,
+
4562 };
+
4563
+
4564 curr_time(&now);
+
4565 next_clean = now.tv_sec;
+
4566 while (!fuse_session_exited(se)) {
+
4567 unsigned timeout;
+
4568
+
4569 curr_time(&now);
+
4570 if (now.tv_sec < next_clean)
+
4571 timeout = next_clean - now.tv_sec;
+
4572 else
+
4573 timeout = 0;
+
4574
+
4575 res = poll(&fds, 1, timeout * 1000);
+
4576 if (res == -1) {
+
4577 if (errno == EINTR)
+
4578 continue;
+
4579 else
+
4580 break;
+
4581 } else if (res > 0) {
+
4582 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4583 NULL);
+
4584 if (res == -EINTR)
+
4585 continue;
+
4586 if (res <= 0)
+
4587 break;
+
4588
+
4589 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4590 } else {
+
4591 timeout = fuse_clean_cache(f);
+
4592 curr_time(&now);
+
4593 next_clean = now.tv_sec + timeout;
+
4594 }
+
4595 }
+
4596
+
4597 free(fbuf.mem);
+ +
4599 return res < 0 ? -1 : 0;
+
4600}
+
4601
+
+
4602int fuse_loop(struct fuse *f)
+
4603{
+
4604 if (!f)
+
4605 return -1;
+
4606
+
4607 if (lru_enabled(f))
+
4608 return fuse_session_loop_remember(f);
+
4609
+
4610 return fuse_session_loop(f->se);
+
4611}
+
+
4612
+
4613FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4614int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4615{
+
4616 if (f == NULL)
+
4617 return -1;
+
4618
+
4619 int res = fuse_start_cleanup_thread(f);
+
4620 if (res)
+
4621 return -1;
+
4622
+
4623 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4625 return res;
+
4626}
+
4627
+
4628int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4629FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4630int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4631{
+
4632 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4633 if (config == NULL)
+
4634 return ENOMEM;
+
4635
+
4636 fuse_loop_cfg_convert(config, config_v1);
+
4637
+
4638 int res = fuse_loop_mt_312(f, config);
+
4639
+
4640 fuse_loop_cfg_destroy(config);
+
4641
+
4642 return res;
+
4643}
+
4644
+
4645int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4646FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4647int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4648{
+
4649 int err;
+
4650 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4651
+
4652 if (config == NULL)
+
4653 return ENOMEM;
+
4654
+
4655 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4656
+
4657 err = fuse_loop_mt_312(f, config);
+
4658
+
4659 fuse_loop_cfg_destroy(config);
+
4660
+
4661 return err;
+
4662}
+
4663
+
+
4664void fuse_exit(struct fuse *f)
+
4665{
+
4666 fuse_session_exit(f->se);
+
4667}
+
+
4668
+
+ +
4670{
+
4671 struct fuse_context_i *c = fuse_get_context_internal();
+
4672
+
4673 if (c)
+
4674 return &c->ctx;
+
4675 else
+
4676 return NULL;
+
4677}
+
+
4678
+
+
4679int fuse_getgroups(int size, gid_t list[])
+
4680{
+
4681 struct fuse_context_i *c = fuse_get_context_internal();
+
4682 if (!c)
+
4683 return -EINVAL;
+
4684
+
4685 return fuse_req_getgroups(c->req, size, list);
+
4686}
+
+
4687
+
+ +
4689{
+
4690 struct fuse_context_i *c = fuse_get_context_internal();
+
4691
+
4692 if (c)
+
4693 return fuse_req_interrupted(c->req);
+
4694 else
+
4695 return 0;
+
4696}
+
+
4697
+
+
4698int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4699 fuse_ino_t ino;
+
4700 int err = lookup_path_in_cache(f, path, &ino);
+
4701 if (err) {
+
4702 return err;
+
4703 }
+
4704
+
4705 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4706}
+
+
4707
+
4708#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4709
+
4710static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4713 FUSE_LIB_OPT("debug", debug, 1),
+
4714 FUSE_LIB_OPT("-d", debug, 1),
+
4715 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4716 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4717 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4718 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4719 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4720 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4721 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4722 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4723 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4724 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4725 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4726 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4727 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4728 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4729 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4730 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4731 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4732 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4733 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4734 FUSE_LIB_OPT("noforget", remember, -1),
+
4735 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4736 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4737 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4739};
+
4740
+
4741static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4742 struct fuse_args *outargs)
+
4743{
+
4744 (void) arg; (void) outargs; (void) data; (void) key;
+
4745
+
4746 /* Pass through unknown options */
+
4747 return 1;
+
4748}
+
4749
+
4750
+
4751static const struct fuse_opt fuse_help_opts[] = {
+
4752 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4753 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4755};
+
4756
+
4757static void print_module_help(const char *name,
+ +
4759{
+
4760 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4761 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4762 fuse_opt_add_arg(&a, "-h") == -1)
+
4763 return;
+
4764 printf("\nOptions for %s module:\n", name);
+
4765 (*fac)(&a, NULL);
+ +
4767}
+
4768
+
+
4769void fuse_lib_help(struct fuse_args *args)
+
4770{
+
4771 /* These are not all options, but only the ones that
+
4772 may be of interest to an end-user */
+
4773 printf(
+
4774" -o kernel_cache cache files in kernel\n"
+
4775" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4776" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4777" -o umask=M set file permissions (octal)\n"
+
4778" -o fmask=M set file permissions (octal)\n"
+
4779" -o dmask=M set dir permissions (octal)\n"
+
4780" -o uid=N set file owner\n"
+
4781" -o gid=N set file group\n"
+
4782" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4783" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4784" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4785" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4786" -o noforget never forget cached inodes\n"
+
4787" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4788" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4789
+
4790
+
4791 /* Print low-level help */
+ +
4793
+
4794 /* Print help for builtin modules */
+
4795 print_module_help("subdir", &fuse_module_subdir_factory);
+
4796#ifdef HAVE_ICONV
+
4797 print_module_help("iconv", &fuse_module_iconv_factory);
+
4798#endif
+
4799
+
4800 /* Parse command line options in case we need to
+
4801 activate more modules */
+
4802 struct fuse_config conf = { .modules = NULL };
+
4803 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4804 fuse_lib_opt_proc) == -1
+
4805 || !conf.modules)
+
4806 return;
+
4807
+
4808 char *module;
+
4809 char *next;
+
4810 struct fuse_module *m;
+
4811
+
4812 // Iterate over all modules
+
4813 for (module = conf.modules; module; module = next) {
+
4814 char *p;
+
4815 for (p = module; *p && *p != ':'; p++);
+
4816 next = *p ? p + 1 : NULL;
+
4817 *p = '\0';
+
4818
+
4819 m = fuse_get_module(module);
+
4820 if (m)
+
4821 print_module_help(module, &m->factory);
+
4822 }
+
4823}
+
+
4824
+
4825static int fuse_init_intr_signal(int signum, int *installed)
+
4826{
+
4827 struct sigaction old_sa;
+
4828
+
4829 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4830 perror("fuse: cannot get old signal handler");
+
4831 return -1;
+
4832 }
+
4833
+
4834 if (old_sa.sa_handler == SIG_DFL) {
+
4835 struct sigaction sa;
+
4836
+
4837 memset(&sa, 0, sizeof(struct sigaction));
+
4838 sa.sa_handler = fuse_intr_sighandler;
+
4839 sigemptyset(&sa.sa_mask);
+
4840
+
4841 if (sigaction(signum, &sa, NULL) == -1) {
+
4842 perror("fuse: cannot set interrupt signal handler");
+
4843 return -1;
+
4844 }
+
4845 *installed = 1;
+
4846 }
+
4847 return 0;
+
4848}
+
4849
+
4850static void fuse_restore_intr_signal(int signum)
+
4851{
+
4852 struct sigaction sa;
+
4853
+
4854 memset(&sa, 0, sizeof(struct sigaction));
+
4855 sa.sa_handler = SIG_DFL;
+
4856 sigaction(signum, &sa, NULL);
+
4857}
+
4858
+
4859
+
4860static int fuse_push_module(struct fuse *f, const char *module,
+
4861 struct fuse_args *args)
+
4862{
+
4863 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4864 struct fuse_fs *newfs;
+
4865 struct fuse_module *m = fuse_get_module(module);
+
4866
+
4867 if (!m)
+
4868 return -1;
+
4869
+
4870 newfs = m->factory(args, fs);
+
4871 if (!newfs) {
+
4872 fuse_put_module(m);
+
4873 return -1;
+
4874 }
+
4875 f->fs = newfs;
+
4876 return 0;
+
4877}
+
4878
+
+
4879struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4880 void *user_data)
+
4881{
+
4882 struct fuse_fs *fs;
+
4883
+
4884 if (sizeof(struct fuse_operations) < op_size) {
+
4885 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4886 op_size = sizeof(struct fuse_operations);
+
4887 }
+
4888
+
4889 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4890 if (!fs) {
+
4891 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4892 return NULL;
+
4893 }
+
4894
+
4895 fs->user_data = user_data;
+
4896 if (op)
+
4897 memcpy(&fs->op, op, op_size);
+
4898 return fs;
+
4899}
+
+
4900
+
4901static int node_table_init(struct node_table *t)
+
4902{
+
4903 t->size = NODE_TABLE_MIN_SIZE;
+
4904 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4905 if (t->array == NULL) {
+
4906 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4907 return -1;
+
4908 }
+
4909 t->use = 0;
+
4910 t->split = 0;
+
4911
+
4912 return 0;
+
4913}
+
4914
+
4915static void *fuse_prune_nodes(void *fuse)
+
4916{
+
4917 struct fuse *f = fuse;
+
4918 int sleep_time;
+
4919
+
4920#ifdef HAVE_PTHREAD_SETNAME_NP
+
4921 pthread_setname_np(pthread_self(), "fuse_prune_nodes");
+
4922#endif
+
4923
+
4924 while(1) {
+
4925 sleep_time = fuse_clean_cache(f);
+
4926 sleep(sleep_time);
+
4927 }
+
4928 return NULL;
+
4929}
+
4930
+
+
4931int fuse_start_cleanup_thread(struct fuse *f)
+
4932{
+
4933 if (lru_enabled(f))
+
4934 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4935
+
4936 return 0;
+
4937}
+
+
4938
+
+
4939void fuse_stop_cleanup_thread(struct fuse *f)
+
4940{
+
4941 if (lru_enabled(f)) {
+
4942 pthread_mutex_lock(&f->lock);
+
4943 pthread_cancel(f->prune_thread);
+
4944 pthread_mutex_unlock(&f->lock);
+
4945 pthread_join(f->prune_thread, NULL);
+
4946 }
+
4947}
+
+
4948
+
4949/*
+
4950 * Not supposed to be called directly, but supposed to be called
+
4951 * through the fuse_new macro
+
4952 */
+
4953struct fuse *_fuse_new_31(struct fuse_args *args,
+
4954 const struct fuse_operations *op, size_t op_size,
+
4955 struct libfuse_version *version, void *user_data)
+
4956{
+
4957 struct fuse *f;
+
4958 struct node *root;
+
4959 struct fuse_fs *fs;
+
4960 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4961
+
4962 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4963 if (f == NULL) {
+
4964 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4965 goto out;
+
4966 }
+
4967
+
4968 f->conf.entry_timeout = 1.0;
+
4969 f->conf.attr_timeout = 1.0;
+
4970 f->conf.negative_timeout = 0.0;
+
4971 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
4972
+
4973 /* Parse options */
+
4974 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
4975 fuse_lib_opt_proc) == -1)
+
4976 goto out_free;
+
4977
+
4978 pthread_mutex_lock(&fuse_context_lock);
+
4979 static int builtin_modules_registered = 0;
+
4980 /* Have the builtin modules already been registered? */
+
4981 if (builtin_modules_registered == 0) {
+
4982 /* If not, register them. */
+
4983 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
4984#ifdef HAVE_ICONV
+
4985 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
4986#endif
+
4987 builtin_modules_registered= 1;
+
4988 }
+
4989 pthread_mutex_unlock(&fuse_context_lock);
+
4990
+
4991 if (fuse_create_context_key() == -1)
+
4992 goto out_free;
+
4993
+
4994 fs = fuse_fs_new(op, op_size, user_data);
+
4995 if (!fs)
+
4996 goto out_delete_context_key;
+
4997
+
4998 f->fs = fs;
+
4999
+
5000 /* Oh f**k, this is ugly! */
+
5001 if (!fs->op.lock) {
+
5002 llop.getlk = NULL;
+
5003 llop.setlk = NULL;
+
5004 }
+
5005
+
5006 f->pagesize = getpagesize();
+
5007 init_list_head(&f->partial_slabs);
+
5008 init_list_head(&f->full_slabs);
+
5009 init_list_head(&f->lru_table);
+
5010
+
5011 if (f->conf.modules) {
+
5012 char *module;
+
5013 char *next;
+
5014
+
5015 for (module = f->conf.modules; module; module = next) {
+
5016 char *p;
+
5017 for (p = module; *p && *p != ':'; p++);
+
5018 next = *p ? p + 1 : NULL;
+
5019 *p = '\0';
+
5020 if (module[0] &&
+
5021 fuse_push_module(f, module, args) == -1)
+
5022 goto out_free_fs;
+
5023 }
+
5024 }
+
5025
+
5026 if (!f->conf.ac_attr_timeout_set)
+
5027 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5028
+
5029#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5030 /*
+
5031 * In FreeBSD, we always use these settings as inode numbers
+
5032 * are needed to make getcwd(3) work.
+
5033 */
+
5034 f->conf.readdir_ino = 1;
+
5035#endif
+
5036
+
5037 /* not declared globally, to restrict usage of this function */
+
5038 struct fuse_session *fuse_session_new_versioned(
+
5039 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5040 size_t op_size, struct libfuse_version *version,
+
5041 void *userdata);
+
5042 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5043 f);
+
5044 if (f->se == NULL)
+
5045 goto out_free_fs;
+
5046
+
5047 if (f->conf.debug) {
+
5048 fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok);
+
5049 }
+
5050
+
5051 /* Trace topmost layer by default */
+
5052 f->fs->debug = f->conf.debug;
+
5053 f->ctr = 0;
+
5054 f->generation = 0;
+
5055 if (node_table_init(&f->name_table) == -1)
+
5056 goto out_free_session;
+
5057
+
5058 if (node_table_init(&f->id_table) == -1)
+
5059 goto out_free_name_table;
+
5060
+
5061 pthread_mutex_init(&f->lock, NULL);
+
5062
+
5063 root = alloc_node(f);
+
5064 if (root == NULL) {
+
5065 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5066 goto out_free_id_table;
+
5067 }
+
5068 if (lru_enabled(f)) {
+
5069 struct node_lru *lnode = node_lru(root);
+
5070 init_list_head(&lnode->lru);
+
5071 }
+
5072
+
5073 strcpy(root->inline_name, "/");
+
5074 root->name = root->inline_name;
+
5075 root->parent = NULL;
+
5076 root->nodeid = FUSE_ROOT_ID;
+
5077 inc_nlookup(root);
+
5078 hash_id(f, root);
+
5079
+
5080 return f;
+
5081
+
5082out_free_id_table:
+
5083 free(f->id_table.array);
+
5084out_free_name_table:
+
5085 free(f->name_table.array);
+
5086out_free_session:
+
5087 fuse_session_destroy(f->se);
+
5088out_free_fs:
+
5089 free(f->fs);
+
5090 free(f->conf.modules);
+
5091out_delete_context_key:
+
5092 fuse_delete_context_key();
+
5093out_free:
+
5094 free(f);
+
5095out:
+
5096 return NULL;
+
5097}
+
5098
+
5099/* Emulates 3.0-style fuse_new(), which processes --help */
+
5100FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5101struct fuse *_fuse_new_30(struct fuse_args *args,
+
5102 const struct fuse_operations *op,
+
5103 size_t op_size,
+
5104 struct libfuse_version *version,
+
5105 void *user_data)
+
5106{
+
5107 struct fuse_config conf = {0};
+
5108
+
5109 const struct fuse_opt opts[] = {
+
5110 FUSE_LIB_OPT("-h", show_help, 1),
+
5111 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5113 };
+
5114
+
5115 if (fuse_opt_parse(args, &conf, opts,
+
5116 fuse_lib_opt_proc) == -1)
+
5117 return NULL;
+
5118
+
5119 if (conf.show_help) {
+
5120 fuse_lib_help(args);
+
5121 return NULL;
+
5122 } else
+
5123 return _fuse_new_31(args, op, op_size, version, user_data);
+
5124}
+
5125
+
5126/* ABI compat version */
+
5127struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5128 size_t op_size, void *user_data);
+
5129FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5130struct fuse *fuse_new_31(struct fuse_args *args,
+
5131 const struct fuse_operations *op,
+
5132 size_t op_size, void *user_data)
+
5133{
+
5134 /* unknown version */
+
5135 struct libfuse_version version = { 0 };
+
5136
+
5137 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5138}
+
5139
+
5140/*
+
5141 * ABI compat version
+
5142 * Emulates 3.0-style fuse_new(), which processes --help
+
5143 */
+
5144struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5145 size_t op_size, void *user_data);
+
5146FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5147struct fuse *fuse_new_30(struct fuse_args *args,
+
5148 const struct fuse_operations *op,
+
5149 size_t op_size, void *user_data)
+
5150{
+
5151 struct fuse_config conf = {0};
+
5152
+
5153 const struct fuse_opt opts[] = {
+
5154 FUSE_LIB_OPT("-h", show_help, 1),
+
5155 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5157 };
+
5158
+
5159 if (fuse_opt_parse(args, &conf, opts,
+
5160 fuse_lib_opt_proc) == -1)
+
5161 return NULL;
+
5162
+
5163 if (conf.show_help) {
+
5164 fuse_lib_help(args);
+
5165 return NULL;
+
5166 } else
+
5167 return fuse_new_31(args, op, op_size, user_data);
+
5168}
+
5169
+
5170
+
+
5171void fuse_destroy(struct fuse *f)
+
5172{
+
5173 size_t i;
+
5174
+
5175 if (f->conf.intr && f->intr_installed)
+
5176 fuse_restore_intr_signal(f->conf.intr_signal);
+
5177
+
5178 if (f->fs) {
+
5179 fuse_create_context(f);
+
5180
+
5181 for (i = 0; i < f->id_table.size; i++) {
+
5182 struct node *node;
+
5183
+
5184 for (node = f->id_table.array[i]; node != NULL;
+
5185 node = node->id_next) {
+
5186 if (node->is_hidden) {
+
5187 char *path;
+
5188 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5189 fuse_fs_unlink(f->fs, path);
+
5190 free(path);
+
5191 }
+
5192 }
+
5193 }
+
5194 }
+
5195 }
+
5196 for (i = 0; i < f->id_table.size; i++) {
+
5197 struct node *node;
+
5198 struct node *next;
+
5199
+
5200 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5201 next = node->id_next;
+
5202 free_node(f, node);
+
5203 f->id_table.use--;
+
5204 }
+
5205 }
+
5206 assert(list_empty(&f->partial_slabs));
+
5207 assert(list_empty(&f->full_slabs));
+
5208
+
5209 while (fuse_modules) {
+
5210 fuse_put_module(fuse_modules);
+
5211 }
+
5212 free(f->id_table.array);
+
5213 free(f->name_table.array);
+
5214 pthread_mutex_destroy(&f->lock);
+
5215 fuse_session_destroy(f->se);
+
5216 free(f->fs);
+
5217 free(f->conf.modules);
+
5218 free(f);
+
5219 fuse_delete_context_key();
+
5220}
+
+
5221
+
+
5222int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5223 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5224}
+
+
5225
+
5226
+
+
5227void fuse_unmount(struct fuse *f) {
+ +
5229}
+
+
5230
+
+ +
5232{
+
5233 return FUSE_VERSION;
+
5234}
+
+
5235
+
+
5236const char *fuse_pkgversion(void)
+
5237{
+
5238 return PACKAGE_VERSION;
+
5239}
+
+ +
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4679
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5222
+
int fuse_interrupted(void)
Definition fuse.c:4688
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5171
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4931
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4664
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4458
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5227
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4939
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
int fuse_version(void)
Definition fuse.c:5231
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + + +
struct fuse_buf buf[1]
+ + +
int32_t show_help
Definition fuse.h:279
+ + +
uint32_t no_interrupt
+
uint64_t want_ext
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:66
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:95
+
uint32_t parallel_direct_writes
+
uint32_t noflush
Definition fuse_common.h:99
+
uint32_t flush
Definition fuse_common.h:80
+
uint32_t direct_io
Definition fuse_common.h:69
+
uint32_t keep_cache
Definition fuse_common.h:75
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+ + + + +
+ + + + diff --git a/doc/html/fuse_8h.html b/doc/html/fuse_8h.html new file mode 100644 index 0000000..e69ad23 --- /dev/null +++ b/doc/html/fuse_8h.html @@ -0,0 +1,838 @@ + + + + + + + +libfuse: include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1395 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 87 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1366 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: all file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+
+ +

Definition at line 58 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 42 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4458 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5171 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4664 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4879 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4669 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4545 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4679 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4688 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4698 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4769 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4602 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+ +

Definition at line 307 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5222 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 475 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4931 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4939 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5227 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse_8h_source.html b/doc/html/fuse_8h_source.html new file mode 100644 index 0000000..8d59444 --- /dev/null +++ b/doc/html/fuse_8h_source.html @@ -0,0 +1,645 @@ + + + + + + + +libfuse: include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
37struct fuse;
+
38
+
+ + +
52 FUSE_READDIR_PLUS = (1 << 0)
+
53};
+
+
54
+
+ + +
69 FUSE_FILL_DIR_PLUS = (1 << 1)
+
70};
+
+
71
+
87typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
88 const struct stat *stbuf, off_t off,
+
89 enum fuse_fill_dir_flags flags);
+
+ +
106 int32_t set_gid;
+
107 uint32_t gid;
+
108
+
113 int32_t set_uid;
+
114 uint32_t uid;
+
115
+
120 int32_t set_mode;
+
121 uint32_t umask;
+
122
+ +
128
+ +
138
+ +
144
+
148 int32_t intr;
+
149
+
155 int32_t intr_signal;
+
156
+
167 int32_t remember;
+
168
+
185 int32_t hard_remove;
+
186
+
198 int32_t use_ino;
+
199
+
207 int32_t readdir_ino;
+
208
+
226 int32_t direct_io;
+
227
+ +
246
+
253 int32_t auto_cache;
+
254
+
255 /*
+
256 * The timeout in seconds for which file attributes are cached
+
257 * for the purpose of checking if auto_cache should flush the
+
258 * file data on open.
+
259 */
+
260 int32_t ac_attr_timeout_set;
+
261 double ac_attr_timeout;
+
262
+
273 int32_t nullpath_ok;
+
274
+
279 int32_t show_help;
+
280 char *modules;
+
281 int32_t debug;
+
282
+
288 uint32_t fmask;
+
289 uint32_t dmask;
+
290
+ +
298
+ +
313
+
314
+
318 uint32_t flags;
+
319
+
323 uint64_t reserved[48];
+
324};
+
+
325
+
326
+
+ +
361 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
362
+
371 int (*readlink) (const char *, char *, size_t);
+
372
+
379 int (*mknod) (const char *, mode_t, dev_t);
+
380
+
387 int (*mkdir) (const char *, mode_t);
+
388
+
390 int (*unlink) (const char *);
+
391
+
393 int (*rmdir) (const char *);
+
394
+
396 int (*symlink) (const char *, const char *);
+
397
+
407 int (*rename) (const char *, const char *, unsigned int flags);
+
408
+
410 int (*link) (const char *, const char *);
+
411
+
417 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
418
+
427 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
428
+
437 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
438
+
486 int (*open) (const char *, struct fuse_file_info *);
+
487
+
497 int (*read) (const char *, char *, size_t, off_t,
+
498 struct fuse_file_info *);
+
499
+
509 int (*write) (const char *, const char *, size_t, off_t,
+
510 struct fuse_file_info *);
+
511
+
516 int (*statfs) (const char *, struct statvfs *);
+
517
+
546 int (*flush) (const char *, struct fuse_file_info *);
+
547
+
560 int (*release) (const char *, struct fuse_file_info *);
+
561
+
567 int (*fsync) (const char *, int, struct fuse_file_info *);
+
568
+
570 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
571
+
573 int (*getxattr) (const char *, const char *, char *, size_t);
+
574
+
576 int (*listxattr) (const char *, char *, size_t);
+
577
+
579 int (*removexattr) (const char *, const char *);
+
580
+
589 int (*opendir) (const char *, struct fuse_file_info *);
+
590
+
613 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
614 struct fuse_file_info *, enum fuse_readdir_flags);
+
615
+
621 int (*releasedir) (const char *, struct fuse_file_info *);
+
622
+
631 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
632
+
641 void *(*init) (struct fuse_conn_info *conn,
+
642 struct fuse_config *cfg);
+
643
+
649 void (*destroy) (void *private_data);
+
650
+
660 int (*access) (const char *, int);
+
661
+
672 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
673
+
704 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
705 struct flock *);
+
706
+
719 int (*utimens) (const char *, const struct timespec tv[2],
+
720 struct fuse_file_info *fi);
+
721
+
728 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
729
+
730#if FUSE_USE_VERSION < 35
+
731 int (*ioctl) (const char *, int cmd, void *arg,
+
732 struct fuse_file_info *, unsigned int flags, void *data);
+
733#else
+
750 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
751 struct fuse_file_info *, unsigned int flags, void *data);
+
752#endif
+
753
+
769 int (*poll) (const char *, struct fuse_file_info *,
+
770 struct fuse_pollhandle *ph, unsigned *reventsp);
+
771
+
781 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
782 struct fuse_file_info *);
+
783
+
798 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
799 size_t size, off_t off, struct fuse_file_info *);
+
818 int (*flock) (const char *, struct fuse_file_info *, int op);
+
819
+
828 int (*fallocate) (const char *, int, off_t, off_t,
+
829 struct fuse_file_info *);
+
830
+
843 ssize_t (*copy_file_range) (const char *path_in,
+
844 struct fuse_file_info *fi_in,
+
845 off_t offset_in, const char *path_out,
+
846 struct fuse_file_info *fi_out,
+
847 off_t offset_out, size_t size, int flags);
+
848
+
852 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
853};
+
+
854
+
+ +
862 struct fuse *fuse;
+
863
+
865 uid_t uid;
+
866
+
868 gid_t gid;
+
869
+
871 pid_t pid;
+
872
+ +
875
+
877 mode_t umask;
+
878};
+
+
879
+
885int fuse_main_real_versioned(int argc, char *argv[],
+
886 const struct fuse_operations *op, size_t op_size,
+
887 struct libfuse_version *version, void *user_data);
+
888static inline int fuse_main_real(int argc, char *argv[],
+
889 const struct fuse_operations *op,
+
890 size_t op_size, void *user_data)
+
891{
+
892 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
893 .minor = FUSE_MINOR_VERSION,
+
894 .hotfix = FUSE_HOTFIX_VERSION,
+
895 .padding = 0 };
+
896
+
897 fuse_log(FUSE_LOG_ERR,
+
898 "%s is a libfuse internal function, please use fuse_main()\n",
+
899 __func__);
+
900
+
901 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
902 user_data);
+
903}
+
904
+
959static inline int fuse_main_fn(int argc, char *argv[],
+
960 const struct fuse_operations *op,
+
961 void *user_data)
+
962{
+
963 struct libfuse_version version = {
+
964 .major = FUSE_MAJOR_VERSION,
+
965 .minor = FUSE_MINOR_VERSION,
+
966 .hotfix = FUSE_HOTFIX_VERSION,
+
967 .padding = 0
+
968 };
+
969
+
970 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
971 user_data);
+
972}
+
973#define fuse_main(argc, argv, op, user_data) \
+
974 fuse_main_fn(argc, argv, op, user_data)
+
975
+
976/* ----------------------------------------------------------- *
+
977 * More detailed API *
+
978 * ----------------------------------------------------------- */
+
979
+
991void fuse_lib_help(struct fuse_args *args);
+
992
+
993/* Do not call this directly, use fuse_new() instead */
+
994struct fuse *_fuse_new_30(struct fuse_args *args,
+
995 const struct fuse_operations *op, size_t op_size,
+
996 struct libfuse_version *version, void *user_data);
+
997struct fuse *_fuse_new_31(struct fuse_args *args,
+
998 const struct fuse_operations *op, size_t op_size,
+
999 struct libfuse_version *version, void *user_data);
+
1000
+
1028#if FUSE_USE_VERSION == 30
+
1029static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1030 const struct fuse_operations *op,
+
1031 size_t op_size, void *user_data)
+
1032{
+
1033 struct libfuse_version version = {
+
1034 .major = FUSE_MAJOR_VERSION,
+
1035 .minor = FUSE_MINOR_VERSION,
+
1036 .hotfix = FUSE_HOTFIX_VERSION,
+
1037 .padding = 0
+
1038 };
+
1039
+
1040 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1041}
+
1042#else /* FUSE_USE_VERSION */
+
1043static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1044 const struct fuse_operations *op,
+
1045 size_t op_size, void *user_data)
+
1046{
+
1047 struct libfuse_version version = {
+
1048 .major = FUSE_MAJOR_VERSION,
+
1049 .minor = FUSE_MINOR_VERSION,
+
1050 .hotfix = FUSE_HOTFIX_VERSION,
+
1051 .padding = 0
+
1052 };
+
1053
+
1054 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1055}
+
1056#endif
+
1057#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1058
+
1067int fuse_mount(struct fuse *f, const char *mountpoint);
+
1068
+
1076void fuse_unmount(struct fuse *f);
+
1077
+
1086void fuse_destroy(struct fuse *f);
+
1087
+
1103int fuse_loop(struct fuse *f);
+
1104
+
1113void fuse_exit(struct fuse *f);
+
1114
+
1115#if FUSE_USE_VERSION < 32
+
1116int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1117#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1118#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1119int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1120#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1121#else
+
1153#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1154int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1155#else
+
1156#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1157#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1158#endif
+
1159
+
1160
+
1169struct fuse_context *fuse_get_context(void);
+
1170
+
1189int fuse_getgroups(int size, gid_t list[]);
+
1190
+
1196int fuse_interrupted(void);
+
1197
+
1209int fuse_invalidate_path(struct fuse *f, const char *path);
+
1210
+ +
1219
+
1226void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1227
+
1237int fuse_clean_cache(struct fuse *fuse);
+
1238
+
1239/*
+
1240 * Stacking API
+
1241 */
+
1242
+
1248struct fuse_fs;
+
1249
+
1250/*
+
1251 * These functions call the relevant filesystem operation, and return
+
1252 * the result.
+
1253 *
+
1254 * If the operation is not defined, they return -ENOSYS, with the
+
1255 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1256 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1257 */
+
1258
+
1259int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1260 struct fuse_file_info *fi);
+
1261int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1262 const char *newpath, unsigned int flags);
+
1263int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1264int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1265int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1266 const char *path);
+
1267int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1268int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1269 struct fuse_file_info *fi);
+
1270int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1271 struct fuse_file_info *fi);
+
1272int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1273 off_t off, struct fuse_file_info *fi);
+
1274int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1275 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1276 struct fuse_file_info *fi);
+
1277int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1278 size_t size, off_t off, struct fuse_file_info *fi);
+
1279int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1280 struct fuse_bufvec *buf, off_t off,
+
1281 struct fuse_file_info *fi);
+
1282int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1283 struct fuse_file_info *fi);
+
1284int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1285 struct fuse_file_info *fi);
+
1286int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1287int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1290 fuse_fill_dir_t filler, off_t off,
+
1291 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1292int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1293 struct fuse_file_info *fi);
+
1294int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1295 struct fuse_file_info *fi);
+
1296int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1297 struct fuse_file_info *fi);
+
1298int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1299 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1300int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1301 struct fuse_file_info *fi, int op);
+
1302int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1303 struct fuse_file_info *fi);
+
1304int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1305 struct fuse_file_info *fi);
+
1306int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1307 struct fuse_file_info *fi);
+
1308int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1309 const struct timespec tv[2], struct fuse_file_info *fi);
+
1310int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1311int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1312 size_t len);
+
1313int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1314 dev_t rdev);
+
1315int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1316int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1317 const char *value, size_t size, int flags);
+
1318int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1319 char *value, size_t size);
+
1320int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1321 size_t size);
+
1322int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1323 const char *name);
+
1324int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1325 uint64_t *idx);
+
1326#if FUSE_USE_VERSION < 35
+
1327int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1328 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1329 void *data);
+
1330#else
+
1331int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1332 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1333 void *data);
+
1334#endif
+
1335int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1336 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1337 unsigned *reventsp);
+
1338int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1339 off_t offset, off_t length, struct fuse_file_info *fi);
+
1340ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1341 struct fuse_file_info *fi_in, off_t off_in,
+
1342 const char *path_out,
+
1343 struct fuse_file_info *fi_out, off_t off_out,
+
1344 size_t len, int flags);
+
1345off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1346 struct fuse_file_info *fi);
+
1347void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1348 struct fuse_config *cfg);
+
1349void fuse_fs_destroy(struct fuse_fs *fs);
+
1350
+
1351int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1352
+
1366struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1367 void *private_data);
+
1368
+
1383typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1384 struct fuse_fs *fs[]);
+
+
1395#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1396 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1397
+
1399struct fuse_session *fuse_get_session(struct fuse *f);
+
1400
+
1409int fuse_open_channel(const char *mountpoint, const char *options);
+
1410
+
1411#ifdef __cplusplus
+
1412}
+
1413#endif
+
1414
+
1415#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4679
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5222
+
int fuse_interrupted(void)
Definition fuse.c:4688
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5171
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4931
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4664
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4458
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:475
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5227
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4939
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+ +
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+ + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* unlink)(const char *)
Definition fuse.h:390
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/fuse__common_8h.html b/doc/html/fuse__common_8h.html new file mode 100644 index 0000000..a1793ff --- /dev/null +++ b/doc/html/fuse__common_8h.html @@ -0,0 +1,1139 @@ + + + + + + + +libfuse: include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include <stdbool.h>
+#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1 << 0)
 
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
 
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
 
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
 
#define FUSE_CAP_DONT_MASK   (1 << 6)
 
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
 
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
 
#define FUSE_CAP_SPLICE_READ   (1 << 9)
 
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
 
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
 
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
 
#define FUSE_CAP_READDIRPLUS   (1 << 13)
 
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
 
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
 
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
 
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
 
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
 
#define FUSE_CAP_POSIX_ACL   (1 << 19)
 
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
 
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
 
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
 
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
 
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
 
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
 
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
 
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
 
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
 
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
 
#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 673 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 336 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1 << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 185 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 202 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 289 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 426 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 496 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1 << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 222 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 480 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 464 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 214 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 260 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 396 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 413 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 267 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 516 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 360 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 441 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 368 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 508 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1 << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 387 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 193 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1 << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 297 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 325 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 487 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 238 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1 << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 247 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 230 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 345 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 528 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 839 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 808 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 412 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 459 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5236 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1907 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 185 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 163 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 138 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5231 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse__common_8h_source.html b/doc/html/fuse__common_8h_source.html new file mode 100644 index 0000000..6ed9adf --- /dev/null +++ b/doc/html/fuse__common_8h_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file COPYING.LIB.
+
6*/
+
7
+
10#include <stdbool.h>
+
11#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
12#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
13#endif
+
14
+
15#ifndef FUSE_COMMON_H_
+
16#define FUSE_COMMON_H_
+
17
+
18#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
19#include "fuse_config.h"
+
20#endif
+
21
+
22#include "libfuse_config.h"
+
23
+
24#include "fuse_opt.h"
+
25#include "fuse_log.h"
+
26#include <stdint.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#ifdef HAVE_STATIC_ASSERT
+
34#define fuse_static_assert(condition, message) static_assert(condition, message)
+
35#else
+
36#define fuse_static_assert(condition, message)
+
37#endif
+
38
+
39#ifdef __cplusplus
+
40extern "C" {
+
41#endif
+
42
+
+ +
58 int32_t flags;
+
59
+
66 uint32_t writepage : 1;
+
67
+
69 uint32_t direct_io : 1;
+
70
+
75 uint32_t keep_cache : 1;
+
76
+
80 uint32_t flush : 1;
+
81
+
84 uint32_t nonseekable : 1;
+
85
+
86 /* Indicates that flock locks for this file should be
+
87 released. If set, lock_owner shall contain a valid value.
+
88 May only be set in ->release(). */
+
89 uint32_t flock_release : 1;
+
90
+
95 uint32_t cache_readdir : 1;
+
96
+
99 uint32_t noflush : 1;
+
100
+ +
104
+
106 uint32_t padding : 23;
+
107 uint32_t padding2 : 32;
+
108 uint32_t padding3 : 32;
+
109
+
113 uint64_t fh;
+
114
+
116 uint64_t lock_owner;
+
117
+
120 uint32_t poll_events;
+
121
+
125 int32_t backing_id;
+
126
+
128 uint64_t compat_flags;
+
129
+
130 uint64_t reserved[2];
+
131};
+
+
132fuse_static_assert(sizeof(struct fuse_file_info) == 64,
+
133 "fuse_file_info size mismatch");
+
134
+
145#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
146struct fuse_loop_config_v1; /* forward declaration */
+
+ +
148#else
+
149struct fuse_loop_config_v1 {
+
150#endif
+ +
156
+
167 unsigned int max_idle_threads;
+
168};
+
+
169
+
170
+
171/**************************************************************************
+
172 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
173 **************************************************************************/
+
174
+
185#define FUSE_CAP_ASYNC_READ (1 << 0)
+
186
+
193#define FUSE_CAP_POSIX_LOCKS (1 << 1)
+
194
+
202#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
+
203
+
214#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
+
215
+
222#define FUSE_CAP_DONT_MASK (1 << 6)
+
223
+
230#define FUSE_CAP_SPLICE_WRITE (1 << 7)
+
231
+
238#define FUSE_CAP_SPLICE_MOVE (1 << 8)
+
239
+
247#define FUSE_CAP_SPLICE_READ (1 << 9)
+
248
+
260#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
+
261
+
267#define FUSE_CAP_IOCTL_DIR (1 << 11)
+
268
+
289#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12)
+
290
+
297#define FUSE_CAP_READDIRPLUS (1 << 13)
+
298
+
325#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14)
+
326
+
336#define FUSE_CAP_ASYNC_DIO (1 << 15)
+
337
+
345#define FUSE_CAP_WRITEBACK_CACHE (1 << 16)
+
346
+
360#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17)
+
361
+
368#define FUSE_CAP_PARALLEL_DIROPS (1 << 18)
+
369
+
387#define FUSE_CAP_POSIX_ACL (1 << 19)
+
388
+
396#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20)
+
397
+
413#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21)
+
414
+
426#define FUSE_CAP_CACHE_SYMLINKS (1 << 23)
+
427
+
441#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
+
442
+
464#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25)
+
465
+
480#define FUSE_CAP_EXPIRE_ONLY (1 << 26)
+
481
+
487#define FUSE_CAP_SETXATTR_EXT (1 << 27)
+
488
+
496#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1 << 28)
+
497
+
508#define FUSE_CAP_PASSTHROUGH (1 << 29)
+
509
+
516#define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30)
+
517
+
528#define FUSE_IOCTL_COMPAT (1 << 0)
+
529#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
530#define FUSE_IOCTL_RETRY (1 << 2)
+
531#define FUSE_IOCTL_DIR (1 << 4)
+
532
+
533#define FUSE_IOCTL_MAX_IOV 256
+
534
+
+ +
550 uint32_t proto_major;
+
551
+
555 uint32_t proto_minor;
+
556
+
560 uint32_t max_write;
+
561
+
574 uint32_t max_read;
+
575
+ +
580
+
586 uint32_t capable;
+
587
+
598 uint32_t want;
+
599
+ +
629
+ +
639
+
655 uint32_t time_gran;
+
656
+
673#define FUSE_BACKING_STACKED_UNDER (0)
+
674#define FUSE_BACKING_STACKED_OVER (1)
+
675 uint32_t max_backing_stack_depth;
+
676
+
685 uint32_t no_interrupt : 1;
+
686
+
687 /* reserved bits for future use */
+
688 uint32_t padding : 31;
+
689
+
694 uint64_t capable_ext;
+
695
+
704 uint64_t want_ext;
+
705
+
709 uint32_t reserved[16];
+
710};
+
+
711fuse_static_assert(sizeof(struct fuse_conn_info) == 128,
+
712 "Size of struct fuse_conn_info must be 128 bytes");
+
713
+
714struct fuse_session;
+
715struct fuse_pollhandle;
+
716struct fuse_conn_info_opts;
+
717
+
760struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
761
+
769void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
770 struct fuse_conn_info *conn);
+
771
+
778int fuse_daemonize(int foreground);
+
779
+
785int fuse_version(void);
+
786
+
792const char *fuse_pkgversion(void);
+
793
+
799void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
800
+
801/* ----------------------------------------------------------- *
+
802 * Data buffer *
+
803 * ----------------------------------------------------------- */
+
804
+
+ +
815 FUSE_BUF_IS_FD = (1 << 1),
+
816
+ +
825
+
833 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
835
+
+ + +
850
+ +
858
+ +
867
+ + +
+
877
+
+
884struct fuse_buf {
+
888 size_t size;
+
889
+ +
894
+
900 void *mem;
+
901
+
907 int fd;
+
908
+
914 off_t pos;
+
915
+
922 size_t mem_size;
+
923};
+
+
924
+
+ +
937 size_t count;
+
938
+
942 size_t idx;
+
943
+
947 size_t off;
+
948
+
952 struct fuse_buf buf[1];
+
953};
+
+
954
+
+ +
960{
+
961 uint32_t major;
+
962 uint32_t minor;
+
963 uint32_t hotfix;
+
964 uint32_t padding;
+
965};
+
+
966
+
967/* Initialize bufvec with a single buffer of given size */
+
968#define FUSE_BUFVEC_INIT(size__) \
+
969 ((struct fuse_bufvec) { \
+
970 /* .count= */ 1, \
+
971 /* .idx = */ 0, \
+
972 /* .off = */ 0, \
+
973 /* .buf = */ { /* [0] = */ { \
+
974 /* .size = */ (size__), \
+
975 /* .flags = */ (enum fuse_buf_flags) 0, \
+
976 /* .mem = */ NULL, \
+
977 /* .fd = */ -1, \
+
978 /* .pos = */ 0, \
+
979 /* .mem_size = */ 0, \
+
980 } } \
+
981 } )
+
982
+
989size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
990
+
999ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1000 enum fuse_buf_copy_flags flags);
+
1001
+
1002/* ----------------------------------------------------------- *
+
1003 * Signal handling *
+
1004 * ----------------------------------------------------------- */
+
1005
+
1021int fuse_set_signal_handlers(struct fuse_session *se);
+
1022
+
1038int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1039
+
1051void fuse_remove_signal_handlers(struct fuse_session *se);
+
1052
+
1058#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1064struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1065
+
1069void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1070
+
1074void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1075 unsigned int value);
+
1076
+
1080void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1081 unsigned int value);
+
1082
+
1086void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1087 unsigned int value);
+
1088
+
1095void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1096 struct fuse_loop_config_v1 *v1_conf);
+
1097#endif
+
1098
+
1099
+
1100static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
1101 uint64_t flag)
+
1102{
+
1103 if (conn->capable_ext & flag) {
+
1104 conn->want_ext |= flag;
+
1105 return true;
+
1106 }
+
1107 return false;
+
1108}
+
1109
+
1110static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
1111 uint64_t flag)
+
1112{
+
1113 conn->want_ext &= ~flag;
+
1114}
+
1115
+
1116static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
1117 uint64_t flag)
+
1118{
+
1119 return conn->capable_ext & flag ? true : false;
+
1120}
+
1121
+
1122/* ----------------------------------------------------------- *
+
1123 * Compatibility stuff *
+
1124 * ----------------------------------------------------------- */
+
1125
+
1126#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1127# error only API version 30 or greater is supported
+
1128#endif
+
1129
+
1130#ifdef __cplusplus
+
1131}
+
1132#endif
+
1133
+
1134
+
1135/*
+
1136 * This interface uses 64 bit off_t.
+
1137 *
+
1138 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1139 */
+
1140
+
1141#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1142_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1143#else
+
1144struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1145 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1146#endif
+
1147
+
1148#endif /* FUSE_COMMON_H_ */
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:459
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5231
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ + + + +
enum fuse_buf_flags flags
+
size_t mem_size
+
void * mem
+ +
off_t pos
+
size_t size
+ + + + + +
uint32_t time_gran
+
uint32_t proto_major
+ +
uint32_t congestion_threshold
+
uint32_t proto_minor
+
uint32_t max_write
+
uint64_t capable_ext
+
uint32_t max_readahead
+
uint32_t no_interrupt
+
uint32_t max_read
+
uint32_t max_background
+
uint32_t capable
+
uint64_t want_ext
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:66
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:95
+
uint32_t nonseekable
Definition fuse_common.h:84
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t padding
+
uint32_t noflush
Definition fuse_common.h:99
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:80
+
uint32_t direct_io
Definition fuse_common.h:69
+
uint32_t keep_cache
Definition fuse_common.h:75
+ + + +
unsigned int max_idle_threads
+ +
+ + + + diff --git a/doc/html/fuse__config_8h_source.html b/doc/html/fuse__config_8h_source.html new file mode 100644 index 0000000..f4aa1f7 --- /dev/null +++ b/doc/html/fuse__config_8h_source.html @@ -0,0 +1,105 @@ + + + + + + + +libfuse: build-ubuntu/fuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define HAVE_BACKTRACE
+
9
+
10#define HAVE_CLOSE_RANGE
+
11
+
12#define HAVE_COPY_FILE_RANGE
+
13
+
14#define HAVE_FALLOCATE
+
15
+
16#define HAVE_FDATASYNC
+
17
+
18#define HAVE_FORK
+
19
+
20#define HAVE_FSTATAT
+
21
+
22#define HAVE_ICONV
+
23
+
24#define HAVE_OPENAT
+
25
+
26#define HAVE_PIPE2
+
27
+
28#define HAVE_POSIX_FALLOCATE
+
29
+
30#define HAVE_READLINKAT
+
31
+
32#define HAVE_SETXATTR
+
33
+
34#define HAVE_SPLICE
+
35
+
36#define HAVE_STRUCT_STAT_ST_ATIM
+
37
+
38#undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
39
+
40#define HAVE_UTIMENSAT
+
41
+
42#define HAVE_VMSPLICE
+
43
+
44#define PACKAGE_VERSION "3.17.1-rc1"
+
45
+
+ + + + diff --git a/doc/html/fuse__i_8h_source.html b/doc/html/fuse__i_8h_source.html new file mode 100644 index 0000000..279e38a --- /dev/null +++ b/doc/html/fuse__i_8h_source.html @@ -0,0 +1,292 @@ + + + + + + + +libfuse: lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include "fuse.h"
+
10#include "fuse_lowlevel.h"
+
11#include "util.h"
+
12
+
13#include <stdint.h>
+
14#include <stdbool.h>
+
15#include <errno.h>
+
16
+
17#define MIN(a, b) \
+
18({ \
+
19 typeof(a) _a = (a); \
+
20 typeof(b) _b = (b); \
+
21 _a < _b ? _a : _b; \
+
22})
+
23
+
24struct mount_opts;
+
25
+
26struct fuse_req {
+
27 struct fuse_session *se;
+
28 uint64_t unique;
+
29 _Atomic int ref_cnt;
+
30 pthread_mutex_t lock;
+
31 struct fuse_ctx ctx;
+
32 struct fuse_chan *ch;
+
33 int interrupted;
+
34 unsigned int ioctl_64bit : 1;
+
35 union {
+
36 struct {
+
37 uint64_t unique;
+
38 } i;
+
39 struct {
+ +
41 void *data;
+
42 } ni;
+
43 } u;
+
44 struct fuse_req *next;
+
45 struct fuse_req *prev;
+
46};
+
47
+
48struct fuse_notify_req {
+
49 uint64_t unique;
+
50 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
51 const void *, const struct fuse_buf *);
+
52 struct fuse_notify_req *next;
+
53 struct fuse_notify_req *prev;
+
54};
+
55
+
56struct fuse_session {
+
57 char *mountpoint;
+
58 volatile int exited;
+
59 int fd;
+
60 struct fuse_custom_io *io;
+
61 struct mount_opts *mo;
+
62 int debug;
+
63 int deny_others;
+
64 struct fuse_lowlevel_ops op;
+
65 int got_init;
+
66 struct cuse_data *cuse_data;
+
67 void *userdata;
+
68 uid_t owner;
+
69 struct fuse_conn_info conn;
+
70 struct fuse_req list;
+
71 struct fuse_req interrupts;
+
72 pthread_mutex_t lock;
+
73 int got_destroy;
+
74 pthread_key_t pipe_key;
+
75 int broken_splice_nonblock;
+
76 uint64_t notify_ctr;
+
77 struct fuse_notify_req notify_list;
+
78 _Atomic size_t bufsize;
+
79 int error;
+
80
+
81 /* This is useful if any kind of ABI incompatibility is found at
+
82 * a later version, to 'fix' it at run time.
+
83 */
+
84 struct libfuse_version version;
+
85
+
86 /* true if reading requests from /dev/fuse are handled internally */
+
87 bool buf_reallocable;
+
88};
+
89
+
90struct fuse_chan {
+
91 pthread_mutex_t lock;
+
92 int ctr;
+
93 int fd;
+
94};
+
95
+
+ +
104 char *name;
+
105 fuse_module_factory_t factory;
+
106 struct fuse_module *next;
+
107 struct fusemod_so *so;
+
108 int ctr;
+
109};
+
+
110
+
119#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
120struct fuse_loop_config
+
121{
+
122 /* verififier that a correct struct was was passed. This is especially
+
123 * needed, as versions below (3, 12) were using a public struct
+
124 * (now called fuse_loop_config_v1), which was hard to extend with
+
125 * additional parameters, without risking that file system implementations
+
126 * would not have noticed and might either pass uninitialized members
+
127 * or even too small structs.
+
128 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
129 * or 1. v2 or even higher version just need to set a value here
+
130 * which not conflicting and very unlikely as having been set by
+
131 * file system implementation.
+
132 */
+
133 int version_id;
+
134
+
139 int clone_fd;
+ +
152
+
158 unsigned int max_threads;
+
159};
+
160#endif
+
161
+
162/* ----------------------------------------------------------- *
+
163 * Channel interface (when using -o clone_fd) *
+
164 * ----------------------------------------------------------- */
+
165
+
172struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
173
+
179void fuse_chan_put(struct fuse_chan *ch);
+
180
+
181struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
182void destroy_mount_opts(struct mount_opts *mo);
+
183void fuse_mount_version(void);
+
184unsigned get_max_read(struct mount_opts *o);
+
185void fuse_kern_unmount(const char *mountpoint, int fd);
+
186int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
187
+
188int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
189 int count);
+
190void fuse_free_req(fuse_req_t req);
+
191
+
192void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
193
+
194int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
195
+
196void fuse_buf_free(struct fuse_buf *buf);
+
197
+
198int fuse_session_receive_buf_internal(struct fuse_session *se,
+
199 struct fuse_buf *buf,
+
200 struct fuse_chan *ch);
+
201void fuse_session_process_buf_internal(struct fuse_session *se,
+
202 const struct fuse_buf *buf,
+
203 struct fuse_chan *ch);
+
204
+
205struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
206 size_t op_size, void *private_data);
+
207int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
208int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
209
+
215int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
216
+
217
+
218/*
+
219 * This can be changed dynamically on recent kernels through the
+
220 * /proc/sys/fs/fuse/max_pages_limit interface.
+
221 *
+
222 * Older kernels will always use the default value.
+
223 */
+
224#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
225#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
226
+
227/* room needed in buffer to accommodate header */
+
228#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
229
+
233static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn,
+
234 uint64_t want_ext_default,
+
235 uint32_t want_default)
+
236{
+
237 /*
+
238 * Convert want to want_ext if necessary.
+
239 * For the high level interface this function might be called
+
240 * twice, once from the high level interface and once from the
+
241 * low level interface. Both, with different want_ext_default and
+
242 * want_default values. In order to suppress a failure for the
+
243 * second call, we check if the lower 32 bits of want_ext are
+
244 * already set to the value of want.
+
245 */
+
246 if (conn->want != want_default &&
+
247 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
248 if (conn->want_ext != want_ext_default) {
+
249 fuse_log(FUSE_LOG_ERR,
+
250 "fuse: both 'want' and 'want_ext' are set\n");
+
251 return -EINVAL;
+
252 }
+
253
+
254 /* high bits from want_ext, low bits from want */
+
255 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
256 conn->want;
+
257 }
+
258
+
259 /* ensure there won't be a second conversion */
+
260 conn->want = fuse_lower_32_bits(conn->want_ext);
+
261
+
262 return 0;
+
263}
+ +
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1383
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+ +
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + +
uint64_t want_ext
+ + + +
unsigned int max_threads
Definition fuse_i.h:158
+
int max_idle_threads
Definition fuse_i.h:151
+ + + + +
+ + + + diff --git a/doc/html/fuse__kernel_8h_source.html b/doc/html/fuse__kernel_8h_source.html new file mode 100644 index 0000000..cb08ac4 --- /dev/null +++ b/doc/html/fuse__kernel_8h_source.html @@ -0,0 +1,1097 @@ + + + + + + + +libfuse: include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 */
+
221
+
222#ifndef _LINUX_FUSE_H
+
223#define _LINUX_FUSE_H
+
224
+
225#ifdef __KERNEL__
+
226#include <linux/types.h>
+
227#else
+
228#include <stdint.h>
+
229#endif
+
230
+
231/*
+
232 * Version negotiation:
+
233 *
+
234 * Both the kernel and userspace send the version they support in the
+
235 * INIT request and reply respectively.
+
236 *
+
237 * If the major versions match then both shall use the smallest
+
238 * of the two minor versions for communication.
+
239 *
+
240 * If the kernel supports a larger major version, then userspace shall
+
241 * reply with the major version it supports, ignore the rest of the
+
242 * INIT message and expect a new INIT message from the kernel with a
+
243 * matching major version.
+
244 *
+
245 * If the library supports a larger major version, then it shall fall
+
246 * back to the major protocol version sent by the kernel for
+
247 * communication and reply with that major version (and an arbitrary
+
248 * supported minor version).
+
249 */
+
250
+
252#define FUSE_KERNEL_VERSION 7
+
253
+
255#define FUSE_KERNEL_MINOR_VERSION 40
+
256
+
258#define FUSE_ROOT_ID 1
+
259
+
260/* Make sure all structures are padded to 64bit boundary, so 32bit
+
261 userspace works under 64bit kernels */
+
262
+
263struct fuse_attr {
+
264 uint64_t ino;
+
265 uint64_t size;
+
266 uint64_t blocks;
+
267 uint64_t atime;
+
268 uint64_t mtime;
+
269 uint64_t ctime;
+
270 uint32_t atimensec;
+
271 uint32_t mtimensec;
+
272 uint32_t ctimensec;
+
273 uint32_t mode;
+
274 uint32_t nlink;
+
275 uint32_t uid;
+
276 uint32_t gid;
+
277 uint32_t rdev;
+
278 uint32_t blksize;
+
279 uint32_t flags;
+
280};
+
281
+
282/*
+
283 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
284 * Linux.
+
285 */
+
286struct fuse_sx_time {
+
287 int64_t tv_sec;
+
288 uint32_t tv_nsec;
+
289 int32_t __reserved;
+
290};
+
291
+
292struct fuse_statx {
+
293 uint32_t mask;
+
294 uint32_t blksize;
+
295 uint64_t attributes;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint16_t mode;
+
300 uint16_t __spare0[1];
+
301 uint64_t ino;
+
302 uint64_t size;
+
303 uint64_t blocks;
+
304 uint64_t attributes_mask;
+
305 struct fuse_sx_time atime;
+
306 struct fuse_sx_time btime;
+
307 struct fuse_sx_time ctime;
+
308 struct fuse_sx_time mtime;
+
309 uint32_t rdev_major;
+
310 uint32_t rdev_minor;
+
311 uint32_t dev_major;
+
312 uint32_t dev_minor;
+
313 uint64_t __spare2[14];
+
314};
+
315
+
316struct fuse_kstatfs {
+
317 uint64_t blocks;
+
318 uint64_t bfree;
+
319 uint64_t bavail;
+
320 uint64_t files;
+
321 uint64_t ffree;
+
322 uint32_t bsize;
+
323 uint32_t namelen;
+
324 uint32_t frsize;
+
325 uint32_t padding;
+
326 uint32_t spare[6];
+
327};
+
328
+
329struct fuse_file_lock {
+
330 uint64_t start;
+
331 uint64_t end;
+
332 uint32_t type;
+
333 uint32_t pid; /* tgid */
+
334};
+
335
+
339#define FATTR_MODE (1 << 0)
+
340#define FATTR_UID (1 << 1)
+
341#define FATTR_GID (1 << 2)
+
342#define FATTR_SIZE (1 << 3)
+
343#define FATTR_ATIME (1 << 4)
+
344#define FATTR_MTIME (1 << 5)
+
345#define FATTR_FH (1 << 6)
+
346#define FATTR_ATIME_NOW (1 << 7)
+
347#define FATTR_MTIME_NOW (1 << 8)
+
348#define FATTR_LOCKOWNER (1 << 9)
+
349#define FATTR_CTIME (1 << 10)
+
350#define FATTR_KILL_SUIDGID (1 << 11)
+
351
+
364#define FOPEN_DIRECT_IO (1 << 0)
+
365#define FOPEN_KEEP_CACHE (1 << 1)
+
366#define FOPEN_NONSEEKABLE (1 << 2)
+
367#define FOPEN_CACHE_DIR (1 << 3)
+
368#define FOPEN_STREAM (1 << 4)
+
369#define FOPEN_NOFLUSH (1 << 5)
+
370#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
371#define FOPEN_PASSTHROUGH (1 << 7)
+
372
+
425#define FUSE_ASYNC_READ (1 << 0)
+
426#define FUSE_POSIX_LOCKS (1 << 1)
+
427#define FUSE_FILE_OPS (1 << 2)
+
428#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
429#define FUSE_EXPORT_SUPPORT (1 << 4)
+
430#define FUSE_BIG_WRITES (1 << 5)
+
431#define FUSE_DONT_MASK (1 << 6)
+
432#define FUSE_SPLICE_WRITE (1 << 7)
+
433#define FUSE_SPLICE_MOVE (1 << 8)
+
434#define FUSE_SPLICE_READ (1 << 9)
+
435#define FUSE_FLOCK_LOCKS (1 << 10)
+
436#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
437#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
438#define FUSE_DO_READDIRPLUS (1 << 13)
+
439#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
440#define FUSE_ASYNC_DIO (1 << 15)
+
441#define FUSE_WRITEBACK_CACHE (1 << 16)
+
442#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
443#define FUSE_PARALLEL_DIROPS (1 << 18)
+
444#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
445#define FUSE_POSIX_ACL (1 << 20)
+
446#define FUSE_ABORT_ERROR (1 << 21)
+
447#define FUSE_MAX_PAGES (1 << 22)
+
448#define FUSE_CACHE_SYMLINKS (1 << 23)
+
449#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
450#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
451#define FUSE_MAP_ALIGNMENT (1 << 26)
+
452#define FUSE_SUBMOUNTS (1 << 27)
+
453#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
454#define FUSE_SETXATTR_EXT (1 << 29)
+
455#define FUSE_INIT_EXT (1 << 30)
+
456#define FUSE_INIT_RESERVED (1 << 31)
+
457/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
458#define FUSE_SECURITY_CTX (1ULL << 32)
+
459#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
460#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
461#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
462#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
463#define FUSE_PASSTHROUGH (1ULL << 37)
+
464#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
465#define FUSE_HAS_RESEND (1ULL << 39)
+
466
+
467/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
468#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
469
+
475#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
476
+
480#define FUSE_RELEASE_FLUSH (1 << 0)
+
481#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
482
+
486#define FUSE_GETATTR_FH (1 << 0)
+
487
+
491#define FUSE_LK_FLOCK (1 << 0)
+
492
+
500#define FUSE_WRITE_CACHE (1 << 0)
+
501#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
502#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
503
+
504/* Obsolete alias; this flag implies killing suid/sgid only. */
+
505#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
506
+
510#define FUSE_READ_LOCKOWNER (1 << 1)
+
511
+
524#define FUSE_IOCTL_COMPAT (1 << 0)
+
525#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
526#define FUSE_IOCTL_RETRY (1 << 2)
+
527#define FUSE_IOCTL_32BIT (1 << 3)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
530
+
531#define FUSE_IOCTL_MAX_IOV 256
+
532
+
538#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
539
+
545#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
546
+
553#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
554#define FUSE_ATTR_DAX (1 << 1)
+
555
+
560#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
561
+
566#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
567
+
572#define FUSE_EXPIRE_ONLY (1 << 0)
+
573
+
579enum fuse_ext_type {
+
580 /* Types 0..31 are reserved for fuse_secctx_header */
+
581 FUSE_MAX_NR_SECCTX = 31,
+
582 FUSE_EXT_GROUPS = 32,
+
583};
+
584
+
585enum fuse_opcode {
+
586 FUSE_LOOKUP = 1,
+
587 FUSE_FORGET = 2, /* no reply */
+
588 FUSE_GETATTR = 3,
+
589 FUSE_SETATTR = 4,
+
590 FUSE_READLINK = 5,
+
591 FUSE_SYMLINK = 6,
+
592 FUSE_MKNOD = 8,
+
593 FUSE_MKDIR = 9,
+
594 FUSE_UNLINK = 10,
+
595 FUSE_RMDIR = 11,
+
596 FUSE_RENAME = 12,
+
597 FUSE_LINK = 13,
+
598 FUSE_OPEN = 14,
+
599 FUSE_READ = 15,
+
600 FUSE_WRITE = 16,
+
601 FUSE_STATFS = 17,
+
602 FUSE_RELEASE = 18,
+
603 FUSE_FSYNC = 20,
+
604 FUSE_SETXATTR = 21,
+
605 FUSE_GETXATTR = 22,
+
606 FUSE_LISTXATTR = 23,
+
607 FUSE_REMOVEXATTR = 24,
+
608 FUSE_FLUSH = 25,
+
609 FUSE_INIT = 26,
+
610 FUSE_OPENDIR = 27,
+
611 FUSE_READDIR = 28,
+
612 FUSE_RELEASEDIR = 29,
+
613 FUSE_FSYNCDIR = 30,
+
614 FUSE_GETLK = 31,
+
615 FUSE_SETLK = 32,
+
616 FUSE_SETLKW = 33,
+
617 FUSE_ACCESS = 34,
+
618 FUSE_CREATE = 35,
+
619 FUSE_INTERRUPT = 36,
+
620 FUSE_BMAP = 37,
+
621 FUSE_DESTROY = 38,
+
622 FUSE_IOCTL = 39,
+
623 FUSE_POLL = 40,
+
624 FUSE_NOTIFY_REPLY = 41,
+
625 FUSE_BATCH_FORGET = 42,
+
626 FUSE_FALLOCATE = 43,
+
627 FUSE_READDIRPLUS = 44,
+
628 FUSE_RENAME2 = 45,
+
629 FUSE_LSEEK = 46,
+
630 FUSE_COPY_FILE_RANGE = 47,
+
631 FUSE_SETUPMAPPING = 48,
+
632 FUSE_REMOVEMAPPING = 49,
+
633 FUSE_SYNCFS = 50,
+
634 FUSE_TMPFILE = 51,
+
635 FUSE_STATX = 52,
+
636
+
637 /* CUSE specific operations */
+
638 CUSE_INIT = 4096,
+
639
+
640 /* Reserved opcodes: helpful to detect structure endian-ness */
+
641 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
642 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
643};
+
644
+
645enum fuse_notify_code {
+
646 FUSE_NOTIFY_POLL = 1,
+
647 FUSE_NOTIFY_INVAL_INODE = 2,
+
648 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
649 FUSE_NOTIFY_STORE = 4,
+
650 FUSE_NOTIFY_RETRIEVE = 5,
+
651 FUSE_NOTIFY_DELETE = 6,
+
652 FUSE_NOTIFY_RESEND = 7,
+
653 FUSE_NOTIFY_CODE_MAX,
+
654};
+
655
+
656/* The read buffer is required to be at least 8k, but may be much larger */
+
657#define FUSE_MIN_READ_BUFFER 8192
+
658
+
659#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
660
+
661struct fuse_entry_out {
+
662 uint64_t nodeid; /* Inode ID */
+
663 uint64_t generation; /* Inode generation: nodeid:gen must
+
664 be unique for the fs's lifetime */
+
665 uint64_t entry_valid; /* Cache timeout for the name */
+
666 uint64_t attr_valid; /* Cache timeout for the attributes */
+
667 uint32_t entry_valid_nsec;
+
668 uint32_t attr_valid_nsec;
+
669 struct fuse_attr attr;
+
670};
+
671
+
672struct fuse_forget_in {
+
673 uint64_t nlookup;
+
674};
+
675
+
676struct fuse_forget_one {
+
677 uint64_t nodeid;
+
678 uint64_t nlookup;
+
679};
+
680
+
681struct fuse_batch_forget_in {
+
682 uint32_t count;
+
683 uint32_t dummy;
+
684};
+
685
+
686struct fuse_getattr_in {
+
687 uint32_t getattr_flags;
+
688 uint32_t dummy;
+
689 uint64_t fh;
+
690};
+
691
+
692#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
693
+
694struct fuse_attr_out {
+
695 uint64_t attr_valid; /* Cache timeout for the attributes */
+
696 uint32_t attr_valid_nsec;
+
697 uint32_t dummy;
+
698 struct fuse_attr attr;
+
699};
+
700
+
701struct fuse_statx_in {
+
702 uint32_t getattr_flags;
+
703 uint32_t reserved;
+
704 uint64_t fh;
+
705 uint32_t sx_flags;
+
706 uint32_t sx_mask;
+
707};
+
708
+
709struct fuse_statx_out {
+
710 uint64_t attr_valid; /* Cache timeout for the attributes */
+
711 uint32_t attr_valid_nsec;
+
712 uint32_t flags;
+
713 uint64_t spare[2];
+
714 struct fuse_statx stat;
+
715};
+
716
+
717#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
718
+
719struct fuse_mknod_in {
+
720 uint32_t mode;
+
721 uint32_t rdev;
+
722 uint32_t umask;
+
723 uint32_t padding;
+
724};
+
725
+
726struct fuse_mkdir_in {
+
727 uint32_t mode;
+
728 uint32_t umask;
+
729};
+
730
+
731struct fuse_rename_in {
+
732 uint64_t newdir;
+
733};
+
734
+
735struct fuse_rename2_in {
+
736 uint64_t newdir;
+
737 uint32_t flags;
+
738 uint32_t padding;
+
739};
+
740
+
741struct fuse_link_in {
+
742 uint64_t oldnodeid;
+
743};
+
744
+
745struct fuse_setattr_in {
+
746 uint32_t valid;
+
747 uint32_t padding;
+
748 uint64_t fh;
+
749 uint64_t size;
+
750 uint64_t lock_owner;
+
751 uint64_t atime;
+
752 uint64_t mtime;
+
753 uint64_t ctime;
+
754 uint32_t atimensec;
+
755 uint32_t mtimensec;
+
756 uint32_t ctimensec;
+
757 uint32_t mode;
+
758 uint32_t unused4;
+
759 uint32_t uid;
+
760 uint32_t gid;
+
761 uint32_t unused5;
+
762};
+
763
+
764struct fuse_open_in {
+
765 uint32_t flags;
+
766 uint32_t open_flags; /* FUSE_OPEN_... */
+
767};
+
768
+
769struct fuse_create_in {
+
770 uint32_t flags;
+
771 uint32_t mode;
+
772 uint32_t umask;
+
773 uint32_t open_flags; /* FUSE_OPEN_... */
+
774};
+
775
+
776struct fuse_open_out {
+
777 uint64_t fh;
+
778 uint32_t open_flags;
+
779 int32_t backing_id;
+
780};
+
781
+
782struct fuse_release_in {
+
783 uint64_t fh;
+
784 uint32_t flags;
+
785 uint32_t release_flags;
+
786 uint64_t lock_owner;
+
787};
+
788
+
789struct fuse_flush_in {
+
790 uint64_t fh;
+
791 uint32_t unused;
+
792 uint32_t padding;
+
793 uint64_t lock_owner;
+
794};
+
795
+
796struct fuse_read_in {
+
797 uint64_t fh;
+
798 uint64_t offset;
+
799 uint32_t size;
+
800 uint32_t read_flags;
+
801 uint64_t lock_owner;
+
802 uint32_t flags;
+
803 uint32_t padding;
+
804};
+
805
+
806#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
807
+
808struct fuse_write_in {
+
809 uint64_t fh;
+
810 uint64_t offset;
+
811 uint32_t size;
+
812 uint32_t write_flags;
+
813 uint64_t lock_owner;
+
814 uint32_t flags;
+
815 uint32_t padding;
+
816};
+
817
+
818struct fuse_write_out {
+
819 uint32_t size;
+
820 uint32_t padding;
+
821};
+
822
+
823#define FUSE_COMPAT_STATFS_SIZE 48
+
824
+
825struct fuse_statfs_out {
+
826 struct fuse_kstatfs st;
+
827};
+
828
+
829struct fuse_fsync_in {
+
830 uint64_t fh;
+
831 uint32_t fsync_flags;
+
832 uint32_t padding;
+
833};
+
834
+
835#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
836
+
837struct fuse_setxattr_in {
+
838 uint32_t size;
+
839 uint32_t flags;
+
840 uint32_t setxattr_flags;
+
841 uint32_t padding;
+
842};
+
843
+
844struct fuse_getxattr_in {
+
845 uint32_t size;
+
846 uint32_t padding;
+
847};
+
848
+
849struct fuse_getxattr_out {
+
850 uint32_t size;
+
851 uint32_t padding;
+
852};
+
853
+
854struct fuse_lk_in {
+
855 uint64_t fh;
+
856 uint64_t owner;
+
857 struct fuse_file_lock lk;
+
858 uint32_t lk_flags;
+
859 uint32_t padding;
+
860};
+
861
+
862struct fuse_lk_out {
+
863 struct fuse_file_lock lk;
+
864};
+
865
+
866struct fuse_access_in {
+
867 uint32_t mask;
+
868 uint32_t padding;
+
869};
+
870
+
871struct fuse_init_in {
+
872 uint32_t major;
+
873 uint32_t minor;
+
874 uint32_t max_readahead;
+
875 uint32_t flags;
+
876 uint32_t flags2;
+
877 uint32_t unused[11];
+
878};
+
879
+
880#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
881#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
882
+
883struct fuse_init_out {
+
884 uint32_t major;
+
885 uint32_t minor;
+
886 uint32_t max_readahead;
+
887 uint32_t flags;
+
888 uint16_t max_background;
+
889 uint16_t congestion_threshold;
+
890 uint32_t max_write;
+
891 uint32_t time_gran;
+
892 uint16_t max_pages;
+
893 uint16_t map_alignment;
+
894 uint32_t flags2;
+
895 uint32_t max_stack_depth;
+
896 uint32_t unused[6];
+
897};
+
898
+
899#define CUSE_INIT_INFO_MAX 4096
+
900
+
901struct cuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t unused;
+
905 uint32_t flags;
+
906};
+
907
+
908struct cuse_init_out {
+
909 uint32_t major;
+
910 uint32_t minor;
+
911 uint32_t unused;
+
912 uint32_t flags;
+
913 uint32_t max_read;
+
914 uint32_t max_write;
+
915 uint32_t dev_major; /* chardev major */
+
916 uint32_t dev_minor; /* chardev minor */
+
917 uint32_t spare[10];
+
918};
+
919
+
920struct fuse_interrupt_in {
+
921 uint64_t unique;
+
922};
+
923
+
924struct fuse_bmap_in {
+
925 uint64_t block;
+
926 uint32_t blocksize;
+
927 uint32_t padding;
+
928};
+
929
+
930struct fuse_bmap_out {
+
931 uint64_t block;
+
932};
+
933
+
934struct fuse_ioctl_in {
+
935 uint64_t fh;
+
936 uint32_t flags;
+
937 uint32_t cmd;
+
938 uint64_t arg;
+
939 uint32_t in_size;
+
940 uint32_t out_size;
+
941};
+
942
+
943struct fuse_ioctl_iovec {
+
944 uint64_t base;
+
945 uint64_t len;
+
946};
+
947
+
948struct fuse_ioctl_out {
+
949 int32_t result;
+
950 uint32_t flags;
+
951 uint32_t in_iovs;
+
952 uint32_t out_iovs;
+
953};
+
954
+
955struct fuse_poll_in {
+
956 uint64_t fh;
+
957 uint64_t kh;
+
958 uint32_t flags;
+
959 uint32_t events;
+
960};
+
961
+
962struct fuse_poll_out {
+
963 uint32_t revents;
+
964 uint32_t padding;
+
965};
+
966
+
967struct fuse_notify_poll_wakeup_out {
+
968 uint64_t kh;
+
969};
+
970
+
971struct fuse_fallocate_in {
+
972 uint64_t fh;
+
973 uint64_t offset;
+
974 uint64_t length;
+
975 uint32_t mode;
+
976 uint32_t padding;
+
977};
+
978
+
985#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
986
+
987struct fuse_in_header {
+
988 uint32_t len;
+
989 uint32_t opcode;
+
990 uint64_t unique;
+
991 uint64_t nodeid;
+
992 uint32_t uid;
+
993 uint32_t gid;
+
994 uint32_t pid;
+
995 uint16_t total_extlen; /* length of extensions in 8byte units */
+
996 uint16_t padding;
+
997};
+
998
+
999struct fuse_out_header {
+
1000 uint32_t len;
+
1001 int32_t error;
+
1002 uint64_t unique;
+
1003};
+
1004
+
1005struct fuse_dirent {
+
1006 uint64_t ino;
+
1007 uint64_t off;
+
1008 uint32_t namelen;
+
1009 uint32_t type;
+
1010 char name[];
+
1011};
+
1012
+
1013/* Align variable length records to 64bit boundary */
+
1014#define FUSE_REC_ALIGN(x) \
+
1015 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1016
+
1017#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1018#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1019#define FUSE_DIRENT_SIZE(d) \
+
1020 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1021
+
1022struct fuse_direntplus {
+
1023 struct fuse_entry_out entry_out;
+
1024 struct fuse_dirent dirent;
+
1025};
+
1026
+
1027#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1028 offsetof(struct fuse_direntplus, dirent.name)
+
1029#define FUSE_DIRENTPLUS_SIZE(d) \
+
1030 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1031
+
1032struct fuse_notify_inval_inode_out {
+
1033 uint64_t ino;
+
1034 int64_t off;
+
1035 int64_t len;
+
1036};
+
1037
+
1038struct fuse_notify_inval_entry_out {
+
1039 uint64_t parent;
+
1040 uint32_t namelen;
+
1041 uint32_t flags;
+
1042};
+
1043
+
1044struct fuse_notify_delete_out {
+
1045 uint64_t parent;
+
1046 uint64_t child;
+
1047 uint32_t namelen;
+
1048 uint32_t padding;
+
1049};
+
1050
+
1051struct fuse_notify_store_out {
+
1052 uint64_t nodeid;
+
1053 uint64_t offset;
+
1054 uint32_t size;
+
1055 uint32_t padding;
+
1056};
+
1057
+
1058struct fuse_notify_retrieve_out {
+
1059 uint64_t notify_unique;
+
1060 uint64_t nodeid;
+
1061 uint64_t offset;
+
1062 uint32_t size;
+
1063 uint32_t padding;
+
1064};
+
1065
+
1066/* Matches the size of fuse_write_in */
+
1067struct fuse_notify_retrieve_in {
+
1068 uint64_t dummy1;
+
1069 uint64_t offset;
+
1070 uint32_t size;
+
1071 uint32_t dummy2;
+
1072 uint64_t dummy3;
+
1073 uint64_t dummy4;
+
1074};
+
1075
+
1076struct fuse_backing_map {
+
1077 int32_t fd;
+
1078 uint32_t flags;
+
1079 uint64_t padding;
+
1080};
+
1081
+
1082/* Device ioctls: */
+
1083#define FUSE_DEV_IOC_MAGIC 229
+
1084#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1085#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1086 struct fuse_backing_map)
+
1087#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1088
+
1089struct fuse_lseek_in {
+
1090 uint64_t fh;
+
1091 uint64_t offset;
+
1092 uint32_t whence;
+
1093 uint32_t padding;
+
1094};
+
1095
+
1096struct fuse_lseek_out {
+
1097 uint64_t offset;
+
1098};
+
1099
+
1100struct fuse_copy_file_range_in {
+
1101 uint64_t fh_in;
+
1102 uint64_t off_in;
+
1103 uint64_t nodeid_out;
+
1104 uint64_t fh_out;
+
1105 uint64_t off_out;
+
1106 uint64_t len;
+
1107 uint64_t flags;
+
1108};
+
1109
+
1110#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1111#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1112struct fuse_setupmapping_in {
+
1113 /* An already open handle */
+
1114 uint64_t fh;
+
1115 /* Offset into the file to start the mapping */
+
1116 uint64_t foffset;
+
1117 /* Length of mapping required */
+
1118 uint64_t len;
+
1119 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1120 uint64_t flags;
+
1121 /* Offset in Memory Window */
+
1122 uint64_t moffset;
+
1123};
+
1124
+
1125struct fuse_removemapping_in {
+
1126 /* number of fuse_removemapping_one follows */
+
1127 uint32_t count;
+
1128};
+
1129
+
1130struct fuse_removemapping_one {
+
1131 /* Offset into the dax window start the unmapping */
+
1132 uint64_t moffset;
+
1133 /* Length of mapping required */
+
1134 uint64_t len;
+
1135};
+
1136
+
1137#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1138 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1139
+
1140struct fuse_syncfs_in {
+
1141 uint64_t padding;
+
1142};
+
1143
+
1144/*
+
1145 * For each security context, send fuse_secctx with size of security context
+
1146 * fuse_secctx will be followed by security context name and this in turn
+
1147 * will be followed by actual context label.
+
1148 * fuse_secctx, name, context
+
1149 */
+
1150struct fuse_secctx {
+
1151 uint32_t size;
+
1152 uint32_t padding;
+
1153};
+
1154
+
1155/*
+
1156 * Contains the information about how many fuse_secctx structures are being
+
1157 * sent and what's the total size of all security contexts (including
+
1158 * size of fuse_secctx_header).
+
1159 *
+
1160 */
+
1161struct fuse_secctx_header {
+
1162 uint32_t size;
+
1163 uint32_t nr_secctx;
+
1164};
+
1165
+
+ +
1175 uint32_t size;
+
1176 uint32_t type;
+
1177};
+
+
1178
+
+ +
1185 uint32_t nr_groups;
+
1186 uint32_t groups[];
+
1187};
+
+
1188
+
1189#endif /* _LINUX_FUSE_H */
+ + +
+ + + + diff --git a/doc/html/fuse__log_8c_source.html b/doc/html/fuse__log_8c_source.html new file mode 100644 index 0000000..e4228ce --- /dev/null +++ b/doc/html/fuse__log_8c_source.html @@ -0,0 +1,171 @@ + + + + + + + +libfuse: lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdarg.h>
+
14#include <stdio.h>
+
15#include <stdbool.h>
+
16#include <syslog.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(__attribute__((unused)) enum fuse_log_level level,
+
23 const char *fmt, va_list ap)
+
24{
+
25 if (to_syslog) {
+
26 int sys_log_level = LOG_ERR;
+
27
+
28 /*
+
29 * with glibc fuse_log_level has identical values as
+
30 * syslog levels, but we also support BSD - better we convert to
+
31 * be sure.
+
32 */
+
33 switch (level) {
+
34 case FUSE_LOG_DEBUG:
+
35 sys_log_level = LOG_DEBUG;
+
36 break;
+
37 case FUSE_LOG_INFO:
+
38 sys_log_level = LOG_INFO;
+
39 break;
+
40 case FUSE_LOG_NOTICE:
+
41 sys_log_level = LOG_NOTICE;
+
42 break;
+
43 case FUSE_LOG_WARNING:
+
44 sys_log_level = LOG_WARNING;
+
45 break;
+
46 case FUSE_LOG_ERR:
+
47 sys_log_level = LOG_ERR;
+
48 break;
+
49 case FUSE_LOG_CRIT:
+
50 sys_log_level = LOG_CRIT;
+
51 break;
+
52 case FUSE_LOG_ALERT:
+
53 sys_log_level = LOG_ALERT;
+
54 break;
+
55 case FUSE_LOG_EMERG:
+
56 sys_log_level = LOG_EMERG;
+
57 }
+
58
+
59 char log[MAX_SYSLOG_LINE_LEN];
+
60 vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap);
+
61 syslog(sys_log_level, "%s", log);
+
62 } else {
+
63 vfprintf(stderr, fmt, ap);
+
64 }
+
65}
+
66
+
67static fuse_log_func_t log_func = default_log_func;
+
68
+
+ +
70{
+
71 if (!func)
+
72 func = default_log_func;
+
73
+
74 log_func = func;
+
75}
+
+
76
+
+
77void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
78{
+
79 va_list ap;
+
80
+
81 va_start(ap, fmt);
+
82 log_func(level, fmt, ap);
+
83 va_end(ap);
+
84}
+
+
85
+
+
86void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
87{
+
88 to_syslog = true;
+
89
+
90 openlog(ident, option, facility);
+
91}
+
+
92
+
+ +
94{
+
95 closelog();
+
96}
+
+ +
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse__log_8h.html b/doc/html/fuse__log_8h.html new file mode 100644 index 0000000..4a758cd --- /dev/null +++ b/doc/html/fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...)
 
void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 77 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse__log_8h_source.html b/doc/html/fuse__log_8h_source.html new file mode 100644 index 0000000..c17273a --- /dev/null +++ b/doc/html/fuse__log_8h_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...);
+
77
+
83void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
84
+
88void fuse_log_close_syslog(void);
+
89
+
90#ifdef __cplusplus
+
91}
+
92#endif
+
93
+
94#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/fuse__loop_8c_source.html b/doc/html/fuse__loop_8c_source.html new file mode 100644 index 0000000..a0024f9 --- /dev/null +++ b/doc/html/fuse__loop_8c_source.html @@ -0,0 +1,115 @@ + + + + + + + +libfuse: lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+ +
45 return res;
+
46}
+
+ +
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_reset(struct fuse_session *se)
+ +
void * mem
+
+ + + + diff --git a/doc/html/fuse__loop__mt_8c_source.html b/doc/html/fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..17a36e7 --- /dev/null +++ b/doc/html/fuse__loop__mt_8c_source.html @@ -0,0 +1,602 @@ + + + + + + + +libfuse: lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <string.h>
+
23#include <unistd.h>
+
24#include <signal.h>
+
25#include <semaphore.h>
+
26#include <errno.h>
+
27#include <sys/time.h>
+
28#include <sys/ioctl.h>
+
29#include <assert.h>
+
30#include <limits.h>
+
31
+
32/* Environment var controlling the thread stack size */
+
33#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
34
+
35#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
36#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
37#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
38#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
39 * by default */
+
40
+
41/* an arbitrary large value that cannot be valid */
+
42#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
43
+
44struct fuse_worker {
+
45 struct fuse_worker *prev;
+
46 struct fuse_worker *next;
+
47 pthread_t thread_id;
+
48
+
49 // We need to include fuse_buf so that we can properly free
+
50 // it when a thread is terminated by pthread_cancel().
+
51 struct fuse_buf fbuf;
+
52 struct fuse_chan *ch;
+
53 struct fuse_mt *mt;
+
54};
+
55
+
56struct fuse_mt {
+
57 pthread_mutex_t lock;
+
58 int numworker;
+
59 int numavail;
+
60 struct fuse_session *se;
+
61 struct fuse_worker main;
+
62 sem_t finish;
+
63 int exit;
+
64 int error;
+
65 int clone_fd;
+
66 int max_idle;
+
67 int max_threads;
+
68};
+
69
+
70static struct fuse_chan *fuse_chan_new(int fd)
+
71{
+
72 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
73 if (ch == NULL) {
+
74 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
75 return NULL;
+
76 }
+
77
+
78 memset(ch, 0, sizeof(*ch));
+
79 ch->fd = fd;
+
80 ch->ctr = 1;
+
81 pthread_mutex_init(&ch->lock, NULL);
+
82
+
83 return ch;
+
84}
+
85
+
86struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
87{
+
88 assert(ch->ctr > 0);
+
89 pthread_mutex_lock(&ch->lock);
+
90 ch->ctr++;
+
91 pthread_mutex_unlock(&ch->lock);
+
92
+
93 return ch;
+
94}
+
95
+
96void fuse_chan_put(struct fuse_chan *ch)
+
97{
+
98 if (ch == NULL)
+
99 return;
+
100 pthread_mutex_lock(&ch->lock);
+
101 ch->ctr--;
+
102 if (!ch->ctr) {
+
103 pthread_mutex_unlock(&ch->lock);
+
104 close(ch->fd);
+
105 pthread_mutex_destroy(&ch->lock);
+
106 free(ch);
+
107 } else
+
108 pthread_mutex_unlock(&ch->lock);
+
109}
+
110
+
111static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
112{
+
113 struct fuse_worker *prev = next->prev;
+
114 w->next = next;
+
115 w->prev = prev;
+
116 prev->next = w;
+
117 next->prev = w;
+
118}
+
119
+
120static void list_del_worker(struct fuse_worker *w)
+
121{
+
122 struct fuse_worker *prev = w->prev;
+
123 struct fuse_worker *next = w->next;
+
124 prev->next = next;
+
125 next->prev = prev;
+
126}
+
127
+
128static int fuse_loop_start_thread(struct fuse_mt *mt);
+
129
+
130static void *fuse_do_work(void *data)
+
131{
+
132 struct fuse_worker *w = (struct fuse_worker *) data;
+
133 struct fuse_mt *mt = w->mt;
+
134
+
135#ifdef HAVE_PTHREAD_SETNAME_NP
+
136 pthread_setname_np(pthread_self(), "fuse_worker");
+
137#endif
+
138
+
139 while (!fuse_session_exited(mt->se)) {
+
140 int isforget = 0;
+
141 int res;
+
142
+
143 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
144 res = fuse_session_receive_buf_internal(mt->se, &w->fbuf,
+
145 w->ch);
+
146 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
147 if (res == -EINTR)
+
148 continue;
+
149 if (res <= 0) {
+
150 if (res < 0) {
+
151 fuse_session_exit(mt->se);
+
152 mt->error = res;
+
153 }
+
154 break;
+
155 }
+
156
+
157 pthread_mutex_lock(&mt->lock);
+
158 if (mt->exit) {
+
159 pthread_mutex_unlock(&mt->lock);
+
160 return NULL;
+
161 }
+
162
+
163 /*
+
164 * This disgusting hack is needed so that zillions of threads
+
165 * are not created on a burst of FORGET messages
+
166 */
+
167 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
168 struct fuse_in_header *in = w->fbuf.mem;
+
169
+
170 if (in->opcode == FUSE_FORGET ||
+
171 in->opcode == FUSE_BATCH_FORGET)
+
172 isforget = 1;
+
173 }
+
174
+
175 if (!isforget)
+
176 mt->numavail--;
+
177 if (mt->numavail == 0 && mt->numworker < mt->max_threads)
+
178 fuse_loop_start_thread(mt);
+
179 pthread_mutex_unlock(&mt->lock);
+
180
+
181 fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch);
+
182
+
183 pthread_mutex_lock(&mt->lock);
+
184 if (!isforget)
+
185 mt->numavail++;
+
186
+
187 /* creating and destroying threads is rather expensive - and there is
+
188 * not much gain from destroying existing threads. It is therefore
+
189 * discouraged to set max_idle to anything else than -1. If there
+
190 * is indeed a good reason to destruct threads it should be done
+
191 * delayed, a moving average might be useful for that.
+
192 */
+
193 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
194 if (mt->exit) {
+
195 pthread_mutex_unlock(&mt->lock);
+
196 return NULL;
+
197 }
+
198 list_del_worker(w);
+
199 mt->numavail--;
+
200 mt->numworker--;
+
201 pthread_mutex_unlock(&mt->lock);
+
202
+
203 pthread_detach(w->thread_id);
+
204 fuse_buf_free(&w->fbuf);
+
205 fuse_chan_put(w->ch);
+
206 free(w);
+
207 return NULL;
+
208 }
+
209 pthread_mutex_unlock(&mt->lock);
+
210 }
+
211
+
212 sem_post(&mt->finish);
+
213
+
214 return NULL;
+
215}
+
216
+
217int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
218{
+
219 sigset_t oldset;
+
220 sigset_t newset;
+
221 int res;
+
222 pthread_attr_t attr;
+
223 char *stack_size;
+
224
+
225 /* Override default stack size
+
226 * XXX: This should ideally be a parameter option. It is rather
+
227 * well hidden here.
+
228 */
+
229 pthread_attr_init(&attr);
+
230 stack_size = getenv(ENVNAME_THREAD_STACK);
+
231 if (stack_size) {
+
232 long size;
+
233
+
234 res = libfuse_strtol(stack_size, &size);
+
235 if (res)
+
236 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
237 stack_size);
+
238 else if (pthread_attr_setstacksize(&attr, size))
+
239 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
240 size);
+
241 }
+
242
+
243 /* Disallow signal reception in worker threads */
+
244 sigemptyset(&newset);
+
245 sigaddset(&newset, SIGTERM);
+
246 sigaddset(&newset, SIGINT);
+
247 sigaddset(&newset, SIGHUP);
+
248 sigaddset(&newset, SIGQUIT);
+
249 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
250 res = pthread_create(thread_id, &attr, func, arg);
+
251 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
252 pthread_attr_destroy(&attr);
+
253 if (res != 0) {
+
254 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
255 strerror(res));
+
256 return -1;
+
257 }
+
258
+
259 return 0;
+
260}
+
261
+
262static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
263{
+
264 int res;
+
265 int clonefd;
+
266 uint32_t masterfd;
+
267 const char *devname = "/dev/fuse";
+
268
+
269#ifndef O_CLOEXEC
+
270#define O_CLOEXEC 0
+
271#endif
+
272 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
273 if (clonefd == -1) {
+
274 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
275 strerror(errno));
+
276 return -1;
+
277 }
+
278#ifndef O_CLOEXEC
+
279 fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
280#endif
+
281
+
282 masterfd = se->fd;
+
283 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
284 if (res == -1) {
+
285 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
286 strerror(errno));
+
287 close(clonefd);
+
288 return -1;
+
289 }
+
290 return clonefd;
+
291}
+
292
+
293static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
294{
+
295 int clonefd;
+
296 struct fuse_session *se = mt->se;
+
297 struct fuse_chan *newch;
+
298
+
299 if (se->io != NULL) {
+
300 if (se->io->clone_fd != NULL)
+
301 clonefd = se->io->clone_fd(se->fd);
+
302 else
+
303 return NULL;
+
304 } else {
+
305 clonefd = fuse_clone_chan_fd_default(se);
+
306 }
+
307 if (clonefd < 0)
+
308 return NULL;
+
309
+
310 newch = fuse_chan_new(clonefd);
+
311 if (newch == NULL)
+
312 close(clonefd);
+
313
+
314 return newch;
+
315}
+
316
+
317static int fuse_loop_start_thread(struct fuse_mt *mt)
+
318{
+
319 int res;
+
320
+
321 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
322 if (!w) {
+
323 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
324 return -1;
+
325 }
+
326 memset(w, 0, sizeof(struct fuse_worker));
+
327 w->fbuf.mem = NULL;
+
328 w->mt = mt;
+
329
+
330 w->ch = NULL;
+
331 if (mt->clone_fd) {
+
332 w->ch = fuse_clone_chan(mt);
+
333 if(!w->ch) {
+
334 /* Don't attempt this again */
+
335 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
336 "without -o clone_fd.\n");
+
337 mt->clone_fd = 0;
+
338 }
+
339 }
+
340
+
341 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
342 if (res == -1) {
+
343 fuse_chan_put(w->ch);
+
344 free(w);
+
345 return -1;
+
346 }
+
347 list_add_worker(w, &mt->main);
+
348 mt->numavail ++;
+
349 mt->numworker ++;
+
350
+
351 return 0;
+
352}
+
353
+
354static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
355{
+
356 pthread_join(w->thread_id, NULL);
+
357 pthread_mutex_lock(&mt->lock);
+
358 list_del_worker(w);
+
359 pthread_mutex_unlock(&mt->lock);
+
360 fuse_buf_free(&w->fbuf);
+
361 fuse_chan_put(w->ch);
+
362 free(w);
+
363}
+
364
+
365int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
366FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
367int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
368{
+
369int err;
+
370 struct fuse_mt mt;
+
371 struct fuse_worker *w;
+
372 int created_config = 0;
+
373
+
374 if (config) {
+
375 err = fuse_loop_cfg_verify(config);
+
376 if (err)
+
377 return err;
+
378 } else {
+
379 /* The caller does not care about parameters - use the default */
+
380 config = fuse_loop_cfg_create();
+
381 created_config = 1;
+
382 }
+
383
+
384
+
385 memset(&mt, 0, sizeof(struct fuse_mt));
+
386 mt.se = se;
+
387 mt.clone_fd = config->clone_fd;
+
388 mt.error = 0;
+
389 mt.numworker = 0;
+
390 mt.numavail = 0;
+
391 mt.max_idle = config->max_idle_threads;
+
392 mt.max_threads = config->max_threads;
+
393 mt.main.thread_id = pthread_self();
+
394 mt.main.prev = mt.main.next = &mt.main;
+
395 sem_init(&mt.finish, 0, 0);
+
396 pthread_mutex_init(&mt.lock, NULL);
+
397
+
398 pthread_mutex_lock(&mt.lock);
+
399 err = fuse_loop_start_thread(&mt);
+
400 pthread_mutex_unlock(&mt.lock);
+
401 if (!err) {
+
402 /* sem_wait() is interruptible */
+
403 while (!fuse_session_exited(se))
+
404 sem_wait(&mt.finish);
+
405
+
406 pthread_mutex_lock(&mt.lock);
+
407 for (w = mt.main.next; w != &mt.main; w = w->next)
+
408 pthread_cancel(w->thread_id);
+
409 mt.exit = 1;
+
410 pthread_mutex_unlock(&mt.lock);
+
411
+
412 while (mt.main.next != &mt.main)
+
413 fuse_join_worker(&mt, mt.main.next);
+
414
+
415 err = mt.error;
+
416 }
+
417
+
418 pthread_mutex_destroy(&mt.lock);
+
419 sem_destroy(&mt.finish);
+
420 if(se->error != 0)
+
421 err = se->error;
+ +
423
+
424 if (created_config) {
+
425 fuse_loop_cfg_destroy(config);
+
426 config = NULL;
+
427 }
+
428
+
429 return err;
+
430}
+
431
+
432int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
433FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
434int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
435{
+
436 int err;
+
437 struct fuse_loop_config *config = NULL;
+
438
+
439 if (config_v1 != NULL) {
+
440 /* convert the given v1 config */
+
441 config = fuse_loop_cfg_create();
+
442 if (config == NULL)
+
443 return ENOMEM;
+
444
+
445 fuse_loop_cfg_convert(config, config_v1);
+
446 }
+
447
+
448 err = fuse_session_loop_mt_312(se, config);
+
449
+
450 fuse_loop_cfg_destroy(config);
+
451
+
452 return err;
+
453}
+
454
+
455
+
456int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
457FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
458int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
459{
+
460 int err;
+
461 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
462 if (clone_fd > 0)
+
463 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
464 err = fuse_session_loop_mt_312(se, config);
+
465
+
466 fuse_loop_cfg_destroy(config);
+
467
+
468 return err;
+
469}
+
470
+
471struct fuse_loop_config *fuse_loop_cfg_create(void)
+
472{
+
473 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
474 if (config == NULL)
+
475 return NULL;
+
476
+
477 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
478 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
479 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
480 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
481
+
482 return config;
+
483}
+
484
+
485void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
486{
+
487 free(config);
+
488}
+
489
+
490int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
491{
+
492 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
493 return -EINVAL;
+
494
+
495 return 0;
+
496}
+
497
+
498void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
499 struct fuse_loop_config_v1 *v1_conf)
+
500{
+
501 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
502
+
503 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
504}
+
505
+
506void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
507 unsigned int value)
+
508{
+
509 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
510 if (value != UINT_MAX)
+
511 fuse_log(FUSE_LOG_ERR,
+
512 "Ignoring invalid max threads value "
+
513 "%u > max (%u).\n", value,
+
514 FUSE_LOOP_MT_MAX_THREADS);
+
515 return;
+
516 }
+
517 config->max_idle_threads = value;
+
518}
+
519
+
520void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
521 unsigned int value)
+
522{
+
523 config->max_threads = value;
+
524}
+
525
+
526void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
527 unsigned int value)
+
528{
+
529 config->clone_fd = value;
+
530}
+
531
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+ +
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_reset(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:158
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/fuse__lowlevel_8c_source.html b/doc/html/fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..b3acf8f --- /dev/null +++ b/doc/html/fuse__lowlevel_8c_source.html @@ -0,0 +1,3834 @@ + + + + + + + +libfuse: lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21
+
22#include <stdint.h>
+
23#include <stdbool.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <stddef.h>
+
27#include <stdalign.h>
+
28#include <string.h>
+
29#include <unistd.h>
+
30#include <limits.h>
+
31#include <errno.h>
+
32#include <assert.h>
+
33#include <sys/file.h>
+
34#include <sys/ioctl.h>
+
35
+
36#ifndef F_LINUX_SPECIFIC_BASE
+
37#define F_LINUX_SPECIFIC_BASE 1024
+
38#endif
+
39#ifndef F_SETPIPE_SZ
+
40#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
41#endif
+
42
+
43
+
44#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
45#define OFFSET_MAX 0x7fffffffffffffffLL
+
46
+
47#define container_of(ptr, type, member) ({ \
+
48 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
49 (type *)( (char *)__mptr - offsetof(type,member) );})
+
50
+
51struct fuse_pollhandle {
+
52 uint64_t kh;
+
53 struct fuse_session *se;
+
54};
+
55
+
56static size_t pagesize;
+
57
+
58static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
59{
+
60 pagesize = getpagesize();
+
61}
+
62
+
63static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
64{
+
65 attr->ino = stbuf->st_ino;
+
66 attr->mode = stbuf->st_mode;
+
67 attr->nlink = stbuf->st_nlink;
+
68 attr->uid = stbuf->st_uid;
+
69 attr->gid = stbuf->st_gid;
+
70 attr->rdev = stbuf->st_rdev;
+
71 attr->size = stbuf->st_size;
+
72 attr->blksize = stbuf->st_blksize;
+
73 attr->blocks = stbuf->st_blocks;
+
74 attr->atime = stbuf->st_atime;
+
75 attr->mtime = stbuf->st_mtime;
+
76 attr->ctime = stbuf->st_ctime;
+
77 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
78 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
79 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
80}
+
81
+
82static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
83{
+
84 stbuf->st_mode = attr->mode;
+
85 stbuf->st_uid = attr->uid;
+
86 stbuf->st_gid = attr->gid;
+
87 stbuf->st_size = attr->size;
+
88 stbuf->st_atime = attr->atime;
+
89 stbuf->st_mtime = attr->mtime;
+
90 stbuf->st_ctime = attr->ctime;
+
91 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
92 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
93 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
94}
+
95
+
96static size_t iov_length(const struct iovec *iov, size_t count)
+
97{
+
98 size_t seg;
+
99 size_t ret = 0;
+
100
+
101 for (seg = 0; seg < count; seg++)
+
102 ret += iov[seg].iov_len;
+
103 return ret;
+
104}
+
105
+
106static void list_init_req(struct fuse_req *req)
+
107{
+
108 req->next = req;
+
109 req->prev = req;
+
110}
+
111
+
112static void list_del_req(struct fuse_req *req)
+
113{
+
114 struct fuse_req *prev = req->prev;
+
115 struct fuse_req *next = req->next;
+
116 prev->next = next;
+
117 next->prev = prev;
+
118}
+
119
+
120static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
121{
+
122 struct fuse_req *prev = next->prev;
+
123 req->next = next;
+
124 req->prev = prev;
+
125 prev->next = req;
+
126 next->prev = req;
+
127}
+
128
+
129static void destroy_req(fuse_req_t req)
+
130{
+
131 assert(req->ch == NULL);
+
132 pthread_mutex_destroy(&req->lock);
+
133 free(req);
+
134}
+
135
+
136void fuse_free_req(fuse_req_t req)
+
137{
+
138 int ctr;
+
139 struct fuse_session *se = req->se;
+
140
+
141 if (se->conn.no_interrupt) {
+
142 ctr = --req->ref_cnt;
+
143 fuse_chan_put(req->ch);
+
144 req->ch = NULL;
+
145 } else {
+
146 pthread_mutex_lock(&se->lock);
+
147 req->u.ni.func = NULL;
+
148 req->u.ni.data = NULL;
+
149 list_del_req(req);
+
150 ctr = --req->ref_cnt;
+
151 fuse_chan_put(req->ch);
+
152 req->ch = NULL;
+
153 pthread_mutex_unlock(&se->lock);
+
154 }
+
155 if (!ctr)
+
156 destroy_req(req);
+
157}
+
158
+
159static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
160{
+
161 struct fuse_req *req;
+
162
+
163 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
164 if (req == NULL) {
+
165 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
166 } else {
+
167 req->se = se;
+
168 req->ref_cnt = 1;
+
169 list_init_req(req);
+
170 pthread_mutex_init(&req->lock, NULL);
+
171 }
+
172
+
173 return req;
+
174}
+
175
+
176/* Send data. If *ch* is NULL, send via session master fd */
+
177static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
178 struct iovec *iov, int count)
+
179{
+
180 struct fuse_out_header *out = iov[0].iov_base;
+
181
+
182 assert(se != NULL);
+
183 out->len = iov_length(iov, count);
+
184 if (se->debug) {
+
185 if (out->unique == 0) {
+
186 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
187 out->error, out->len);
+
188 } else if (out->error) {
+
189 fuse_log(FUSE_LOG_DEBUG,
+
190 " unique: %llu, error: %i (%s), outsize: %i\n",
+
191 (unsigned long long) out->unique, out->error,
+
192 strerror(-out->error), out->len);
+
193 } else {
+
194 fuse_log(FUSE_LOG_DEBUG,
+
195 " unique: %llu, success, outsize: %i\n",
+
196 (unsigned long long) out->unique, out->len);
+
197 }
+
198 }
+
199
+
200 ssize_t res;
+
201 if (se->io != NULL)
+
202 /* se->io->writev is never NULL if se->io is not NULL as
+
203 specified by fuse_session_custom_io()*/
+
204 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
205 se->userdata);
+
206 else
+
207 res = writev(ch ? ch->fd : se->fd, iov, count);
+
208
+
209 int err = errno;
+
210
+
211 if (res == -1) {
+
212 /* ENOENT means the operation was interrupted */
+
213 if (!fuse_session_exited(se) && err != ENOENT)
+
214 perror("fuse: writing device");
+
215 return -err;
+
216 }
+
217
+
218 return 0;
+
219}
+
220
+
221
+
222int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
223 int count)
+
224{
+
225 struct fuse_out_header out;
+
226
+
227#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
228 const char *str = strerrordesc_np(error * -1);
+
229 if ((str == NULL && error != 0) || error > 0) {
+
230#else
+
231 if (error <= -1000 || error > 0) {
+
232#endif
+
233 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
234 error = -ERANGE;
+
235 }
+
236
+
237 out.unique = req->unique;
+
238 out.error = error;
+
239
+
240 iov[0].iov_base = &out;
+
241 iov[0].iov_len = sizeof(struct fuse_out_header);
+
242
+
243 return fuse_send_msg(req->se, req->ch, iov, count);
+
244}
+
245
+
246static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
247 int count)
+
248{
+
249 int res;
+
250
+
251 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
252 fuse_free_req(req);
+
253 return res;
+
254}
+
255
+
256static int send_reply(fuse_req_t req, int error, const void *arg,
+
257 size_t argsize)
+
258{
+
259 struct iovec iov[2];
+
260 int count = 1;
+
261 if (argsize) {
+
262 iov[1].iov_base = (void *) arg;
+
263 iov[1].iov_len = argsize;
+
264 count++;
+
265 }
+
266 return send_reply_iov(req, error, iov, count);
+
267}
+
268
+
+
269int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
270{
+
271 int res;
+
272 struct iovec *padded_iov;
+
273
+
274 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
275 if (padded_iov == NULL)
+
276 return fuse_reply_err(req, ENOMEM);
+
277
+
278 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
279 count++;
+
280
+
281 res = send_reply_iov(req, 0, padded_iov, count);
+
282 free(padded_iov);
+
283
+
284 return res;
+
285}
+
+
286
+
287
+
288/* `buf` is allowed to be empty so that the proper size may be
+
289 allocated by the caller */
+
+
290size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
291 const char *name, const struct stat *stbuf, off_t off)
+
292{
+
293 (void)req;
+
294 size_t namelen;
+
295 size_t entlen;
+
296 size_t entlen_padded;
+
297 struct fuse_dirent *dirent;
+
298
+
299 namelen = strlen(name);
+
300 entlen = FUSE_NAME_OFFSET + namelen;
+
301 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
302
+
303 if ((buf == NULL) || (entlen_padded > bufsize))
+
304 return entlen_padded;
+
305
+
306 dirent = (struct fuse_dirent*) buf;
+
307 dirent->ino = stbuf->st_ino;
+
308 dirent->off = off;
+
309 dirent->namelen = namelen;
+
310 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
311 memcpy(dirent->name, name, namelen);
+
312 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
313
+
314 return entlen_padded;
+
315}
+
+
316
+
317static void convert_statfs(const struct statvfs *stbuf,
+
318 struct fuse_kstatfs *kstatfs)
+
319{
+
320 kstatfs->bsize = stbuf->f_bsize;
+
321 kstatfs->frsize = stbuf->f_frsize;
+
322 kstatfs->blocks = stbuf->f_blocks;
+
323 kstatfs->bfree = stbuf->f_bfree;
+
324 kstatfs->bavail = stbuf->f_bavail;
+
325 kstatfs->files = stbuf->f_files;
+
326 kstatfs->ffree = stbuf->f_ffree;
+
327 kstatfs->namelen = stbuf->f_namemax;
+
328}
+
329
+
330static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
331{
+
332 return send_reply(req, 0, arg, argsize);
+
333}
+
334
+
+
335int fuse_reply_err(fuse_req_t req, int err)
+
336{
+
337 return send_reply(req, -err, NULL, 0);
+
338}
+
+
339
+
+ +
341{
+
342 fuse_free_req(req);
+
343}
+
+
344
+
345static unsigned long calc_timeout_sec(double t)
+
346{
+
347 if (t > (double) ULONG_MAX)
+
348 return ULONG_MAX;
+
349 else if (t < 0.0)
+
350 return 0;
+
351 else
+
352 return (unsigned long) t;
+
353}
+
354
+
355static unsigned int calc_timeout_nsec(double t)
+
356{
+
357 double f = t - (double) calc_timeout_sec(t);
+
358 if (f < 0.0)
+
359 return 0;
+
360 else if (f >= 0.999999999)
+
361 return 999999999;
+
362 else
+
363 return (unsigned int) (f * 1.0e9);
+
364}
+
365
+
366static void fill_entry(struct fuse_entry_out *arg,
+
367 const struct fuse_entry_param *e)
+
368{
+
369 arg->nodeid = e->ino;
+
370 arg->generation = e->generation;
+
371 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
372 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
373 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
374 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
375 convert_stat(&e->attr, &arg->attr);
+
376}
+
377
+
378/* `buf` is allowed to be empty so that the proper size may be
+
379 allocated by the caller */
+
+
380size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
381 const char *name,
+
382 const struct fuse_entry_param *e, off_t off)
+
383{
+
384 (void)req;
+
385 size_t namelen;
+
386 size_t entlen;
+
387 size_t entlen_padded;
+
388
+
389 namelen = strlen(name);
+
390 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
391 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
392 if ((buf == NULL) || (entlen_padded > bufsize))
+
393 return entlen_padded;
+
394
+
395 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
396 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
397 fill_entry(&dp->entry_out, e);
+
398
+
399 struct fuse_dirent *dirent = &dp->dirent;
+
400 dirent->ino = e->attr.st_ino;
+
401 dirent->off = off;
+
402 dirent->namelen = namelen;
+
403 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
404 memcpy(dirent->name, name, namelen);
+
405 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
406
+
407 return entlen_padded;
+
408}
+
+
409
+
410static void fill_open(struct fuse_open_out *arg,
+
411 const struct fuse_file_info *f)
+
412{
+
413 arg->fh = f->fh;
+
414 if (f->backing_id > 0) {
+
415 arg->backing_id = f->backing_id;
+
416 arg->open_flags |= FOPEN_PASSTHROUGH;
+
417 }
+
418 if (f->direct_io)
+
419 arg->open_flags |= FOPEN_DIRECT_IO;
+
420 if (f->keep_cache)
+
421 arg->open_flags |= FOPEN_KEEP_CACHE;
+
422 if (f->cache_readdir)
+
423 arg->open_flags |= FOPEN_CACHE_DIR;
+
424 if (f->nonseekable)
+
425 arg->open_flags |= FOPEN_NONSEEKABLE;
+
426 if (f->noflush)
+
427 arg->open_flags |= FOPEN_NOFLUSH;
+ +
429 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
430}
+
431
+
+ +
433{
+
434 struct fuse_entry_out arg;
+
435 size_t size = req->se->conn.proto_minor < 9 ?
+
436 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
437
+
438 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
439 negative entry */
+
440 if (!e->ino && req->se->conn.proto_minor < 4)
+
441 return fuse_reply_err(req, ENOENT);
+
442
+
443 memset(&arg, 0, sizeof(arg));
+
444 fill_entry(&arg, e);
+
445 return send_reply_ok(req, &arg, size);
+
446}
+
+
447
+
+ +
449 const struct fuse_file_info *f)
+
450{
+
451 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
452 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
453 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
454 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
455 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
456
+
457 memset(buf, 0, sizeof(buf));
+
458 fill_entry(earg, e);
+
459 fill_open(oarg, f);
+
460 return send_reply_ok(req, buf,
+
461 entrysize + sizeof(struct fuse_open_out));
+
462}
+
+
463
+
+
464int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
465 double attr_timeout)
+
466{
+
467 struct fuse_attr_out arg;
+
468 size_t size = req->se->conn.proto_minor < 9 ?
+
469 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
470
+
471 memset(&arg, 0, sizeof(arg));
+
472 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
473 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
474 convert_stat(attr, &arg.attr);
+
475
+
476 return send_reply_ok(req, &arg, size);
+
477}
+
+
478
+
+
479int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
480{
+
481 return send_reply_ok(req, linkname, strlen(linkname));
+
482}
+
+
483
+
+ +
485{
+
486 struct fuse_backing_map map = { .fd = fd };
+
487 int ret;
+
488
+
489 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
490 if (ret <= 0) {
+
491 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
492 return 0;
+
493 }
+
494
+
495 return ret;
+
496}
+
+
497
+
498int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
499{
+
500 int ret;
+
501
+
502 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
503 if (ret < 0)
+
504 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
505
+
506 return ret;
+
507}
+
508
+
+ +
510{
+
511 struct fuse_open_out arg;
+
512
+
513 memset(&arg, 0, sizeof(arg));
+
514 fill_open(&arg, f);
+
515 return send_reply_ok(req, &arg, sizeof(arg));
+
516}
+
+
517
+
+
518int fuse_reply_write(fuse_req_t req, size_t count)
+
519{
+
520 struct fuse_write_out arg;
+
521
+
522 memset(&arg, 0, sizeof(arg));
+
523 arg.size = count;
+
524
+
525 return send_reply_ok(req, &arg, sizeof(arg));
+
526}
+
+
527
+
+
528int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
529{
+
530 return send_reply_ok(req, buf, size);
+
531}
+
+
532
+
533static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
534 struct fuse_chan *ch,
+
535 struct iovec *iov, int iov_count,
+
536 struct fuse_bufvec *buf,
+
537 size_t len)
+
538{
+
539 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
540 void *mbuf;
+
541 int res;
+
542
+
543 /* Optimize common case */
+
544 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
545 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
546 /* FIXME: also avoid memory copy if there are multiple buffers
+
547 but none of them contain an fd */
+
548
+
549 iov[iov_count].iov_base = buf->buf[0].mem;
+
550 iov[iov_count].iov_len = len;
+
551 iov_count++;
+
552 return fuse_send_msg(se, ch, iov, iov_count);
+
553 }
+
554
+
555 res = posix_memalign(&mbuf, pagesize, len);
+
556 if (res != 0)
+
557 return res;
+
558
+
559 mem_buf.buf[0].mem = mbuf;
+
560 res = fuse_buf_copy(&mem_buf, buf, 0);
+
561 if (res < 0) {
+
562 free(mbuf);
+
563 return -res;
+
564 }
+
565 len = res;
+
566
+
567 iov[iov_count].iov_base = mbuf;
+
568 iov[iov_count].iov_len = len;
+
569 iov_count++;
+
570 res = fuse_send_msg(se, ch, iov, iov_count);
+
571 free(mbuf);
+
572
+
573 return res;
+
574}
+
575
+
576struct fuse_ll_pipe {
+
577 size_t size;
+
578 int can_grow;
+
579 int pipe[2];
+
580};
+
581
+
582static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
583{
+
584 close(llp->pipe[0]);
+
585 close(llp->pipe[1]);
+
586 free(llp);
+
587}
+
588
+
589#ifdef HAVE_SPLICE
+
590#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
591static int fuse_pipe(int fds[2])
+
592{
+
593 int rv = pipe(fds);
+
594
+
595 if (rv == -1)
+
596 return rv;
+
597
+
598 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
599 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
600 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
601 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
602 close(fds[0]);
+
603 close(fds[1]);
+
604 rv = -1;
+
605 }
+
606 return rv;
+
607}
+
608#else
+
609static int fuse_pipe(int fds[2])
+
610{
+
611 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
612}
+
613#endif
+
614
+
615static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
616{
+
617 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
618 if (llp == NULL) {
+
619 int res;
+
620
+
621 llp = malloc(sizeof(struct fuse_ll_pipe));
+
622 if (llp == NULL)
+
623 return NULL;
+
624
+
625 res = fuse_pipe(llp->pipe);
+
626 if (res == -1) {
+
627 free(llp);
+
628 return NULL;
+
629 }
+
630
+
631 /*
+
632 *the default size is 16 pages on linux
+
633 */
+
634 llp->size = pagesize * 16;
+
635 llp->can_grow = 1;
+
636
+
637 pthread_setspecific(se->pipe_key, llp);
+
638 }
+
639
+
640 return llp;
+
641}
+
642#endif
+
643
+
644static void fuse_ll_clear_pipe(struct fuse_session *se)
+
645{
+
646 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
647 if (llp) {
+
648 pthread_setspecific(se->pipe_key, NULL);
+
649 fuse_ll_pipe_free(llp);
+
650 }
+
651}
+
652
+
653#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
654static int read_back(int fd, char *buf, size_t len)
+
655{
+
656 int res;
+
657
+
658 res = read(fd, buf, len);
+
659 if (res == -1) {
+
660 fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+
661 return -EIO;
+
662 }
+
663 if (res != len) {
+
664 fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+
665 return -EIO;
+
666 }
+
667 return 0;
+
668}
+
669
+
670static int grow_pipe_to_max(int pipefd)
+
671{
+
672 int res;
+
673 long max;
+
674 long maxfd;
+
675 char buf[32];
+
676
+
677 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
678 if (maxfd < 0)
+
679 return -errno;
+
680
+
681 res = read(maxfd, buf, sizeof(buf) - 1);
+
682 if (res < 0) {
+
683 int saved_errno;
+
684
+
685 saved_errno = errno;
+
686 close(maxfd);
+
687 return -saved_errno;
+
688 }
+
689 close(maxfd);
+
690 buf[res] = '\0';
+
691
+
692 res = libfuse_strtol(buf, &max);
+
693 if (res)
+
694 return res;
+
695 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
696 if (res < 0)
+
697 return -errno;
+
698 return max;
+
699}
+
700
+
701static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
702 struct iovec *iov, int iov_count,
+
703 struct fuse_bufvec *buf, unsigned int flags)
+
704{
+
705 int res;
+
706 size_t len = fuse_buf_size(buf);
+
707 struct fuse_out_header *out = iov[0].iov_base;
+
708 struct fuse_ll_pipe *llp;
+
709 int splice_flags;
+
710 size_t pipesize;
+
711 size_t total_buf_size;
+
712 size_t idx;
+
713 size_t headerlen;
+
714 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
715
+
716 if (se->broken_splice_nonblock)
+
717 goto fallback;
+
718
+
719 if (flags & FUSE_BUF_NO_SPLICE)
+
720 goto fallback;
+
721
+
722 total_buf_size = 0;
+
723 for (idx = buf->idx; idx < buf->count; idx++) {
+
724 total_buf_size += buf->buf[idx].size;
+
725 if (idx == buf->idx)
+
726 total_buf_size -= buf->off;
+
727 }
+
728 if (total_buf_size < 2 * pagesize)
+
729 goto fallback;
+
730
+
731 if (se->conn.proto_minor < 14 ||
+
732 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
733 goto fallback;
+
734
+
735 llp = fuse_ll_get_pipe(se);
+
736 if (llp == NULL)
+
737 goto fallback;
+
738
+
739
+
740 headerlen = iov_length(iov, iov_count);
+
741
+
742 out->len = headerlen + len;
+
743
+
744 /*
+
745 * Heuristic for the required pipe size, does not work if the
+
746 * source contains less than page size fragments
+
747 */
+
748 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
749
+
750 if (llp->size < pipesize) {
+
751 if (llp->can_grow) {
+
752 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
753 if (res == -1) {
+
754 res = grow_pipe_to_max(llp->pipe[0]);
+
755 if (res > 0)
+
756 llp->size = res;
+
757 llp->can_grow = 0;
+
758 goto fallback;
+
759 }
+
760 llp->size = res;
+
761 }
+
762 if (llp->size < pipesize)
+
763 goto fallback;
+
764 }
+
765
+
766
+
767 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
768 if (res == -1)
+
769 goto fallback;
+
770
+
771 if (res != headerlen) {
+
772 res = -EIO;
+
773 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
774 headerlen);
+
775 goto clear_pipe;
+
776 }
+
777
+
778 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
779 pipe_buf.buf[0].fd = llp->pipe[1];
+
780
+
781 res = fuse_buf_copy(&pipe_buf, buf,
+ +
783 if (res < 0) {
+
784 if (res == -EAGAIN || res == -EINVAL) {
+
785 /*
+
786 * Should only get EAGAIN on kernels with
+
787 * broken SPLICE_F_NONBLOCK support (<=
+
788 * 2.6.35) where this error or a short read is
+
789 * returned even if the pipe itself is not
+
790 * full
+
791 *
+
792 * EINVAL might mean that splice can't handle
+
793 * this combination of input and output.
+
794 */
+
795 if (res == -EAGAIN)
+
796 se->broken_splice_nonblock = 1;
+
797
+
798 pthread_setspecific(se->pipe_key, NULL);
+
799 fuse_ll_pipe_free(llp);
+
800 goto fallback;
+
801 }
+
802 res = -res;
+
803 goto clear_pipe;
+
804 }
+
805
+
806 if (res != 0 && res < len) {
+
807 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
808 void *mbuf;
+
809 size_t now_len = res;
+
810 /*
+
811 * For regular files a short count is either
+
812 * 1) due to EOF, or
+
813 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
814 *
+
815 * For other inputs it's possible that we overflowed
+
816 * the pipe because of small buffer fragments.
+
817 */
+
818
+
819 res = posix_memalign(&mbuf, pagesize, len);
+
820 if (res != 0)
+
821 goto clear_pipe;
+
822
+
823 mem_buf.buf[0].mem = mbuf;
+
824 mem_buf.off = now_len;
+
825 res = fuse_buf_copy(&mem_buf, buf, 0);
+
826 if (res > 0) {
+
827 char *tmpbuf;
+
828 size_t extra_len = res;
+
829 /*
+
830 * Trickiest case: got more data. Need to get
+
831 * back the data from the pipe and then fall
+
832 * back to regular write.
+
833 */
+
834 tmpbuf = malloc(headerlen);
+
835 if (tmpbuf == NULL) {
+
836 free(mbuf);
+
837 res = ENOMEM;
+
838 goto clear_pipe;
+
839 }
+
840 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
841 free(tmpbuf);
+
842 if (res != 0) {
+
843 free(mbuf);
+
844 goto clear_pipe;
+
845 }
+
846 res = read_back(llp->pipe[0], mbuf, now_len);
+
847 if (res != 0) {
+
848 free(mbuf);
+
849 goto clear_pipe;
+
850 }
+
851 len = now_len + extra_len;
+
852 iov[iov_count].iov_base = mbuf;
+
853 iov[iov_count].iov_len = len;
+
854 iov_count++;
+
855 res = fuse_send_msg(se, ch, iov, iov_count);
+
856 free(mbuf);
+
857 return res;
+
858 }
+
859 free(mbuf);
+
860 res = now_len;
+
861 }
+
862 len = res;
+
863 out->len = headerlen + len;
+
864
+
865 if (se->debug) {
+
866 fuse_log(FUSE_LOG_DEBUG,
+
867 " unique: %llu, success, outsize: %i (splice)\n",
+
868 (unsigned long long) out->unique, out->len);
+
869 }
+
870
+
871 splice_flags = 0;
+
872 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
873 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
874 splice_flags |= SPLICE_F_MOVE;
+
875
+
876 if (se->io != NULL && se->io->splice_send != NULL) {
+
877 res = se->io->splice_send(llp->pipe[0], NULL,
+
878 ch ? ch->fd : se->fd, NULL, out->len,
+
879 splice_flags, se->userdata);
+
880 } else {
+
881 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
882 out->len, splice_flags);
+
883 }
+
884 if (res == -1) {
+
885 res = -errno;
+
886 perror("fuse: splice from pipe");
+
887 goto clear_pipe;
+
888 }
+
889 if (res != out->len) {
+
890 res = -EIO;
+
891 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
892 res, out->len);
+
893 goto clear_pipe;
+
894 }
+
895 return 0;
+
896
+
897clear_pipe:
+
898 fuse_ll_clear_pipe(se);
+
899 return res;
+
900
+
901fallback:
+
902 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
903}
+
904#else
+
905static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
906 struct iovec *iov, int iov_count,
+
907 struct fuse_bufvec *buf, unsigned int flags)
+
908{
+
909 size_t len = fuse_buf_size(buf);
+
910 (void) flags;
+
911
+
912 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
913}
+
914#endif
+
915
+
+ +
917 enum fuse_buf_copy_flags flags)
+
918{
+
919 struct iovec iov[2];
+
920 struct fuse_out_header out;
+
921 int res;
+
922
+
923 iov[0].iov_base = &out;
+
924 iov[0].iov_len = sizeof(struct fuse_out_header);
+
925
+
926 out.unique = req->unique;
+
927 out.error = 0;
+
928
+
929 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags);
+
930 if (res <= 0) {
+
931 fuse_free_req(req);
+
932 return res;
+
933 } else {
+
934 return fuse_reply_err(req, res);
+
935 }
+
936}
+
+
937
+
+
938int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
939{
+
940 struct fuse_statfs_out arg;
+
941 size_t size = req->se->conn.proto_minor < 4 ?
+
942 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
943
+
944 memset(&arg, 0, sizeof(arg));
+
945 convert_statfs(stbuf, &arg.st);
+
946
+
947 return send_reply_ok(req, &arg, size);
+
948}
+
+
949
+
+
950int fuse_reply_xattr(fuse_req_t req, size_t count)
+
951{
+
952 struct fuse_getxattr_out arg;
+
953
+
954 memset(&arg, 0, sizeof(arg));
+
955 arg.size = count;
+
956
+
957 return send_reply_ok(req, &arg, sizeof(arg));
+
958}
+
+
959
+
+
960int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
961{
+
962 struct fuse_lk_out arg;
+
963
+
964 memset(&arg, 0, sizeof(arg));
+
965 arg.lk.type = lock->l_type;
+
966 if (lock->l_type != F_UNLCK) {
+
967 arg.lk.start = lock->l_start;
+
968 if (lock->l_len == 0)
+
969 arg.lk.end = OFFSET_MAX;
+
970 else
+
971 arg.lk.end = lock->l_start + lock->l_len - 1;
+
972 }
+
973 arg.lk.pid = lock->l_pid;
+
974 return send_reply_ok(req, &arg, sizeof(arg));
+
975}
+
+
976
+
+
977int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
978{
+
979 struct fuse_bmap_out arg;
+
980
+
981 memset(&arg, 0, sizeof(arg));
+
982 arg.block = idx;
+
983
+
984 return send_reply_ok(req, &arg, sizeof(arg));
+
985}
+
+
986
+
987static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
988 size_t count)
+
989{
+
990 struct fuse_ioctl_iovec *fiov;
+
991 size_t i;
+
992
+
993 fiov = malloc(sizeof(fiov[0]) * count);
+
994 if (!fiov)
+
995 return NULL;
+
996
+
997 for (i = 0; i < count; i++) {
+
998 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
999 fiov[i].len = iov[i].iov_len;
+
1000 }
+
1001
+
1002 return fiov;
+
1003}
+
1004
+
+ +
1006 const struct iovec *in_iov, size_t in_count,
+
1007 const struct iovec *out_iov, size_t out_count)
+
1008{
+
1009 struct fuse_ioctl_out arg;
+
1010 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1011 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1012 struct iovec iov[4];
+
1013 size_t count = 1;
+
1014 int res;
+
1015
+
1016 memset(&arg, 0, sizeof(arg));
+
1017 arg.flags |= FUSE_IOCTL_RETRY;
+
1018 arg.in_iovs = in_count;
+
1019 arg.out_iovs = out_count;
+
1020 iov[count].iov_base = &arg;
+
1021 iov[count].iov_len = sizeof(arg);
+
1022 count++;
+
1023
+
1024 if (req->se->conn.proto_minor < 16) {
+
1025 if (in_count) {
+
1026 iov[count].iov_base = (void *)in_iov;
+
1027 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1028 count++;
+
1029 }
+
1030
+
1031 if (out_count) {
+
1032 iov[count].iov_base = (void *)out_iov;
+
1033 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1034 count++;
+
1035 }
+
1036 } else {
+
1037 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1038 if (sizeof(void *) == 4 && req->ioctl_64bit) {
+
1039 res = fuse_reply_err(req, EINVAL);
+
1040 goto out;
+
1041 }
+
1042
+
1043 if (in_count) {
+
1044 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1045 if (!in_fiov)
+
1046 goto enomem;
+
1047
+
1048 iov[count].iov_base = (void *)in_fiov;
+
1049 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1050 count++;
+
1051 }
+
1052 if (out_count) {
+
1053 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1054 if (!out_fiov)
+
1055 goto enomem;
+
1056
+
1057 iov[count].iov_base = (void *)out_fiov;
+
1058 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1059 count++;
+
1060 }
+
1061 }
+
1062
+
1063 res = send_reply_iov(req, 0, iov, count);
+
1064out:
+
1065 free(in_fiov);
+
1066 free(out_fiov);
+
1067
+
1068 return res;
+
1069
+
1070enomem:
+
1071 res = fuse_reply_err(req, ENOMEM);
+
1072 goto out;
+
1073}
+
+
1074
+
+
1075int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1076{
+
1077 struct fuse_ioctl_out arg;
+
1078 struct iovec iov[3];
+
1079 size_t count = 1;
+
1080
+
1081 memset(&arg, 0, sizeof(arg));
+
1082 arg.result = result;
+
1083 iov[count].iov_base = &arg;
+
1084 iov[count].iov_len = sizeof(arg);
+
1085 count++;
+
1086
+
1087 if (size) {
+
1088 iov[count].iov_base = (char *) buf;
+
1089 iov[count].iov_len = size;
+
1090 count++;
+
1091 }
+
1092
+
1093 return send_reply_iov(req, 0, iov, count);
+
1094}
+
+
1095
+
+
1096int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1097 int count)
+
1098{
+
1099 struct iovec *padded_iov;
+
1100 struct fuse_ioctl_out arg;
+
1101 int res;
+
1102
+
1103 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1104 if (padded_iov == NULL)
+
1105 return fuse_reply_err(req, ENOMEM);
+
1106
+
1107 memset(&arg, 0, sizeof(arg));
+
1108 arg.result = result;
+
1109 padded_iov[1].iov_base = &arg;
+
1110 padded_iov[1].iov_len = sizeof(arg);
+
1111
+
1112 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1113
+
1114 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1115 free(padded_iov);
+
1116
+
1117 return res;
+
1118}
+
+
1119
+
+
1120int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1121{
+
1122 struct fuse_poll_out arg;
+
1123
+
1124 memset(&arg, 0, sizeof(arg));
+
1125 arg.revents = revents;
+
1126
+
1127 return send_reply_ok(req, &arg, sizeof(arg));
+
1128}
+
+
1129
+
+
1130int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1131{
+
1132 struct fuse_lseek_out arg;
+
1133
+
1134 memset(&arg, 0, sizeof(arg));
+
1135 arg.offset = off;
+
1136
+
1137 return send_reply_ok(req, &arg, sizeof(arg));
+
1138}
+
+
1139
+
1140static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1141{
+
1142 char *name = (char *) inarg;
+
1143
+
1144 if (req->se->op.lookup)
+
1145 req->se->op.lookup(req, nodeid, name);
+
1146 else
+
1147 fuse_reply_err(req, ENOSYS);
+
1148}
+
1149
+
1150static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1151{
+
1152 struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
1153
+
1154 if (req->se->op.forget)
+
1155 req->se->op.forget(req, nodeid, arg->nlookup);
+
1156 else
+
1157 fuse_reply_none(req);
+
1158}
+
1159
+
1160static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+
1161 const void *inarg)
+
1162{
+
1163 struct fuse_batch_forget_in *arg = (void *) inarg;
+
1164 struct fuse_forget_one *param = (void *) PARAM(arg);
+
1165 unsigned int i;
+
1166
+
1167 (void) nodeid;
+
1168
+
1169 if (req->se->op.forget_multi) {
+
1170 req->se->op.forget_multi(req, arg->count,
+
1171 (struct fuse_forget_data *) param);
+
1172 } else if (req->se->op.forget) {
+
1173 for (i = 0; i < arg->count; i++) {
+
1174 struct fuse_forget_one *forget = &param[i];
+
1175 struct fuse_req *dummy_req;
+
1176
+
1177 dummy_req = fuse_ll_alloc_req(req->se);
+
1178 if (dummy_req == NULL)
+
1179 break;
+
1180
+
1181 dummy_req->unique = req->unique;
+
1182 dummy_req->ctx = req->ctx;
+
1183 dummy_req->ch = NULL;
+
1184
+
1185 req->se->op.forget(dummy_req, forget->nodeid,
+
1186 forget->nlookup);
+
1187 }
+
1188 fuse_reply_none(req);
+
1189 } else {
+
1190 fuse_reply_none(req);
+
1191 }
+
1192}
+
1193
+
1194static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1195{
+
1196 struct fuse_file_info *fip = NULL;
+
1197 struct fuse_file_info fi;
+
1198
+
1199 if (req->se->conn.proto_minor >= 9) {
+
1200 struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
1201
+
1202 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1203 memset(&fi, 0, sizeof(fi));
+
1204 fi.fh = arg->fh;
+
1205 fip = &fi;
+
1206 }
+
1207 }
+
1208
+
1209 if (req->se->op.getattr)
+
1210 req->se->op.getattr(req, nodeid, fip);
+
1211 else
+
1212 fuse_reply_err(req, ENOSYS);
+
1213}
+
1214
+
1215static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1216{
+
1217 struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
1218
+
1219 if (req->se->op.setattr) {
+
1220 struct fuse_file_info *fi = NULL;
+
1221 struct fuse_file_info fi_store;
+
1222 struct stat stbuf;
+
1223 memset(&stbuf, 0, sizeof(stbuf));
+
1224 convert_attr(arg, &stbuf);
+
1225 if (arg->valid & FATTR_FH) {
+
1226 arg->valid &= ~FATTR_FH;
+
1227 memset(&fi_store, 0, sizeof(fi_store));
+
1228 fi = &fi_store;
+
1229 fi->fh = arg->fh;
+
1230 }
+
1231 arg->valid &=
+
1232 FUSE_SET_ATTR_MODE |
+
1233 FUSE_SET_ATTR_UID |
+
1234 FUSE_SET_ATTR_GID |
+
1235 FUSE_SET_ATTR_SIZE |
+
1236 FUSE_SET_ATTR_ATIME |
+
1237 FUSE_SET_ATTR_MTIME |
+
1238 FUSE_SET_ATTR_KILL_SUID |
+
1239 FUSE_SET_ATTR_KILL_SGID |
+
1240 FUSE_SET_ATTR_ATIME_NOW |
+
1241 FUSE_SET_ATTR_MTIME_NOW |
+
1242 FUSE_SET_ATTR_CTIME;
+
1243
+
1244 req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+
1245 } else
+
1246 fuse_reply_err(req, ENOSYS);
+
1247}
+
1248
+
1249static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1250{
+
1251 struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
1252
+
1253 if (req->se->op.access)
+
1254 req->se->op.access(req, nodeid, arg->mask);
+
1255 else
+
1256 fuse_reply_err(req, ENOSYS);
+
1257}
+
1258
+
1259static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1260{
+
1261 (void) inarg;
+
1262
+
1263 if (req->se->op.readlink)
+
1264 req->se->op.readlink(req, nodeid);
+
1265 else
+
1266 fuse_reply_err(req, ENOSYS);
+
1267}
+
1268
+
1269static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1270{
+
1271 struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+
1272 char *name = PARAM(arg);
+
1273
+
1274 if (req->se->conn.proto_minor >= 12)
+
1275 req->ctx.umask = arg->umask;
+
1276 else
+
1277 name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1278
+
1279 if (req->se->op.mknod)
+
1280 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1281 else
+
1282 fuse_reply_err(req, ENOSYS);
+
1283}
+
1284
+
1285static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1286{
+
1287 struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
1288
+
1289 if (req->se->conn.proto_minor >= 12)
+
1290 req->ctx.umask = arg->umask;
+
1291
+
1292 if (req->se->op.mkdir)
+
1293 req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+
1294 else
+
1295 fuse_reply_err(req, ENOSYS);
+
1296}
+
1297
+
1298static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1299{
+
1300 char *name = (char *) inarg;
+
1301
+
1302 if (req->se->op.unlink)
+
1303 req->se->op.unlink(req, nodeid, name);
+
1304 else
+
1305 fuse_reply_err(req, ENOSYS);
+
1306}
+
1307
+
1308static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1309{
+
1310 char *name = (char *) inarg;
+
1311
+
1312 if (req->se->op.rmdir)
+
1313 req->se->op.rmdir(req, nodeid, name);
+
1314 else
+
1315 fuse_reply_err(req, ENOSYS);
+
1316}
+
1317
+
1318static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1319{
+
1320 char *name = (char *) inarg;
+
1321 char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
1322
+
1323 if (req->se->op.symlink)
+
1324 req->se->op.symlink(req, linkname, nodeid, name);
+
1325 else
+
1326 fuse_reply_err(req, ENOSYS);
+
1327}
+
1328
+
1329static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1330{
+
1331 struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+
1332 char *oldname = PARAM(arg);
+
1333 char *newname = oldname + strlen(oldname) + 1;
+
1334
+
1335 if (req->se->op.rename)
+
1336 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1337 0);
+
1338 else
+
1339 fuse_reply_err(req, ENOSYS);
+
1340}
+
1341
+
1342static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1343{
+
1344 struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg;
+
1345 char *oldname = PARAM(arg);
+
1346 char *newname = oldname + strlen(oldname) + 1;
+
1347
+
1348 if (req->se->op.rename)
+
1349 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1350 arg->flags);
+
1351 else
+
1352 fuse_reply_err(req, ENOSYS);
+
1353}
+
1354
+
1355static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1356{
+
1357 struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
1358
+
1359 if (req->se->op.link)
+
1360 req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+
1361 else
+
1362 fuse_reply_err(req, ENOSYS);
+
1363}
+
1364
+
1365static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1366{
+
1367 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1368
+
1369 if (req->se->op.tmpfile) {
+
1370 struct fuse_file_info fi;
+
1371
+
1372 memset(&fi, 0, sizeof(fi));
+
1373 fi.flags = arg->flags;
+
1374
+
1375 if (req->se->conn.proto_minor >= 12)
+
1376 req->ctx.umask = arg->umask;
+
1377
+
1378 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1379 } else
+
1380 fuse_reply_err(req, ENOSYS);
+
1381}
+
1382
+
1383static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1384{
+
1385 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1386
+
1387 if (req->se->op.create) {
+
1388 struct fuse_file_info fi;
+
1389 char *name = PARAM(arg);
+
1390
+
1391 memset(&fi, 0, sizeof(fi));
+
1392 fi.flags = arg->flags;
+
1393
+
1394 if (req->se->conn.proto_minor >= 12)
+
1395 req->ctx.umask = arg->umask;
+
1396 else
+
1397 name = (char *) inarg + sizeof(struct fuse_open_in);
+
1398
+
1399 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1400 } else
+
1401 fuse_reply_err(req, ENOSYS);
+
1402}
+
1403
+
1404static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1405{
+
1406 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1407 struct fuse_file_info fi;
+
1408
+
1409 memset(&fi, 0, sizeof(fi));
+
1410 fi.flags = arg->flags;
+
1411
+
1412 if (req->se->op.open)
+
1413 req->se->op.open(req, nodeid, &fi);
+
1414 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1415 fuse_reply_err(req, ENOSYS);
+
1416 else
+
1417 fuse_reply_open(req, &fi);
+
1418}
+
1419
+
1420static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1421{
+
1422 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1423
+
1424 if (req->se->op.read) {
+
1425 struct fuse_file_info fi;
+
1426
+
1427 memset(&fi, 0, sizeof(fi));
+
1428 fi.fh = arg->fh;
+
1429 if (req->se->conn.proto_minor >= 9) {
+
1430 fi.lock_owner = arg->lock_owner;
+
1431 fi.flags = arg->flags;
+
1432 }
+
1433 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1434 } else
+
1435 fuse_reply_err(req, ENOSYS);
+
1436}
+
1437
+
1438static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1439{
+
1440 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1441 struct fuse_file_info fi;
+
1442 char *param;
+
1443
+
1444 memset(&fi, 0, sizeof(fi));
+
1445 fi.fh = arg->fh;
+
1446 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1447
+
1448 if (req->se->conn.proto_minor < 9) {
+
1449 param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1450 } else {
+
1451 fi.lock_owner = arg->lock_owner;
+
1452 fi.flags = arg->flags;
+
1453 param = PARAM(arg);
+
1454 }
+
1455
+
1456 if (req->se->op.write)
+
1457 req->se->op.write(req, nodeid, param, arg->size,
+
1458 arg->offset, &fi);
+
1459 else
+
1460 fuse_reply_err(req, ENOSYS);
+
1461}
+
1462
+
1463static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+
1464 const struct fuse_buf *ibuf)
+
1465{
+
1466 struct fuse_session *se = req->se;
+
1467 struct fuse_bufvec bufv = {
+
1468 .buf[0] = *ibuf,
+
1469 .count = 1,
+
1470 };
+
1471 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1472 struct fuse_file_info fi;
+
1473
+
1474 memset(&fi, 0, sizeof(fi));
+
1475 fi.fh = arg->fh;
+
1476 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1477
+
1478 if (se->conn.proto_minor < 9) {
+
1479 bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1480 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1481 FUSE_COMPAT_WRITE_IN_SIZE;
+
1482 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1483 } else {
+
1484 fi.lock_owner = arg->lock_owner;
+
1485 fi.flags = arg->flags;
+
1486 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1487 bufv.buf[0].mem = PARAM(arg);
+
1488
+
1489 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1490 sizeof(struct fuse_write_in);
+
1491 }
+
1492 if (bufv.buf[0].size < arg->size) {
+
1493 fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n");
+
1494 fuse_reply_err(req, EIO);
+
1495 goto out;
+
1496 }
+
1497 bufv.buf[0].size = arg->size;
+
1498
+
1499 se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
1500
+
1501out:
+
1502 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1503 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1504 fuse_ll_clear_pipe(se);
+
1505}
+
1506
+
1507static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1508{
+
1509 struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+
1510 struct fuse_file_info fi;
+
1511
+
1512 memset(&fi, 0, sizeof(fi));
+
1513 fi.fh = arg->fh;
+
1514 fi.flush = 1;
+
1515 if (req->se->conn.proto_minor >= 7)
+
1516 fi.lock_owner = arg->lock_owner;
+
1517
+
1518 if (req->se->op.flush)
+
1519 req->se->op.flush(req, nodeid, &fi);
+
1520 else
+
1521 fuse_reply_err(req, ENOSYS);
+
1522}
+
1523
+
1524static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1525{
+
1526 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1527 struct fuse_file_info fi;
+
1528
+
1529 memset(&fi, 0, sizeof(fi));
+
1530 fi.flags = arg->flags;
+
1531 fi.fh = arg->fh;
+
1532 if (req->se->conn.proto_minor >= 8) {
+
1533 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1534 fi.lock_owner = arg->lock_owner;
+
1535 }
+
1536 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1537 fi.flock_release = 1;
+
1538 fi.lock_owner = arg->lock_owner;
+
1539 }
+
1540
+
1541 if (req->se->op.release)
+
1542 req->se->op.release(req, nodeid, &fi);
+
1543 else
+
1544 fuse_reply_err(req, 0);
+
1545}
+
1546
+
1547static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1548{
+
1549 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1550 struct fuse_file_info fi;
+
1551 int datasync = arg->fsync_flags & 1;
+
1552
+
1553 memset(&fi, 0, sizeof(fi));
+
1554 fi.fh = arg->fh;
+
1555
+
1556 if (req->se->op.fsync)
+
1557 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1558 else
+
1559 fuse_reply_err(req, ENOSYS);
+
1560}
+
1561
+
1562static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1563{
+
1564 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1565 struct fuse_file_info fi;
+
1566
+
1567 memset(&fi, 0, sizeof(fi));
+
1568 fi.flags = arg->flags;
+
1569
+
1570 if (req->se->op.opendir)
+
1571 req->se->op.opendir(req, nodeid, &fi);
+
1572 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1573 fuse_reply_err(req, ENOSYS);
+
1574 else
+
1575 fuse_reply_open(req, &fi);
+
1576}
+
1577
+
1578static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1579{
+
1580 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1581 struct fuse_file_info fi;
+
1582
+
1583 memset(&fi, 0, sizeof(fi));
+
1584 fi.fh = arg->fh;
+
1585
+
1586 if (req->se->op.readdir)
+
1587 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1588 else
+
1589 fuse_reply_err(req, ENOSYS);
+
1590}
+
1591
+
1592static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1593{
+
1594 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1595 struct fuse_file_info fi;
+
1596
+
1597 memset(&fi, 0, sizeof(fi));
+
1598 fi.fh = arg->fh;
+
1599
+
1600 if (req->se->op.readdirplus)
+
1601 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1602 else
+
1603 fuse_reply_err(req, ENOSYS);
+
1604}
+
1605
+
1606static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1607{
+
1608 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1609 struct fuse_file_info fi;
+
1610
+
1611 memset(&fi, 0, sizeof(fi));
+
1612 fi.flags = arg->flags;
+
1613 fi.fh = arg->fh;
+
1614
+
1615 if (req->se->op.releasedir)
+
1616 req->se->op.releasedir(req, nodeid, &fi);
+
1617 else
+
1618 fuse_reply_err(req, 0);
+
1619}
+
1620
+
1621static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1622{
+
1623 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1624 struct fuse_file_info fi;
+
1625 int datasync = arg->fsync_flags & 1;
+
1626
+
1627 memset(&fi, 0, sizeof(fi));
+
1628 fi.fh = arg->fh;
+
1629
+
1630 if (req->se->op.fsyncdir)
+
1631 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
1632 else
+
1633 fuse_reply_err(req, ENOSYS);
+
1634}
+
1635
+
1636static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1637{
+
1638 (void) nodeid;
+
1639 (void) inarg;
+
1640
+
1641 if (req->se->op.statfs)
+
1642 req->se->op.statfs(req, nodeid);
+
1643 else {
+
1644 struct statvfs buf = {
+
1645 .f_namemax = 255,
+
1646 .f_bsize = 512,
+
1647 };
+
1648 fuse_reply_statfs(req, &buf);
+
1649 }
+
1650}
+
1651
+
1652static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1653{
+
1654 struct fuse_session *se = req->se;
+
1655 unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT);
+
1656 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+
1657 char *name = xattr_ext ? PARAM(arg) :
+
1658 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
1659 char *value = name + strlen(name) + 1;
+
1660
+
1661 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
1662 if (req->se->op.setxattr)
+
1663 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
1664 arg->flags);
+
1665 else
+
1666 fuse_reply_err(req, ENOSYS);
+
1667}
+
1668
+
1669static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1670{
+
1671 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1672
+
1673 if (req->se->op.getxattr)
+
1674 req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+
1675 else
+
1676 fuse_reply_err(req, ENOSYS);
+
1677}
+
1678
+
1679static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1680{
+
1681 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1682
+
1683 if (req->se->op.listxattr)
+
1684 req->se->op.listxattr(req, nodeid, arg->size);
+
1685 else
+
1686 fuse_reply_err(req, ENOSYS);
+
1687}
+
1688
+
1689static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1690{
+
1691 char *name = (char *) inarg;
+
1692
+
1693 if (req->se->op.removexattr)
+
1694 req->se->op.removexattr(req, nodeid, name);
+
1695 else
+
1696 fuse_reply_err(req, ENOSYS);
+
1697}
+
1698
+
1699static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+
1700 struct flock *flock)
+
1701{
+
1702 memset(flock, 0, sizeof(struct flock));
+
1703 flock->l_type = fl->type;
+
1704 flock->l_whence = SEEK_SET;
+
1705 flock->l_start = fl->start;
+
1706 if (fl->end == OFFSET_MAX)
+
1707 flock->l_len = 0;
+
1708 else
+
1709 flock->l_len = fl->end - fl->start + 1;
+
1710 flock->l_pid = fl->pid;
+
1711}
+
1712
+
1713static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1714{
+
1715 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1716 struct fuse_file_info fi;
+
1717 struct flock flock;
+
1718
+
1719 memset(&fi, 0, sizeof(fi));
+
1720 fi.fh = arg->fh;
+
1721 fi.lock_owner = arg->owner;
+
1722
+
1723 convert_fuse_file_lock(&arg->lk, &flock);
+
1724 if (req->se->op.getlk)
+
1725 req->se->op.getlk(req, nodeid, &fi, &flock);
+
1726 else
+
1727 fuse_reply_err(req, ENOSYS);
+
1728}
+
1729
+
1730static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+
1731 const void *inarg, int sleep)
+
1732{
+
1733 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1734 struct fuse_file_info fi;
+
1735 struct flock flock;
+
1736
+
1737 memset(&fi, 0, sizeof(fi));
+
1738 fi.fh = arg->fh;
+
1739 fi.lock_owner = arg->owner;
+
1740
+
1741 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
1742 int op = 0;
+
1743
+
1744 switch (arg->lk.type) {
+
1745 case F_RDLCK:
+
1746 op = LOCK_SH;
+
1747 break;
+
1748 case F_WRLCK:
+
1749 op = LOCK_EX;
+
1750 break;
+
1751 case F_UNLCK:
+
1752 op = LOCK_UN;
+
1753 break;
+
1754 }
+
1755 if (!sleep)
+
1756 op |= LOCK_NB;
+
1757
+
1758 if (req->se->op.flock)
+
1759 req->se->op.flock(req, nodeid, &fi, op);
+
1760 else
+
1761 fuse_reply_err(req, ENOSYS);
+
1762 } else {
+
1763 convert_fuse_file_lock(&arg->lk, &flock);
+
1764 if (req->se->op.setlk)
+
1765 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
1766 else
+
1767 fuse_reply_err(req, ENOSYS);
+
1768 }
+
1769}
+
1770
+
1771static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1772{
+
1773 do_setlk_common(req, nodeid, inarg, 0);
+
1774}
+
1775
+
1776static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1777{
+
1778 do_setlk_common(req, nodeid, inarg, 1);
+
1779}
+
1780
+
1781static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
1782{
+
1783 struct fuse_req *curr;
+
1784
+
1785 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
1786 if (curr->unique == req->u.i.unique) {
+ +
1788 void *data;
+
1789
+
1790 curr->ref_cnt++;
+
1791 pthread_mutex_unlock(&se->lock);
+
1792
+
1793 /* Ugh, ugly locking */
+
1794 pthread_mutex_lock(&curr->lock);
+
1795 pthread_mutex_lock(&se->lock);
+
1796 curr->interrupted = 1;
+
1797 func = curr->u.ni.func;
+
1798 data = curr->u.ni.data;
+
1799 pthread_mutex_unlock(&se->lock);
+
1800 if (func)
+
1801 func(curr, data);
+
1802 pthread_mutex_unlock(&curr->lock);
+
1803
+
1804 pthread_mutex_lock(&se->lock);
+
1805 curr->ref_cnt--;
+
1806 if (!curr->ref_cnt) {
+
1807 destroy_req(curr);
+
1808 }
+
1809
+
1810 return 1;
+
1811 }
+
1812 }
+
1813 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1814 curr = curr->next) {
+
1815 if (curr->u.i.unique == req->u.i.unique)
+
1816 return 1;
+
1817 }
+
1818 return 0;
+
1819}
+
1820
+
1821static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1822{
+
1823 struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+
1824 struct fuse_session *se = req->se;
+
1825
+
1826 (void) nodeid;
+
1827 if (se->debug)
+
1828 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
1829 (unsigned long long) arg->unique);
+
1830
+
1831 req->u.i.unique = arg->unique;
+
1832
+
1833 pthread_mutex_lock(&se->lock);
+
1834 if (find_interrupted(se, req)) {
+
1835 fuse_chan_put(req->ch);
+
1836 req->ch = NULL;
+
1837 destroy_req(req);
+
1838 } else
+
1839 list_add_req(req, &se->interrupts);
+
1840 pthread_mutex_unlock(&se->lock);
+
1841}
+
1842
+
1843static struct fuse_req *check_interrupt(struct fuse_session *se,
+
1844 struct fuse_req *req)
+
1845{
+
1846 struct fuse_req *curr;
+
1847
+
1848 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1849 curr = curr->next) {
+
1850 if (curr->u.i.unique == req->unique) {
+
1851 req->interrupted = 1;
+
1852 list_del_req(curr);
+
1853 fuse_chan_put(curr->ch);
+
1854 curr->ch = NULL;
+
1855 destroy_req(curr);
+
1856 return NULL;
+
1857 }
+
1858 }
+
1859 curr = se->interrupts.next;
+
1860 if (curr != &se->interrupts) {
+
1861 list_del_req(curr);
+
1862 list_init_req(curr);
+
1863 return curr;
+
1864 } else
+
1865 return NULL;
+
1866}
+
1867
+
1868static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1869{
+
1870 struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
1871
+
1872 if (req->se->op.bmap)
+
1873 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
1874 else
+
1875 fuse_reply_err(req, ENOSYS);
+
1876}
+
1877
+
1878static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1879{
+
1880 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+
1881 unsigned int flags = arg->flags;
+
1882 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
1883 struct fuse_file_info fi;
+
1884
+
1885 if (flags & FUSE_IOCTL_DIR &&
+
1886 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
1887 fuse_reply_err(req, ENOTTY);
+
1888 return;
+
1889 }
+
1890
+
1891 memset(&fi, 0, sizeof(fi));
+
1892 fi.fh = arg->fh;
+
1893
+
1894 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
1895 !(flags & FUSE_IOCTL_32BIT)) {
+
1896 req->ioctl_64bit = 1;
+
1897 }
+
1898
+
1899 if (req->se->op.ioctl)
+
1900 req->se->op.ioctl(req, nodeid, arg->cmd,
+
1901 (void *)(uintptr_t)arg->arg, &fi, flags,
+
1902 in_buf, arg->in_size, arg->out_size);
+
1903 else
+
1904 fuse_reply_err(req, ENOSYS);
+
1905}
+
1906
+
+
1907void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
1908{
+
1909 free(ph);
+
1910}
+
+
1911
+
1912static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1913{
+
1914 struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+
1915 struct fuse_file_info fi;
+
1916
+
1917 memset(&fi, 0, sizeof(fi));
+
1918 fi.fh = arg->fh;
+
1919 fi.poll_events = arg->events;
+
1920
+
1921 if (req->se->op.poll) {
+
1922 struct fuse_pollhandle *ph = NULL;
+
1923
+
1924 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
1925 ph = malloc(sizeof(struct fuse_pollhandle));
+
1926 if (ph == NULL) {
+
1927 fuse_reply_err(req, ENOMEM);
+
1928 return;
+
1929 }
+
1930 ph->kh = arg->kh;
+
1931 ph->se = req->se;
+
1932 }
+
1933
+
1934 req->se->op.poll(req, nodeid, &fi, ph);
+
1935 } else {
+
1936 fuse_reply_err(req, ENOSYS);
+
1937 }
+
1938}
+
1939
+
1940static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1941{
+
1942 struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+
1943 struct fuse_file_info fi;
+
1944
+
1945 memset(&fi, 0, sizeof(fi));
+
1946 fi.fh = arg->fh;
+
1947
+
1948 if (req->se->op.fallocate)
+
1949 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+
1950 else
+
1951 fuse_reply_err(req, ENOSYS);
+
1952}
+
1953
+
1954static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
+
1955{
+
1956 struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
+
1957 struct fuse_file_info fi_in, fi_out;
+
1958
+
1959 memset(&fi_in, 0, sizeof(fi_in));
+
1960 fi_in.fh = arg->fh_in;
+
1961
+
1962 memset(&fi_out, 0, sizeof(fi_out));
+
1963 fi_out.fh = arg->fh_out;
+
1964
+
1965
+
1966 if (req->se->op.copy_file_range)
+
1967 req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
+
1968 &fi_in, arg->nodeid_out,
+
1969 arg->off_out, &fi_out, arg->len,
+
1970 arg->flags);
+
1971 else
+
1972 fuse_reply_err(req, ENOSYS);
+
1973}
+
1974
+
1975static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1976{
+
1977 struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg;
+
1978 struct fuse_file_info fi;
+
1979
+
1980 memset(&fi, 0, sizeof(fi));
+
1981 fi.fh = arg->fh;
+
1982
+
1983 if (req->se->op.lseek)
+
1984 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
1985 else
+
1986 fuse_reply_err(req, ENOSYS);
+
1987}
+
1988
+
1989static bool want_flags_valid(uint64_t capable, uint64_t want)
+
1990{
+
1991 uint64_t unknown_flags = want & (~capable);
+
1992 if (unknown_flags != 0) {
+
1993 fuse_log(FUSE_LOG_ERR,
+
1994 "fuse: unknown connection 'want' flags: 0x%08lx\n",
+
1995 unknown_flags);
+
1996 return false;
+
1997 }
+
1998 return true;
+
1999}
+
2000
+
2001/* Prevent bogus data races (bogus since "init" is called before
+
2002 * multi-threading becomes relevant */
+
2003static __attribute__((no_sanitize("thread")))
+
2004void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2005{
+
2006 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
2007 struct fuse_init_out outarg;
+
2008 struct fuse_session *se = req->se;
+
2009 size_t bufsize = se->bufsize;
+
2010 size_t outargsize = sizeof(outarg);
+
2011 uint64_t inargflags = 0;
+
2012 uint64_t outargflags = 0;
+
2013 bool buf_reallocable = se->buf_reallocable;
+
2014 (void) nodeid;
+
2015 if (se->debug) {
+
2016 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2017 if (arg->major == 7 && arg->minor >= 6) {
+
2018 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2019 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2020 arg->max_readahead);
+
2021 }
+
2022 }
+
2023 se->conn.proto_major = arg->major;
+
2024 se->conn.proto_minor = arg->minor;
+
2025 se->conn.capable_ext = 0;
+
2026 se->conn.want_ext = 0;
+
2027
+
2028 memset(&outarg, 0, sizeof(outarg));
+
2029 outarg.major = FUSE_KERNEL_VERSION;
+
2030 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2031
+
2032 if (arg->major < 7) {
+
2033 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2034 arg->major, arg->minor);
+
2035 fuse_reply_err(req, EPROTO);
+
2036 return;
+
2037 }
+
2038
+
2039 if (arg->major > 7) {
+
2040 /* Wait for a second INIT request with a 7.X version */
+
2041 send_reply_ok(req, &outarg, sizeof(outarg));
+
2042 return;
+
2043 }
+
2044
+
2045 if (arg->minor >= 6) {
+
2046 if (arg->max_readahead < se->conn.max_readahead)
+
2047 se->conn.max_readahead = arg->max_readahead;
+
2048 inargflags = arg->flags;
+
2049 if (inargflags & FUSE_INIT_EXT)
+
2050 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2051 if (inargflags & FUSE_ASYNC_READ)
+
2052 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2053 if (inargflags & FUSE_POSIX_LOCKS)
+
2054 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2055 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2056 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2057 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2058 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2059 if (inargflags & FUSE_DONT_MASK)
+
2060 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2061 if (inargflags & FUSE_FLOCK_LOCKS)
+
2062 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2063 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2064 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2065 if (inargflags & FUSE_DO_READDIRPLUS)
+
2066 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2067 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2068 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2069 if (inargflags & FUSE_ASYNC_DIO)
+
2070 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2071 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2072 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2073 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2074 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2075 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2076 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2077 if (inargflags & FUSE_POSIX_ACL)
+
2078 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2079 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2080 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2081 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2082 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2083 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2084 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2085 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2086 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2087 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2088 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2089 if (inargflags & FUSE_SETXATTR_EXT)
+
2090 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2091 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2092 size_t max_bufsize =
+
2093 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2094 + FUSE_BUFFER_HEADER_SIZE;
+
2095 if (bufsize > max_bufsize) {
+
2096 bufsize = max_bufsize;
+
2097 }
+
2098 buf_reallocable = false;
+
2099 }
+
2100 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2101 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2102 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2103 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2104 if (inargflags & FUSE_PASSTHROUGH)
+
2105 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2106 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2107 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2108 } else {
+
2109 se->conn.max_readahead = 0;
+
2110 }
+
2111
+
2112 if (se->conn.proto_minor >= 14) {
+
2113#ifdef HAVE_SPLICE
+
2114#ifdef HAVE_VMSPLICE
+
2115 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2116 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2118 }
+
2119#endif
+
2120 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2121 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2122 }
+
2123#endif
+
2124 }
+
2125 if (se->conn.proto_minor >= 18)
+
2126 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2127
+
2128 /* Default settings for modern filesystems.
+
2129 *
+
2130 * Most of these capabilities were disabled by default in
+
2131 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2132 * we can finally enable them by default (as long as they're
+
2133 * supported by the kernel).
+
2134 */
+
2135#define LL_SET_DEFAULT(cond, cap) \
+
2136 if ((cond)) \
+
2137 fuse_set_feature_flag(&se->conn, cap)
+
2138
+
2139 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2140 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2141 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2142 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2143 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2144 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2145 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2147 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2148 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2149 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2151
+
2152 /* This could safely become default, but libfuse needs an API extension
+
2153 * to support it
+
2154 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2155 */
+
2156
+
2157 se->conn.time_gran = 1;
+
2158
+
2159 se->got_init = 1;
+
2160 if (se->op.init) {
+
2161 uint64_t want_ext_default = se->conn.want_ext;
+
2162 uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext);
+
2163 int rc;
+
2164
+
2165 // Apply the first 32 bits of capable_ext to capable
+
2166 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2167 se->conn.want = want_default;
+
2168
+
2169 se->op.init(se->userdata, &se->conn);
+
2170
+
2171 /*
+
2172 * se->conn.want is 32-bit value and deprecated in favour of
+
2173 * se->conn.want_ext
+
2174 * Userspace might still use conn.want - we need to convert it
+
2175 */
+
2176 rc = convert_to_conn_want_ext(&se->conn, want_ext_default,
+
2177 want_default);
+
2178 if (rc != 0) {
+
2179 fuse_reply_err(req, EPROTO);
+
2180 se->error = -EPROTO;
+ +
2182 return;
+
2183 }
+
2184 }
+
2185
+
2186 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2187 fuse_reply_err(req, EPROTO);
+
2188 se->error = -EPROTO;
+ +
2190 return;
+
2191 }
+
2192
+
2193 unsigned max_read_mo = get_max_read(se->mo);
+
2194 if (se->conn.max_read != max_read_mo) {
+
2195 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2196 "requested different maximum read size (%u vs %u)\n",
+
2197 se->conn.max_read, max_read_mo);
+
2198 fuse_reply_err(req, EPROTO);
+
2199 se->error = -EPROTO;
+ +
2201 return;
+
2202 }
+
2203
+
2204 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2205 fuse_log(FUSE_LOG_ERR,
+
2206 "fuse: warning: buffer size too small: %zu\n",
+
2207 bufsize);
+
2208 bufsize = FUSE_MIN_READ_BUFFER;
+
2209 }
+
2210
+
2211 if (buf_reallocable)
+
2212 bufsize = UINT_MAX;
+
2213 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2214 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2215
+
2216 if (arg->flags & FUSE_MAX_PAGES) {
+
2217 outarg.flags |= FUSE_MAX_PAGES;
+
2218 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2219 }
+
2220 outargflags = outarg.flags;
+
2221 /* Always enable big writes, this is superseded
+
2222 by the max_write option */
+
2223 outargflags |= FUSE_BIG_WRITES;
+
2224
+
2225 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2226 outargflags |= FUSE_ASYNC_READ;
+
2227 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2228 outargflags |= FUSE_POSIX_LOCKS;
+
2229 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2230 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2231 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2232 outargflags |= FUSE_EXPORT_SUPPORT;
+
2233 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2234 outargflags |= FUSE_DONT_MASK;
+
2235 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2236 outargflags |= FUSE_FLOCK_LOCKS;
+
2237 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2238 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2239 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2240 outargflags |= FUSE_DO_READDIRPLUS;
+
2241 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2242 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2243 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2244 outargflags |= FUSE_ASYNC_DIO;
+
2245 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2246 outargflags |= FUSE_WRITEBACK_CACHE;
+
2247 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2248 outargflags |= FUSE_PARALLEL_DIROPS;
+
2249 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2250 outargflags |= FUSE_POSIX_ACL;
+
2251 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2252 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2253 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2254 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2255 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2256 outargflags |= FUSE_CACHE_SYMLINKS;
+
2257 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2258 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2259 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2260 outargflags |= FUSE_SETXATTR_EXT;
+
2261 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2262 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2263 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2264 outargflags |= FUSE_PASSTHROUGH;
+
2265 /*
+
2266 * outarg.max_stack_depth includes the fuse stack layer,
+
2267 * so it is one more than max_backing_stack_depth.
+
2268 */
+
2269 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2270 }
+
2271 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2272 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2273
+
2274 if (inargflags & FUSE_INIT_EXT) {
+
2275 outargflags |= FUSE_INIT_EXT;
+
2276 outarg.flags2 = outargflags >> 32;
+
2277 }
+
2278
+
2279 outarg.flags = outargflags;
+
2280
+
2281 outarg.max_readahead = se->conn.max_readahead;
+
2282 outarg.max_write = se->conn.max_write;
+
2283 if (se->conn.proto_minor >= 13) {
+
2284 if (se->conn.max_background >= (1 << 16))
+
2285 se->conn.max_background = (1 << 16) - 1;
+
2286 if (se->conn.congestion_threshold > se->conn.max_background)
+
2287 se->conn.congestion_threshold = se->conn.max_background;
+
2288 if (!se->conn.congestion_threshold) {
+
2289 se->conn.congestion_threshold =
+
2290 se->conn.max_background * 3 / 4;
+
2291 }
+
2292
+
2293 outarg.max_background = se->conn.max_background;
+
2294 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2295 }
+
2296 if (se->conn.proto_minor >= 23)
+
2297 outarg.time_gran = se->conn.time_gran;
+
2298
+
2299 if (se->debug) {
+
2300 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2301 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2302 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2303 outarg.max_readahead);
+
2304 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2305 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2306 outarg.max_background);
+
2307 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2308 outarg.congestion_threshold);
+
2309 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2310 outarg.time_gran);
+
2311 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2312 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2313 outarg.max_stack_depth);
+
2314 }
+
2315 if (arg->minor < 5)
+
2316 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2317 else if (arg->minor < 23)
+
2318 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2319
+
2320 send_reply_ok(req, &outarg, outargsize);
+
2321}
+
2322
+
2323static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2324{
+
2325 struct fuse_session *se = req->se;
+
2326
+
2327 (void) nodeid;
+
2328 (void) inarg;
+
2329
+
2330 se->got_destroy = 1;
+
2331 se->got_init = 0;
+
2332 if (se->op.destroy)
+
2333 se->op.destroy(se->userdata);
+
2334
+
2335 send_reply_ok(req, NULL, 0);
+
2336}
+
2337
+
2338static void list_del_nreq(struct fuse_notify_req *nreq)
+
2339{
+
2340 struct fuse_notify_req *prev = nreq->prev;
+
2341 struct fuse_notify_req *next = nreq->next;
+
2342 prev->next = next;
+
2343 next->prev = prev;
+
2344}
+
2345
+
2346static void list_add_nreq(struct fuse_notify_req *nreq,
+
2347 struct fuse_notify_req *next)
+
2348{
+
2349 struct fuse_notify_req *prev = next->prev;
+
2350 nreq->next = next;
+
2351 nreq->prev = prev;
+
2352 prev->next = nreq;
+
2353 next->prev = nreq;
+
2354}
+
2355
+
2356static void list_init_nreq(struct fuse_notify_req *nreq)
+
2357{
+
2358 nreq->next = nreq;
+
2359 nreq->prev = nreq;
+
2360}
+
2361
+
2362static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
2363 const void *inarg, const struct fuse_buf *buf)
+
2364{
+
2365 struct fuse_session *se = req->se;
+
2366 struct fuse_notify_req *nreq;
+
2367 struct fuse_notify_req *head;
+
2368
+
2369 pthread_mutex_lock(&se->lock);
+
2370 head = &se->notify_list;
+
2371 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
2372 if (nreq->unique == req->unique) {
+
2373 list_del_nreq(nreq);
+
2374 break;
+
2375 }
+
2376 }
+
2377 pthread_mutex_unlock(&se->lock);
+
2378
+
2379 if (nreq != head)
+
2380 nreq->reply(nreq, req, nodeid, inarg, buf);
+
2381}
+
2382
+
2383static int send_notify_iov(struct fuse_session *se, int notify_code,
+
2384 struct iovec *iov, int count)
+
2385{
+
2386 struct fuse_out_header out;
+
2387
+
2388 if (!se->got_init)
+
2389 return -ENOTCONN;
+
2390
+
2391 out.unique = 0;
+
2392 out.error = notify_code;
+
2393 iov[0].iov_base = &out;
+
2394 iov[0].iov_len = sizeof(struct fuse_out_header);
+
2395
+
2396 return fuse_send_msg(se, NULL, iov, count);
+
2397}
+
2398
+
+
2399int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
2400{
+
2401 if (ph != NULL) {
+
2402 struct fuse_notify_poll_wakeup_out outarg;
+
2403 struct iovec iov[2];
+
2404
+
2405 outarg.kh = ph->kh;
+
2406
+
2407 iov[1].iov_base = &outarg;
+
2408 iov[1].iov_len = sizeof(outarg);
+
2409
+
2410 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
2411 } else {
+
2412 return 0;
+
2413 }
+
2414}
+
+
2415
+
+
2416int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
2417 off_t off, off_t len)
+
2418{
+
2419 struct fuse_notify_inval_inode_out outarg;
+
2420 struct iovec iov[2];
+
2421
+
2422 if (!se)
+
2423 return -EINVAL;
+
2424
+
2425 if (se->conn.proto_minor < 12)
+
2426 return -ENOSYS;
+
2427
+
2428 outarg.ino = ino;
+
2429 outarg.off = off;
+
2430 outarg.len = len;
+
2431
+
2432 iov[1].iov_base = &outarg;
+
2433 iov[1].iov_len = sizeof(outarg);
+
2434
+
2435 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
2436}
+
+
2437
+
2457static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
2458 const char *name, size_t namelen,
+
2459 enum fuse_notify_entry_flags flags)
+
2460{
+
2461 struct fuse_notify_inval_entry_out outarg;
+
2462 struct iovec iov[3];
+
2463
+
2464 if (!se)
+
2465 return -EINVAL;
+
2466
+
2467 if (se->conn.proto_minor < 12)
+
2468 return -ENOSYS;
+
2469
+
2470 outarg.parent = parent;
+
2471 outarg.namelen = namelen;
+
2472 outarg.flags = 0;
+
2473 if (flags & FUSE_LL_EXPIRE_ONLY)
+
2474 outarg.flags |= FUSE_EXPIRE_ONLY;
+
2475
+
2476 iov[1].iov_base = &outarg;
+
2477 iov[1].iov_len = sizeof(outarg);
+
2478 iov[2].iov_base = (void *)name;
+
2479 iov[2].iov_len = namelen + 1;
+
2480
+
2481 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
2482}
+
2483
+
+
2484int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
2485 const char *name, size_t namelen)
+
2486{
+
2487 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
2488}
+
+
2489
+
+
2490int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
2491 const char *name, size_t namelen)
+
2492{
+
2493 if (!se)
+
2494 return -EINVAL;
+
2495
+
2496 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
2497 return -ENOSYS;
+
2498
+
2499 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
2500}
+
+
2501
+
2502
+
+
2503int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
2504 fuse_ino_t parent, fuse_ino_t child,
+
2505 const char *name, size_t namelen)
+
2506{
+
2507 struct fuse_notify_delete_out outarg;
+
2508 struct iovec iov[3];
+
2509
+
2510 if (!se)
+
2511 return -EINVAL;
+
2512
+
2513 if (se->conn.proto_minor < 18)
+
2514 return -ENOSYS;
+
2515
+
2516 outarg.parent = parent;
+
2517 outarg.child = child;
+
2518 outarg.namelen = namelen;
+
2519 outarg.padding = 0;
+
2520
+
2521 iov[1].iov_base = &outarg;
+
2522 iov[1].iov_len = sizeof(outarg);
+
2523 iov[2].iov_base = (void *)name;
+
2524 iov[2].iov_len = namelen + 1;
+
2525
+
2526 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
2527}
+
+
2528
+
+
2529int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
2530 off_t offset, struct fuse_bufvec *bufv,
+
2531 enum fuse_buf_copy_flags flags)
+
2532{
+
2533 struct fuse_out_header out;
+
2534 struct fuse_notify_store_out outarg;
+
2535 struct iovec iov[3];
+
2536 size_t size = fuse_buf_size(bufv);
+
2537 int res;
+
2538
+
2539 if (!se)
+
2540 return -EINVAL;
+
2541
+
2542 if (se->conn.proto_minor < 15)
+
2543 return -ENOSYS;
+
2544
+
2545 out.unique = 0;
+
2546 out.error = FUSE_NOTIFY_STORE;
+
2547
+
2548 outarg.nodeid = ino;
+
2549 outarg.offset = offset;
+
2550 outarg.size = size;
+
2551 outarg.padding = 0;
+
2552
+
2553 iov[0].iov_base = &out;
+
2554 iov[0].iov_len = sizeof(out);
+
2555 iov[1].iov_base = &outarg;
+
2556 iov[1].iov_len = sizeof(outarg);
+
2557
+
2558 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags);
+
2559 if (res > 0)
+
2560 res = -res;
+
2561
+
2562 return res;
+
2563}
+
+
2564
+
2565struct fuse_retrieve_req {
+
2566 struct fuse_notify_req nreq;
+
2567 void *cookie;
+
2568};
+
2569
+
2570static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
2571 fuse_req_t req, fuse_ino_t ino,
+
2572 const void *inarg,
+
2573 const struct fuse_buf *ibuf)
+
2574{
+
2575 struct fuse_session *se = req->se;
+
2576 struct fuse_retrieve_req *rreq =
+
2577 container_of(nreq, struct fuse_retrieve_req, nreq);
+
2578 const struct fuse_notify_retrieve_in *arg = inarg;
+
2579 struct fuse_bufvec bufv = {
+
2580 .buf[0] = *ibuf,
+
2581 .count = 1,
+
2582 };
+
2583
+
2584 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
2585 bufv.buf[0].mem = PARAM(arg);
+
2586
+
2587 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
2588 sizeof(struct fuse_notify_retrieve_in);
+
2589
+
2590 if (bufv.buf[0].size < arg->size) {
+
2591 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
2592 fuse_reply_none(req);
+
2593 goto out;
+
2594 }
+
2595 bufv.buf[0].size = arg->size;
+
2596
+
2597 if (se->op.retrieve_reply) {
+
2598 se->op.retrieve_reply(req, rreq->cookie, ino,
+
2599 arg->offset, &bufv);
+
2600 } else {
+
2601 fuse_reply_none(req);
+
2602 }
+
2603out:
+
2604 free(rreq);
+
2605 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
2606 fuse_ll_clear_pipe(se);
+
2607}
+
2608
+
+
2609int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
2610 size_t size, off_t offset, void *cookie)
+
2611{
+
2612 struct fuse_notify_retrieve_out outarg;
+
2613 struct iovec iov[2];
+
2614 struct fuse_retrieve_req *rreq;
+
2615 int err;
+
2616
+
2617 if (!se)
+
2618 return -EINVAL;
+
2619
+
2620 if (se->conn.proto_minor < 15)
+
2621 return -ENOSYS;
+
2622
+
2623 rreq = malloc(sizeof(*rreq));
+
2624 if (rreq == NULL)
+
2625 return -ENOMEM;
+
2626
+
2627 pthread_mutex_lock(&se->lock);
+
2628 rreq->cookie = cookie;
+
2629 rreq->nreq.unique = se->notify_ctr++;
+
2630 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
2631 list_add_nreq(&rreq->nreq, &se->notify_list);
+
2632 pthread_mutex_unlock(&se->lock);
+
2633
+
2634 outarg.notify_unique = rreq->nreq.unique;
+
2635 outarg.nodeid = ino;
+
2636 outarg.offset = offset;
+
2637 outarg.size = size;
+
2638 outarg.padding = 0;
+
2639
+
2640 iov[1].iov_base = &outarg;
+
2641 iov[1].iov_len = sizeof(outarg);
+
2642
+
2643 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
2644 if (err) {
+
2645 pthread_mutex_lock(&se->lock);
+
2646 list_del_nreq(&rreq->nreq);
+
2647 pthread_mutex_unlock(&se->lock);
+
2648 free(rreq);
+
2649 }
+
2650
+
2651 return err;
+
2652}
+
+
2653
+
+ +
2655{
+
2656 return req->se->userdata;
+
2657}
+
+
2658
+
+ +
2660{
+
2661 return &req->ctx;
+
2662}
+
+
2663
+
+ +
2665 void *data)
+
2666{
+
2667 pthread_mutex_lock(&req->lock);
+
2668 pthread_mutex_lock(&req->se->lock);
+
2669 req->u.ni.func = func;
+
2670 req->u.ni.data = data;
+
2671 pthread_mutex_unlock(&req->se->lock);
+
2672 if (req->interrupted && func)
+
2673 func(req, data);
+
2674 pthread_mutex_unlock(&req->lock);
+
2675}
+
+
2676
+
+ +
2678{
+
2679 int interrupted;
+
2680
+
2681 pthread_mutex_lock(&req->se->lock);
+
2682 interrupted = req->interrupted;
+
2683 pthread_mutex_unlock(&req->se->lock);
+
2684
+
2685 return interrupted;
+
2686}
+
+
2687
+
2688static struct {
+
2689 void (*func)(fuse_req_t, fuse_ino_t, const void *);
+
2690 const char *name;
+
2691} fuse_ll_ops[] = {
+
2692 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
2693 [FUSE_FORGET] = { do_forget, "FORGET" },
+
2694 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
2695 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
2696 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
2697 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
2698 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
2699 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
2700 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
2701 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
2702 [FUSE_RENAME] = { do_rename, "RENAME" },
+
2703 [FUSE_LINK] = { do_link, "LINK" },
+
2704 [FUSE_OPEN] = { do_open, "OPEN" },
+
2705 [FUSE_READ] = { do_read, "READ" },
+
2706 [FUSE_WRITE] = { do_write, "WRITE" },
+
2707 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
2708 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
2709 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
2710 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
2711 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
2712 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
2713 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
2714 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
2715 [FUSE_INIT] = { do_init, "INIT" },
+
2716 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
2717 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
2718 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
2719 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
2720 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
2721 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
2722 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
2723 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
2724 [FUSE_CREATE] = { do_create, "CREATE" },
+
2725 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
2726 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
2727 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
2728 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
2729 [FUSE_POLL] = { do_poll, "POLL" },
+
2730 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
2731 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
2732 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
2733 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
2734 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
2735 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
2736 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
2737 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
2738 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
2739};
+
2740
+
2741/*
+
2742 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
2743 * Without ABI compatibility we could use the size of the array.
+
2744 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
2745 */
+
2746#define FUSE_MAXOP (CUSE_INIT + 1)
+
2747
+
2748static const char *opname(enum fuse_opcode opcode)
+
2749{
+
2750 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
2751 return "???";
+
2752 else
+
2753 return fuse_ll_ops[opcode].name;
+
2754}
+
2755
+
2756static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
2757 struct fuse_bufvec *src)
+
2758{
+
2759 ssize_t res = fuse_buf_copy(dst, src, 0);
+
2760 if (res < 0) {
+
2761 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
2762 return res;
+
2763 }
+
2764 if ((size_t)res < fuse_buf_size(dst)) {
+
2765 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
2766 return -1;
+
2767 }
+
2768 return 0;
+
2769}
+
2770
+
+
2771void fuse_session_process_buf(struct fuse_session *se,
+
2772 const struct fuse_buf *buf)
+
2773{
+
2774 fuse_session_process_buf_internal(se, buf, NULL);
+
2775}
+
+
2776
+
2777/* libfuse internal handler */
+
2778void fuse_session_process_buf_internal(struct fuse_session *se,
+
2779 const struct fuse_buf *buf, struct fuse_chan *ch)
+
2780{
+
2781 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
2782 sizeof(struct fuse_write_in);
+
2783 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
2784 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
2785 struct fuse_in_header *in;
+
2786 const void *inarg;
+
2787 struct fuse_req *req;
+
2788 void *mbuf = NULL;
+
2789 int err;
+
2790 int res;
+
2791
+
2792 if (buf->flags & FUSE_BUF_IS_FD) {
+
2793 if (buf->size < tmpbuf.buf[0].size)
+
2794 tmpbuf.buf[0].size = buf->size;
+
2795
+
2796 mbuf = malloc(tmpbuf.buf[0].size);
+
2797 if (mbuf == NULL) {
+
2798 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
2799 goto clear_pipe;
+
2800 }
+
2801 tmpbuf.buf[0].mem = mbuf;
+
2802
+
2803 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2804 if (res < 0)
+
2805 goto clear_pipe;
+
2806
+
2807 in = mbuf;
+
2808 } else {
+
2809 in = buf->mem;
+
2810 }
+
2811
+
2812 if (se->debug) {
+
2813 fuse_log(FUSE_LOG_DEBUG,
+
2814 "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
2815 (unsigned long long) in->unique,
+
2816 opname((enum fuse_opcode) in->opcode), in->opcode,
+
2817 (unsigned long long) in->nodeid, buf->size, in->pid);
+
2818 }
+
2819
+
2820 req = fuse_ll_alloc_req(se);
+
2821 if (req == NULL) {
+
2822 struct fuse_out_header out = {
+
2823 .unique = in->unique,
+
2824 .error = -ENOMEM,
+
2825 };
+
2826 struct iovec iov = {
+
2827 .iov_base = &out,
+
2828 .iov_len = sizeof(struct fuse_out_header),
+
2829 };
+
2830
+
2831 fuse_send_msg(se, ch, &iov, 1);
+
2832 goto clear_pipe;
+
2833 }
+
2834
+
2835 req->unique = in->unique;
+
2836 req->ctx.uid = in->uid;
+
2837 req->ctx.gid = in->gid;
+
2838 req->ctx.pid = in->pid;
+
2839 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
2840
+
2841 err = EIO;
+
2842 if (!se->got_init) {
+
2843 enum fuse_opcode expected;
+
2844
+
2845 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
2846 if (in->opcode != expected)
+
2847 goto reply_err;
+
2848 } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+
2849 goto reply_err;
+
2850
+
2851 err = EACCES;
+
2852 /* Implement -o allow_root */
+
2853 if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
+
2854 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+
2855 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+
2856 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+
2857 in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+
2858 in->opcode != FUSE_NOTIFY_REPLY &&
+
2859 in->opcode != FUSE_READDIRPLUS)
+
2860 goto reply_err;
+
2861
+
2862 err = ENOSYS;
+
2863 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
2864 goto reply_err;
+
2865 /* Do not process interrupt request */
+
2866 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
2867 if (se->debug)
+
2868 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
2869 goto reply_err;
+
2870 }
+
2871 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
2872 struct fuse_req *intr;
+
2873 pthread_mutex_lock(&se->lock);
+
2874 intr = check_interrupt(se, req);
+
2875 list_add_req(req, &se->list);
+
2876 pthread_mutex_unlock(&se->lock);
+
2877 if (intr)
+
2878 fuse_reply_err(intr, EAGAIN);
+
2879 }
+
2880
+
2881 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
2882 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
2883 in->opcode != FUSE_NOTIFY_REPLY) {
+
2884 void *newmbuf;
+
2885
+
2886 err = ENOMEM;
+
2887 newmbuf = realloc(mbuf, buf->size);
+
2888 if (newmbuf == NULL)
+
2889 goto reply_err;
+
2890 mbuf = newmbuf;
+
2891
+
2892 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
2893 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
2894
+
2895 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2896 err = -res;
+
2897 if (res < 0)
+
2898 goto reply_err;
+
2899
+
2900 in = mbuf;
+
2901 }
+
2902
+
2903 inarg = (void *) &in[1];
+
2904 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
2905 do_write_buf(req, in->nodeid, inarg, buf);
+
2906 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
2907 do_notify_reply(req, in->nodeid, inarg, buf);
+
2908 else
+
2909 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
2910
+
2911out_free:
+
2912 free(mbuf);
+
2913 return;
+
2914
+
2915reply_err:
+
2916 fuse_reply_err(req, err);
+
2917clear_pipe:
+
2918 if (buf->flags & FUSE_BUF_IS_FD)
+
2919 fuse_ll_clear_pipe(se);
+
2920 goto out_free;
+
2921}
+
2922
+
2923#define LL_OPTION(n,o,v) \
+
2924 { n, offsetof(struct fuse_session, o), v }
+
2925
+
2926static const struct fuse_opt fuse_ll_opts[] = {
+
2927 LL_OPTION("debug", debug, 1),
+
2928 LL_OPTION("-d", debug, 1),
+
2929 LL_OPTION("--debug", debug, 1),
+
2930 LL_OPTION("allow_root", deny_others, 1),
+ +
2932};
+
2933
+
+ +
2935{
+
2936 printf("using FUSE kernel interface version %i.%i\n",
+
2937 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
2938 fuse_mount_version();
+
2939}
+
+
2940
+
+ +
2942{
+
2943 /* These are not all options, but the ones that are
+
2944 potentially of interest to an end-user */
+
2945 printf(
+
2946" -o allow_other allow access by all users\n"
+
2947" -o allow_root allow access by root\n"
+
2948" -o auto_unmount auto unmount on process termination\n");
+
2949}
+
+
2950
+
+
2951void fuse_session_destroy(struct fuse_session *se)
+
2952{
+
2953 struct fuse_ll_pipe *llp;
+
2954
+
2955 if (se->got_init && !se->got_destroy) {
+
2956 if (se->op.destroy)
+
2957 se->op.destroy(se->userdata);
+
2958 }
+
2959 llp = pthread_getspecific(se->pipe_key);
+
2960 if (llp != NULL)
+
2961 fuse_ll_pipe_free(llp);
+
2962 pthread_key_delete(se->pipe_key);
+
2963 pthread_mutex_destroy(&se->lock);
+
2964 free(se->cuse_data);
+
2965 if (se->fd != -1)
+
2966 close(se->fd);
+
2967 if (se->io != NULL)
+
2968 free(se->io);
+
2969 destroy_mount_opts(se->mo);
+
2970 free(se);
+
2971}
+
+
2972
+
2973
+
2974static void fuse_ll_pipe_destructor(void *data)
+
2975{
+
2976 struct fuse_ll_pipe *llp = data;
+
2977 fuse_ll_pipe_free(llp);
+
2978}
+
2979
+
2980void fuse_buf_free(struct fuse_buf *buf)
+
2981{
+
2982 if (buf->mem == NULL)
+
2983 return;
+
2984
+
2985 size_t write_header_sz =
+
2986 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
2987
+
2988 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
2989 free(ptr);
+
2990 buf->mem = NULL;
+
2991}
+
2992
+
2993/*
+
2994 * This is used to allocate buffers that hold fuse requests
+
2995 */
+
2996static void *buf_alloc(size_t size, bool internal)
+
2997{
+
2998 /*
+
2999 * For libfuse internal caller add in alignment. That cannot be done
+
3000 * for an external caller, as it is not guaranteed that the external
+
3001 * caller frees the raw pointer.
+
3002 */
+
3003 if (internal) {
+
3004 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3005 sizeof(struct fuse_write_in);
+
3006 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3007
+
3008 char *buf = aligned_alloc(pagesize, new_size);
+
3009 if (buf == NULL)
+
3010 return NULL;
+
3011
+
3012 buf += pagesize - write_header_sz;
+
3013
+
3014 return buf;
+
3015 } else {
+
3016 return malloc(size);
+
3017 }
+
3018}
+
3019
+
3020/*
+
3021 *@param internal true if called from libfuse internal code
+
3022 */
+
3023static int _fuse_session_receive_buf(struct fuse_session *se,
+
3024 struct fuse_buf *buf, struct fuse_chan *ch,
+
3025 bool internal)
+
3026{
+
3027 int err;
+
3028 ssize_t res;
+
3029 size_t bufsize;
+
3030#ifdef HAVE_SPLICE
+
3031 struct fuse_ll_pipe *llp;
+
3032 struct fuse_buf tmpbuf;
+
3033
+
3034pipe_retry:
+
3035 bufsize = se->bufsize;
+
3036
+
3037 if (se->conn.proto_minor < 14 ||
+
3038 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3039 goto fallback;
+
3040
+
3041 llp = fuse_ll_get_pipe(se);
+
3042 if (llp == NULL)
+
3043 goto fallback;
+
3044
+
3045 if (llp->size < bufsize) {
+
3046 if (llp->can_grow) {
+
3047 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3048 if (res == -1) {
+
3049 llp->can_grow = 0;
+
3050 res = grow_pipe_to_max(llp->pipe[0]);
+
3051 if (res > 0)
+
3052 llp->size = res;
+
3053 goto fallback;
+
3054 }
+
3055 llp->size = res;
+
3056 }
+
3057 if (llp->size < bufsize)
+
3058 goto fallback;
+
3059 }
+
3060
+
3061 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3062 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3063 llp->pipe[1], NULL, bufsize, 0,
+
3064 se->userdata);
+
3065 } else {
+
3066 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3067 bufsize, 0);
+
3068 }
+
3069 err = errno;
+
3070
+
3071 if (fuse_session_exited(se))
+
3072 return 0;
+
3073
+
3074 if (res == -1) {
+
3075 if (err == ENODEV) {
+
3076 /* Filesystem was unmounted, or connection was aborted
+
3077 via /sys/fs/fuse/connections */
+ +
3079 return 0;
+
3080 }
+
3081
+
3082 /* FUSE_INIT might have increased the required bufsize */
+
3083 if (err == EINVAL && bufsize < se->bufsize) {
+
3084 fuse_ll_clear_pipe(se);
+
3085 goto pipe_retry;
+
3086 }
+
3087
+
3088 if (err != EINTR && err != EAGAIN)
+
3089 perror("fuse: splice from device");
+
3090 return -err;
+
3091 }
+
3092
+
3093 if (res < sizeof(struct fuse_in_header)) {
+
3094 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3095 return -EIO;
+
3096 }
+
3097
+
3098 tmpbuf = (struct fuse_buf){
+
3099 .size = res,
+
3100 .flags = FUSE_BUF_IS_FD,
+
3101 .fd = llp->pipe[0],
+
3102 };
+
3103
+
3104 /*
+
3105 * Don't bother with zero copy for small requests.
+
3106 * fuse_loop_mt() needs to check for FORGET so this more than
+
3107 * just an optimization.
+
3108 */
+
3109 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3110 pagesize) {
+
3111 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3112 struct fuse_bufvec dst = { .count = 1 };
+
3113
+
3114 if (!buf->mem) {
+
3115 buf->mem = buf_alloc(bufsize, internal);
+
3116 if (!buf->mem) {
+
3117 fuse_log(
+
3118 FUSE_LOG_ERR,
+
3119 "fuse: failed to allocate read buffer\n");
+
3120 return -ENOMEM;
+
3121 }
+
3122 buf->mem_size = bufsize;
+
3123 }
+
3124 buf->size = bufsize;
+
3125 buf->flags = 0;
+
3126 dst.buf[0] = *buf;
+
3127
+
3128 res = fuse_buf_copy(&dst, &src, 0);
+
3129 if (res < 0) {
+
3130 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3131 strerror(-res));
+
3132 fuse_ll_clear_pipe(se);
+
3133 return res;
+
3134 }
+
3135 if (res < tmpbuf.size) {
+
3136 fuse_log(FUSE_LOG_ERR,
+
3137 "fuse: copy from pipe: short read\n");
+
3138 fuse_ll_clear_pipe(se);
+
3139 return -EIO;
+
3140 }
+
3141 assert(res == tmpbuf.size);
+
3142
+
3143 } else {
+
3144 /* Don't overwrite buf->mem, as that would cause a leak */
+
3145 buf->fd = tmpbuf.fd;
+
3146 buf->flags = tmpbuf.flags;
+
3147 }
+
3148 buf->size = tmpbuf.size;
+
3149
+
3150 return res;
+
3151
+
3152fallback:
+
3153#endif
+
3154 bufsize = internal ? buf->mem_size : se->bufsize;
+
3155 if (!buf->mem) {
+
3156 bufsize = se->bufsize; /* might have changed */
+
3157 buf->mem = buf_alloc(bufsize, internal);
+
3158 if (!buf->mem) {
+
3159 fuse_log(FUSE_LOG_ERR,
+
3160 "fuse: failed to allocate read buffer\n");
+
3161 return -ENOMEM;
+
3162 }
+
3163
+
3164 if (internal)
+
3165 buf->mem_size = bufsize;
+
3166 }
+
3167
+
3168restart:
+
3169 if (se->io != NULL) {
+
3170 /* se->io->read is never NULL if se->io is not NULL as
+
3171 specified by fuse_session_custom_io()*/
+
3172 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
3173 se->userdata);
+
3174 } else {
+
3175 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
3176 }
+
3177 err = errno;
+
3178
+
3179 if (fuse_session_exited(se))
+
3180 return 0;
+
3181 if (res == -1) {
+
3182 if (err == EINVAL && internal && se->bufsize > bufsize) {
+
3183 /* FUSE_INIT might have increased the required bufsize */
+
3184 bufsize = se->bufsize;
+
3185 void *newbuf = buf_alloc(bufsize, internal);
+
3186 if (!newbuf) {
+
3187 fuse_log(
+
3188 FUSE_LOG_ERR,
+
3189 "fuse: failed to (re)allocate read buffer\n");
+
3190 return -ENOMEM;
+
3191 }
+
3192 fuse_buf_free(buf);
+
3193 buf->mem = newbuf;
+
3194 buf->mem_size = bufsize;
+
3195 goto restart;
+
3196 }
+
3197
+
3198 /* ENOENT means the operation was interrupted, it's safe
+
3199 to restart */
+
3200 if (err == ENOENT)
+
3201 goto restart;
+
3202
+
3203 if (err == ENODEV) {
+
3204 /* Filesystem was unmounted, or connection was aborted
+
3205 via /sys/fs/fuse/connections */
+ +
3207 return 0;
+
3208 }
+
3209 /* Errors occurring during normal operation: EINTR (read
+
3210 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
3211 umounted) */
+
3212 if (err != EINTR && err != EAGAIN)
+
3213 perror("fuse: reading device");
+
3214 return -err;
+
3215 }
+
3216 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
3217 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
3218 return -EIO;
+
3219 }
+
3220
+
3221 buf->size = res;
+
3222
+
3223 return res;
+
3224}
+
3225
+
+
3226int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
3227{
+
3228 return _fuse_session_receive_buf(se, buf, NULL, false);
+
3229}
+
+
3230
+
3231/* libfuse internal handler */
+
3232int fuse_session_receive_buf_internal(struct fuse_session *se,
+
3233 struct fuse_buf *buf,
+
3234 struct fuse_chan *ch)
+
3235{
+
3236 /*
+
3237 * if run internally thread buffers are from libfuse - we can
+
3238 * reallocate them
+
3239 */
+
3240 if (unlikely(!se->got_init) && !se->buf_reallocable)
+
3241 se->buf_reallocable = true;
+
3242
+
3243 return _fuse_session_receive_buf(se, buf, ch, true);
+
3244}
+
3245
+
3246struct fuse_session *
+
3247fuse_session_new_versioned(struct fuse_args *args,
+
3248 const struct fuse_lowlevel_ops *op, size_t op_size,
+
3249 struct libfuse_version *version, void *userdata)
+
3250{
+
3251 int err;
+
3252 struct fuse_session *se;
+
3253 struct mount_opts *mo;
+
3254
+
3255 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
3256 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3257 op_size = sizeof(struct fuse_lowlevel_ops);
+
3258 }
+
3259
+
3260 if (args->argc == 0) {
+
3261 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
3262 return NULL;
+
3263 }
+
3264
+
3265 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
3266 if (se == NULL) {
+
3267 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
3268 goto out1;
+
3269 }
+
3270 se->fd = -1;
+
3271 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
3272 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
3273 se->conn.max_readahead = UINT_MAX;
+
3274
+
3275 /* Parse options */
+
3276 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
3277 goto out2;
+
3278 if(se->deny_others) {
+
3279 /* Allowing access only by root is done by instructing
+
3280 * kernel to allow access by everyone, and then restricting
+
3281 * access to root and mountpoint owner in libfuse.
+
3282 */
+
3283 // We may be adding the option a second time, but
+
3284 // that doesn't hurt.
+
3285 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
3286 goto out2;
+
3287 }
+
3288 mo = parse_mount_opts(args);
+
3289 if (mo == NULL)
+
3290 goto out3;
+
3291
+
3292 if(args->argc == 1 &&
+
3293 args->argv[0][0] == '-') {
+
3294 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
3295 "will be ignored\n");
+
3296 } else if (args->argc != 1) {
+
3297 int i;
+
3298 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
3299 for(i = 1; i < args->argc-1; i++)
+
3300 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
3301 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
3302 goto out4;
+
3303 }
+
3304
+
3305 if (se->debug)
+
3306 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
3307
+
3308 list_init_req(&se->list);
+
3309 list_init_req(&se->interrupts);
+
3310 list_init_nreq(&se->notify_list);
+
3311 se->notify_ctr = 1;
+
3312 pthread_mutex_init(&se->lock, NULL);
+
3313
+
3314 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
3315 if (err) {
+
3316 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
3317 strerror(err));
+
3318 goto out5;
+
3319 }
+
3320
+
3321 memcpy(&se->op, op, op_size);
+
3322 se->owner = getuid();
+
3323 se->userdata = userdata;
+
3324
+
3325 se->mo = mo;
+
3326
+
3327 /* Fuse server application should pass the version it was compiled
+
3328 * against and pass it. If a libfuse version accidentally introduces an
+
3329 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
3330 * by checking the version numbers.
+
3331 */
+
3332 se->version = *version;
+
3333
+
3334 return se;
+
3335
+
3336out5:
+
3337 pthread_mutex_destroy(&se->lock);
+
3338out4:
+
3339 fuse_opt_free_args(args);
+
3340out3:
+
3341 if (mo != NULL)
+
3342 destroy_mount_opts(mo);
+
3343out2:
+
3344 free(se);
+
3345out1:
+
3346 return NULL;
+
3347}
+
3348
+
3349struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3350 const struct fuse_lowlevel_ops *op,
+
3351 size_t op_size, void *userdata);
+
3352struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3353 const struct fuse_lowlevel_ops *op,
+
3354 size_t op_size,
+
3355 void *userdata)
+
3356{
+
3357 /* unknown version */
+
3358 struct libfuse_version version = { 0 };
+
3359
+
3360 return fuse_session_new_versioned(args, op, op_size, &version,
+
3361 userdata);
+
3362}
+
3363
+
3364FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
3365int fuse_session_custom_io_317(struct fuse_session *se,
+
3366 const struct fuse_custom_io *io, size_t op_size, int fd)
+
3367{
+
3368 if (sizeof(struct fuse_custom_io) < op_size) {
+
3369 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3370 op_size = sizeof(struct fuse_custom_io);
+
3371 }
+
3372
+
3373 if (fd < 0) {
+
3374 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
3375 "fuse_session_custom_io()\n", fd);
+
3376 return -EBADF;
+
3377 }
+
3378 if (io == NULL) {
+
3379 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
3380 "fuse_session_custom_io()\n");
+
3381 return -EINVAL;
+
3382 } else if (io->read == NULL || io->writev == NULL) {
+
3383 /* If the user provides their own file descriptor, we can't
+
3384 guarantee that the default behavior of the io operations made
+
3385 in libfuse will function properly. Therefore, we enforce the
+
3386 user to implement these io operations when using custom io. */
+
3387 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
3388 "implement both io->read() and io->writev\n");
+
3389 return -EINVAL;
+
3390 }
+
3391
+
3392 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
3393 if (se->io == NULL) {
+
3394 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
3395 "Error: %s\n", strerror(errno));
+
3396 return -errno;
+
3397 }
+
3398
+
3399 se->fd = fd;
+
3400 memcpy(se->io, io, op_size);
+
3401 return 0;
+
3402}
+
3403
+
3404int fuse_session_custom_io_30(struct fuse_session *se,
+
3405 const struct fuse_custom_io *io, int fd);
+
3406FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
3407int fuse_session_custom_io_30(struct fuse_session *se,
+
3408 const struct fuse_custom_io *io, int fd)
+
3409{
+
3410 return fuse_session_custom_io_317(se, io,
+
3411 offsetof(struct fuse_custom_io, clone_fd), fd);
+
3412}
+
3413
+
+
3414int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
3415{
+
3416 int fd;
+
3417
+
3418 if (mountpoint == NULL) {
+
3419 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
3420 return -1;
+
3421 }
+
3422
+
3423 /*
+
3424 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
3425 * would ensue.
+
3426 */
+
3427 do {
+
3428 fd = open("/dev/null", O_RDWR);
+
3429 if (fd > 2)
+
3430 close(fd);
+
3431 } while (fd >= 0 && fd <= 2);
+
3432
+
3433 /*
+
3434 * To allow FUSE daemons to run without privileges, the caller may open
+
3435 * /dev/fuse before launching the file system and pass on the file
+
3436 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
3437 * parent process takes care of performing the mount in this case.
+
3438 */
+
3439 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
3440 if (fd != -1) {
+
3441 if (fcntl(fd, F_GETFD) == -1) {
+
3442 fuse_log(FUSE_LOG_ERR,
+
3443 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
3444 fd);
+
3445 return -1;
+
3446 }
+
3447 se->fd = fd;
+
3448 return 0;
+
3449 }
+
3450
+
3451 /* Open channel */
+
3452 fd = fuse_kern_mount(mountpoint, se->mo);
+
3453 if (fd == -1)
+
3454 return -1;
+
3455 se->fd = fd;
+
3456
+
3457 /* Save mountpoint */
+
3458 se->mountpoint = strdup(mountpoint);
+
3459 if (se->mountpoint == NULL)
+
3460 goto error_out;
+
3461
+
3462 return 0;
+
3463
+
3464error_out:
+
3465 fuse_kern_unmount(mountpoint, fd);
+
3466 return -1;
+
3467}
+
+
3468
+
+
3469int fuse_session_fd(struct fuse_session *se)
+
3470{
+
3471 return se->fd;
+
3472}
+
+
3473
+
+
3474void fuse_session_unmount(struct fuse_session *se)
+
3475{
+
3476 if (se->mountpoint != NULL) {
+
3477 fuse_kern_unmount(se->mountpoint, se->fd);
+
3478 se->fd = -1;
+
3479 free(se->mountpoint);
+
3480 se->mountpoint = NULL;
+
3481 }
+
3482}
+
+
3483
+
3484#ifdef linux
+
3485int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3486{
+
3487 char *buf;
+
3488 size_t bufsize = 1024;
+
3489 char path[128];
+
3490 int ret;
+
3491 int fd;
+
3492 unsigned long pid = req->ctx.pid;
+
3493 char *s;
+
3494
+
3495 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
3496
+
3497retry:
+
3498 buf = malloc(bufsize);
+
3499 if (buf == NULL)
+
3500 return -ENOMEM;
+
3501
+
3502 ret = -EIO;
+
3503 fd = open(path, O_RDONLY);
+
3504 if (fd == -1)
+
3505 goto out_free;
+
3506
+
3507 ret = read(fd, buf, bufsize);
+
3508 close(fd);
+
3509 if (ret < 0) {
+
3510 ret = -EIO;
+
3511 goto out_free;
+
3512 }
+
3513
+
3514 if ((size_t)ret == bufsize) {
+
3515 free(buf);
+
3516 bufsize *= 4;
+
3517 goto retry;
+
3518 }
+
3519
+
3520 buf[ret] = '\0';
+
3521 ret = -EIO;
+
3522 s = strstr(buf, "\nGroups:");
+
3523 if (s == NULL)
+
3524 goto out_free;
+
3525
+
3526 s += 8;
+
3527 ret = 0;
+
3528 while (1) {
+
3529 char *end;
+
3530 unsigned long val = strtoul(s, &end, 0);
+
3531 if (end == s)
+
3532 break;
+
3533
+
3534 s = end;
+
3535 if (ret < size)
+
3536 list[ret] = val;
+
3537 ret++;
+
3538 }
+
3539
+
3540out_free:
+
3541 free(buf);
+
3542 return ret;
+
3543}
+
3544#else /* linux */
+
3545/*
+
3546 * This is currently not implemented on other than Linux...
+
3547 */
+
+
3548int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3549{
+
3550 (void) req; (void) size; (void) list;
+
3551 return -ENOSYS;
+
3552}
+
+
3553#endif
+
3554
+
3555/* Prevent spurious data race warning - we don't care
+
3556 * about races for this flag */
+
3557__attribute__((no_sanitize_thread))
+
3558void fuse_session_exit(struct fuse_session *se)
+
3559{
+
3560 se->exited = 1;
+
3561}
+
3562
+
3563__attribute__((no_sanitize_thread))
+
3564void fuse_session_reset(struct fuse_session *se)
+
3565{
+
3566 se->exited = 0;
+
3567 se->error = 0;
+
3568}
+
3569
+
3570__attribute__((no_sanitize_thread))
+
3571int fuse_session_exited(struct fuse_session *se)
+
3572{
+
3573 return se->exited;
+
3574}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+
void * mem
+ +
size_t size
+ + + +
struct fuse_buf buf[1]
+ + +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:66
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:95
+
uint32_t nonseekable
Definition fuse_common.h:84
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t noflush
Definition fuse_common.h:99
+
uint32_t flush
Definition fuse_common.h:80
+
uint32_t direct_io
Definition fuse_common.h:69
+
uint32_t keep_cache
Definition fuse_common.h:75
+ + + + +
+ + + + diff --git a/doc/html/fuse__lowlevel_8h.html b/doc/html/fuse__lowlevel_8h.html new file mode 100644 index 0000000..c007e3e --- /dev/null +++ b/doc/html/fuse__lowlevel_8h.html @@ -0,0 +1,2363 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1948 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 148 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 290 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 380 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 2941 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2503 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2490 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2484 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2416 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2399 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2609 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2529 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2934 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 484 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 464 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 977 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 528 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 448 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 916 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 432 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 335 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1075 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1096 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1005 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 269 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 960 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1130 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 340 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 509 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1120 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 479 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 938 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 518 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 950 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2659 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3548 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2664 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2677 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2654 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 2951 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3469 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3414 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2771 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3226 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3474 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse__lowlevel_8h_source.html b/doc/html/fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..cd879fe --- /dev/null +++ b/doc/html/fuse__lowlevel_8h_source.html @@ -0,0 +1,681 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
57struct fuse_session;
+
58
+
+ + +
69
+
80 uint64_t generation;
+
81
+
89 struct stat attr;
+
90
+ +
96
+ +
102};
+
+
103
+
+
112struct fuse_ctx {
+
114 uid_t uid;
+
115
+
117 gid_t gid;
+
118
+
120 pid_t pid;
+
121
+
123 mode_t umask;
+
124};
+
+
125
+
126struct fuse_forget_data {
+
127 fuse_ino_t ino;
+
128 uint64_t nlookup;
+
129};
+
130
+
131struct fuse_custom_io {
+
132 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
133 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
134 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
135 off_t *offout, size_t len,
+
136 unsigned int flags, void *userdata);
+
137 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 int (*clone_fd)(int master_fd);
+
141};
+
142
+
+ +
149 FUSE_LL_INVALIDATE = 0,
+
150 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
151};
+
+
152
+
153/* 'to_set' flags in setattr */
+
154#define FUSE_SET_ATTR_MODE (1 << 0)
+
155#define FUSE_SET_ATTR_UID (1 << 1)
+
156#define FUSE_SET_ATTR_GID (1 << 2)
+
157#define FUSE_SET_ATTR_SIZE (1 << 3)
+
158#define FUSE_SET_ATTR_ATIME (1 << 4)
+
159#define FUSE_SET_ATTR_MTIME (1 << 5)
+
160#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
161#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
162#define FUSE_SET_ATTR_FORCE (1 << 9)
+
163#define FUSE_SET_ATTR_CTIME (1 << 10)
+
164#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
165#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
166#define FUSE_SET_ATTR_FILE (1 << 13)
+
167#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
168#define FUSE_SET_ATTR_OPEN (1 << 15)
+
169#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
170#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
171
+
172/* ----------------------------------------------------------- *
+
173 * Request methods and replies *
+
174 * ----------------------------------------------------------- */
+
175
+
+ +
223 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
224
+
236 void (*destroy) (void *userdata);
+
237
+
249 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
250
+
287 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
288
+
308 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
309 struct fuse_file_info *fi);
+
310
+
345 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
346 int to_set, struct fuse_file_info *fi);
+
347
+
358 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
359
+
376 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
377 mode_t mode, dev_t rdev);
+
378
+
391 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
392 mode_t mode);
+
393
+
409 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
410
+
426 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
427
+
440 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
441 const char *name);
+
442
+
472 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
473 fuse_ino_t newparent, const char *newname,
+
474 unsigned int flags);
+
475
+
488 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
489 const char *newname);
+
490
+
554 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
555 struct fuse_file_info *fi);
+
556
+
582 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
583 struct fuse_file_info *fi);
+
584
+
611 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
612 size_t size, off_t off, struct fuse_file_info *fi);
+
613
+
652 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
653 struct fuse_file_info *fi);
+
654
+
680 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
681 struct fuse_file_info *fi);
+
682
+
702 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
703 struct fuse_file_info *fi);
+
704
+
734 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
735 struct fuse_file_info *fi);
+
736
+
780 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
781 struct fuse_file_info *fi);
+
782
+ +
800 struct fuse_file_info *fi);
+
801
+
824 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
825 struct fuse_file_info *fi);
+
826
+
837 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
838
+
850 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
851 const char *value, size_t size, int flags);
+
852
+
881 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
882 size_t size);
+
883
+
912 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
913
+
929 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
930
+
951 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
952
+
980 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
981 mode_t mode, struct fuse_file_info *fi);
+
982
+
995 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
996 struct fuse_file_info *fi, struct flock *lock);
+
997
+
1020 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1021 struct fuse_file_info *fi,
+
1022 struct flock *lock, int sleep);
+
1023
+
1044 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1045 uint64_t idx);
+
1046
+
1047#if FUSE_USE_VERSION < 35
+
1048 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1049 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1050 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1051#else
+
1080 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1081 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1082 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1083#endif
+
1084
+
1117 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1118 struct fuse_pollhandle *ph);
+
1119
+ +
1148 struct fuse_bufvec *bufv, off_t off,
+
1149 struct fuse_file_info *fi);
+
1150
+
1163 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1164 off_t offset, struct fuse_bufvec *bufv);
+
1165
+
1177 void (*forget_multi) (fuse_req_t req, size_t count,
+
1178 struct fuse_forget_data *forgets);
+
1179
+
1195 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1196 struct fuse_file_info *fi, int op);
+
1197
+
1218 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1219 off_t offset, off_t length, struct fuse_file_info *fi);
+
1220
+
1246 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1247 struct fuse_file_info *fi);
+
1248
+ +
1280 off_t off_in, struct fuse_file_info *fi_in,
+
1281 fuse_ino_t ino_out, off_t off_out,
+
1282 struct fuse_file_info *fi_out, size_t len,
+
1283 int flags);
+
1284
+
1303 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1304 struct fuse_file_info *fi);
+
1305
+
1306
+
1325 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1326 mode_t mode, struct fuse_file_info *fi);
+
1327
+
1328};
+
+
1329
+
1351int fuse_reply_err(fuse_req_t req, int err);
+
1352
+
1363void fuse_reply_none(fuse_req_t req);
+
1364
+
1378int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1379
+
1398int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1399 const struct fuse_file_info *fi);
+
1400
+
1412int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1413 double attr_timeout);
+
1414
+
1425int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1426
+
1439int fuse_passthrough_open(fuse_req_t req, int fd);
+
1440int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1441
+
1456int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1457
+
1468int fuse_reply_write(fuse_req_t req, size_t count);
+
1469
+
1481int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1482
+
1526int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1528
+
1540int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1541
+
1552int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1553
+
1564int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1565
+
1576int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1577
+
1588int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1589
+
1590/* ----------------------------------------------------------- *
+
1591 * Filling a buffer in readdir *
+
1592 * ----------------------------------------------------------- */
+
1593
+
1621size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1622 const char *name, const struct stat *stbuf,
+
1623 off_t off);
+
1624
+
1638size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1639 const char *name,
+
1640 const struct fuse_entry_param *e, off_t off);
+
1641
+ +
1658 const struct iovec *in_iov, size_t in_count,
+
1659 const struct iovec *out_iov, size_t out_count);
+
1660
+
1672int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1673
+
1685int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1686 int count);
+
1687
+
1694int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1695
+
1706int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1707
+
1708/* ----------------------------------------------------------- *
+
1709 * Notification *
+
1710 * ----------------------------------------------------------- */
+
1711
+
1719int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1720
+
1744int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1745 off_t off, off_t len);
+
1746
+
1771int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1772 const char *name, size_t namelen);
+
1773
+
1802int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1803 const char *name, size_t namelen);
+
1804
+
1833int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1834 fuse_ino_t parent, fuse_ino_t child,
+
1835 const char *name, size_t namelen);
+
1836
+
1862int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1863 off_t offset, struct fuse_bufvec *bufv,
+ +
1894int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1895 size_t size, off_t offset, void *cookie);
+
1896
+
1897
+
1898/* ----------------------------------------------------------- *
+
1899 * Utility functions *
+
1900 * ----------------------------------------------------------- */
+
1901
+
1908void *fuse_req_userdata(fuse_req_t req);
+
1909
+
1919const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1920
+
1940int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1941
+
1948typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1949
+ +
1962 void *data);
+
1963
+ +
1971
+
1972
+
1973/* ----------------------------------------------------------- *
+
1974 * Inquiry functions *
+
1975 * ----------------------------------------------------------- */
+
1976
+
1980void fuse_lowlevel_version(void);
+
1981
+
1987void fuse_lowlevel_help(void);
+
1988
+
1992void fuse_cmdline_help(void);
+
1993
+
1994/* ----------------------------------------------------------- *
+
1995 * Filesystem setup & teardown *
+
1996 * ----------------------------------------------------------- */
+
1997
+
+ +
2004 int singlethread;
+
2005 int foreground;
+
2006 int debug;
+
2007 int nodefault_subtype;
+
2008 char *mountpoint;
+
2009 int show_version;
+
2010 int show_help;
+
2011 int clone_fd;
+
2012 unsigned int max_idle_threads; /* discouraged, due to thread
+
2013 * destruct overhead */
+
2014
+
2015 /* Added in libfuse-3.12 */
+
2016 unsigned int max_threads;
+
2017};
+
+
2018
+
2037#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2038int fuse_parse_cmdline(struct fuse_args *args,
+
2039 struct fuse_cmdline_opts *opts);
+
2040#else
+
2041#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2042int fuse_parse_cmdline_30(struct fuse_args *args,
+
2043 struct fuse_cmdline_opts *opts);
+
2044#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2045#else
+
2046int fuse_parse_cmdline_312(struct fuse_args *args,
+
2047 struct fuse_cmdline_opts *opts);
+
2048#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2049#endif
+
2050#endif
+
2051
+
2052/* Do not call this directly, use fuse_session_new() instead */
+
2053struct fuse_session *
+
2054fuse_session_new_versioned(struct fuse_args *args,
+
2055 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2056 struct libfuse_version *version, void *userdata);
+
2057
+
2086static inline struct fuse_session *
+
2087fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2088 size_t op_size, void *userdata)
+
2089{
+
2090 struct libfuse_version version = {
+
2091 .major = FUSE_MAJOR_VERSION,
+
2092 .minor = FUSE_MINOR_VERSION,
+
2093 .hotfix = FUSE_HOTFIX_VERSION,
+
2094 .padding = 0
+
2095 };
+
2096
+
2097 return fuse_session_new_versioned(args, op, op_size, &version,
+
2098 userdata);
+
2099}
+
2100#define fuse_session_new(args, op, op_size, userdata) \
+
2101 fuse_session_new_fn(args, op, op_size, userdata)
+
2102
+
2103/*
+
2104 * This should mostly not be called directly, but instead the
+
2105 * fuse_session_custom_io() should be used.
+
2106 */
+
2107int fuse_session_custom_io_317(struct fuse_session *se,
+
2108 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2109
+
2137#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2138static inline int fuse_session_custom_io(struct fuse_session *se,
+
2139 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2140{
+
2141 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2142}
+
2143#else
+
2144static inline int fuse_session_custom_io(struct fuse_session *se,
+
2145 const struct fuse_custom_io *io, int fd)
+
2146{
+
2147 return fuse_session_custom_io_317(se, io,
+
2148 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2149}
+
2150#endif
+
2151
+
2160int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2161
+
2184int fuse_session_loop(struct fuse_session *se);
+
2185
+
2186#if FUSE_USE_VERSION < 32
+
2187 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2188 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2189#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2190 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2191 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2192#else
+
2193 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2205 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2206 #else
+
2207 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2208 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2209 #endif
+
2210#endif
+
2211
+
2224void fuse_session_exit(struct fuse_session *se);
+
2225
+
2231void fuse_session_reset(struct fuse_session *se);
+
2232
+
2239int fuse_session_exited(struct fuse_session *se);
+
2240
+
2265void fuse_session_unmount(struct fuse_session *se);
+
2266
+
2272void fuse_session_destroy(struct fuse_session *se);
+
2273
+
2274/* ----------------------------------------------------------- *
+
2275 * Custom event loop support *
+
2276 * ----------------------------------------------------------- */
+
2277
+
2292int fuse_session_fd(struct fuse_session *se);
+
2293
+
2302void fuse_session_process_buf(struct fuse_session *se,
+
2303 const struct fuse_buf *buf);
+
2304
+
2316int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2317
+
2318#ifdef __cplusplus
+
2319}
+
2320#endif
+
2321
+
2322#endif /* FUSE_LOWLEVEL_H_ */
+ +
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+ +
+ + + + diff --git a/doc/html/fuse__misc_8h_source.html b/doc/html/fuse__misc_8h_source.html new file mode 100644 index 0000000..42c19de --- /dev/null +++ b/doc/html/fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/fuse__mount__compat_8h_source.html b/doc/html/fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..5741ac8 --- /dev/null +++ b/doc/html/fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/fuse__opt_8c_source.html b/doc/html/fuse__opt_8c_source.html new file mode 100644 index 0000000..e4ed183 --- /dev/null +++ b/doc/html/fuse__opt_8c_source.html @@ -0,0 +1,517 @@ + + + + + + + +libfuse: lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
+
143
+
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
const char * templ
Definition fuse_opt.h:79
+
unsigned long offset
Definition fuse_opt.h:85
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse__opt_8h.html b/doc/html/fuse__opt_8h.html new file mode 100644 index 0000000..9cd2a20 --- /dev/null +++ b/doc/html/fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/fuse__opt_8h_source.html b/doc/html/fuse__opt_8h_source.html new file mode 100644 index 0000000..d99b5f9 --- /dev/null +++ b/doc/html/fuse__opt_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+ +
118};
+
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
const char * templ
Definition fuse_opt.h:79
+
unsigned long offset
Definition fuse_opt.h:85
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/fuse__signals_8c_source.html b/doc/html/fuse__signals_8c_source.html new file mode 100644 index 0000000..f381af5 --- /dev/null +++ b/doc/html/fuse__signals_8c_source.html @@ -0,0 +1,275 @@ + + + + + + + +libfuse: lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
26static int ignore_sigs[] = { SIGPIPE};
+
27static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
28static struct fuse_session *fuse_instance;
+
29
+
30#define BT_STACK_SZ (1024 * 1024)
+
31static void *backtrace_buffer[BT_STACK_SZ];
+
32
+
33static void dump_stack(void)
+
34{
+
35#ifdef HAVE_BACKTRACE
+
36 char **strings;
+
37
+
38 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
39 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
40
+
41 if (strings == NULL) {
+
42 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
43 strerror(errno));
+
44 return;
+
45 }
+
46
+
47 for (int idx = 0; idx < nptrs; idx++)
+
48 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
49
+
50 free(strings);
+
51#endif
+
52}
+
53
+
54static void exit_handler(int sig)
+
55{
+
56 if (fuse_instance == NULL)
+
57 return;
+
58
+
59 fuse_session_exit(fuse_instance);
+
60
+
61 if (sig < 0) {
+
62 fuse_log(FUSE_LOG_ERR,
+
63 "assertion error: signal value <= 0\n");
+
64 dump_stack();
+
65 abort();
+
66 fuse_instance->error = sig;
+
67 }
+
68
+
69 fuse_instance->error = sig;
+
70}
+
71
+
72static void exit_backtrace(int sig)
+
73{
+
74 if (fuse_instance == NULL)
+
75 return;
+
76
+
77 fuse_session_exit(fuse_instance);
+
78
+
79 fuse_remove_signal_handlers(fuse_instance);
+
80 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
81 dump_stack();
+
82 abort();
+
83}
+
84
+
85
+
86static void do_nothing(int sig)
+
87{
+
88 (void) sig;
+
89}
+
90
+
91static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
92{
+
93 struct sigaction sa;
+
94 struct sigaction old_sa;
+
95
+
96 memset(&sa, 0, sizeof(struct sigaction));
+
97 sa.sa_handler = remove ? SIG_DFL : handler;
+
98 sigemptyset(&(sa.sa_mask));
+
99 sa.sa_flags = 0;
+
100
+
101 if (sigaction(sig, NULL, &old_sa) == -1) {
+
102 perror("fuse: cannot get old signal handler");
+
103 return -1;
+
104 }
+
105
+
106 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
107 sigaction(sig, &sa, NULL) == -1) {
+
108 perror("fuse: cannot set signal handler");
+
109 return -1;
+
110 }
+
111 return 0;
+
112}
+
113
+
114static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
115 void (*handler)(int))
+
116{
+
117 for (int idx = 0; idx < nr_signals; idx++) {
+
118 int signal = signals[idx];
+
119
+
120 /*
+
121 * If we used SIG_IGN instead of the do_nothing function,
+
122 * then we would be unable to tell if we set SIG_IGN (and
+
123 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
124 * or if it was already set to SIG_IGN (and should be left
+
125 * untouched.
+
126 */
+
127 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
128 fuse_log(FUSE_LOG_ERR,
+
129 "Failed to install signal handler for sig %d\n",
+
130 signal);
+
131 return -1;
+
132 }
+
133 }
+
134
+
135 return 0;
+
136}
+
137
+
+
138int fuse_set_signal_handlers(struct fuse_session *se)
+
139{
+
140 size_t nr_signals;
+
141 int rc;
+
142
+
143 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
144 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
145 if (rc < 0)
+
146 return rc;
+
147
+
148 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
149 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
150 if (rc < 0)
+
151 return rc;
+
152
+
153 /*
+
154 * needs to be set independently if already set, as some applications
+
155 * may have multiple sessions and might rely on traditional behavior
+
156 * that the last session is used.
+
157 */
+
158 fuse_instance = se;
+
159
+
160 return 0;
+
161}
+
+
162
+
+
163int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
164{
+
165 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
166
+
167 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
168 exit_backtrace);
+
169 if (rc < 0)
+
170 return rc;
+
171
+
172 /* See fuse_set_signal_handlers, why set unconditionally */
+
173 fuse_instance = se;
+
174
+
175 return 0;
+
176}
+
+
177
+
178static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
179 void (*handler)(int))
+
180{
+
181 for (int idx = 0; idx < nr_signals; idx++)
+
182 set_one_signal_handler(signals[idx], handler, 1);
+
183}
+
184
+
+
185void fuse_remove_signal_handlers(struct fuse_session *se)
+
186{
+
187 size_t nr_signals;
+
188
+
189 if (fuse_instance != se)
+
190 fuse_log(FUSE_LOG_ERR,
+
191 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
192 else
+
193 fuse_instance = NULL;
+
194
+
195 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
196 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
197
+
198 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
199 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
200
+
201 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
202 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
203}
+
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+ +
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/fusermount_8c_source.html b/doc/html/fusermount_8c_source.html new file mode 100644 index 0000000..fa8eda1 --- /dev/null +++ b/doc/html/fusermount_8c_source.html @@ -0,0 +1,1796 @@ + + + + + + + +libfuse: util/fusermount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#ifdef HAVE_LINUX_CLOSE_RANGE_H
+
40#include <linux/close_range.h>
+
41#endif
+
42
+
43#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
44
+
45#define FUSE_DEV "/dev/fuse"
+
46
+
47static const char *progname;
+
48
+
49static int user_allow_other = 0;
+
50static int mount_max = 1000;
+
51
+
52static int auto_unmount = 0;
+
53
+
54#ifdef GETMNTENT_NEEDS_UNESCAPING
+
55// Older versions of musl libc don't unescape entries in /etc/mtab
+
56
+
57// unescapes octal sequences like \040 in-place
+
58// That's ok, because unescaping can not extend the length of the string.
+
59static void unescape(char *buf) {
+
60 char *src = buf;
+
61 char *dest = buf;
+
62 while (1) {
+
63 char *next_src = strchrnul(src, '\\');
+
64 int offset = next_src - src;
+
65 memmove(dest, src, offset);
+
66 src = next_src;
+
67 dest += offset;
+
68
+
69 if(*src == '\0') {
+
70 *dest = *src;
+
71 return;
+
72 }
+
73 src++;
+
74
+
75 if('0' <= src[0] && src[0] < '2' &&
+
76 '0' <= src[1] && src[1] < '8' &&
+
77 '0' <= src[2] && src[2] < '8') {
+
78 *dest++ = (src[0] - '0') << 6
+
79 | (src[1] - '0') << 3
+
80 | (src[2] - '0') << 0;
+
81 src += 3;
+
82 } else if (src[0] == '\\') {
+
83 *dest++ = '\\';
+
84 src += 1;
+
85 } else {
+
86 *dest++ = '\\';
+
87 }
+
88 }
+
89}
+
90
+
91static struct mntent *GETMNTENT(FILE *stream)
+
92{
+
93 struct mntent *entp = getmntent(stream);
+
94 if(entp != NULL) {
+
95 unescape(entp->mnt_fsname);
+
96 unescape(entp->mnt_dir);
+
97 unescape(entp->mnt_type);
+
98 unescape(entp->mnt_opts);
+
99 }
+
100 return entp;
+
101}
+
102#else
+
103#define GETMNTENT getmntent
+
104#endif // GETMNTENT_NEEDS_UNESCAPING
+
105
+
106/*
+
107 * Take a ',' separated option string and extract "x-" options
+
108 */
+
109static int extract_x_options(const char *original, char **non_x_opts,
+
110 char **x_opts)
+
111{
+
112 size_t orig_len;
+
113 const char *opt, *opt_end;
+
114
+
115 orig_len = strlen(original) + 1;
+
116
+
117 *non_x_opts = calloc(1, orig_len);
+
118 *x_opts = calloc(1, orig_len);
+
119
+
120 size_t non_x_opts_len = orig_len;
+
121 size_t x_opts_len = orig_len;
+
122
+
123 if (*non_x_opts == NULL || *x_opts == NULL) {
+
124 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
125 __func__, orig_len);
+
126 return -ENOMEM;
+
127 }
+
128
+
129 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
130 char *opt_buf;
+
131
+
132 opt_end = strchr(opt, ',');
+
133 if (opt_end == NULL)
+
134 opt_end = original + orig_len;
+
135
+
136 size_t opt_len = opt_end - opt;
+
137 size_t opt_len_left = orig_len - (opt - original);
+
138 size_t buf_len;
+
139 bool is_x_opts;
+
140
+
141 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
142 buf_len = x_opts_len;
+
143 is_x_opts = true;
+
144 opt_buf = *x_opts;
+
145 } else {
+
146 buf_len = non_x_opts_len;
+
147 is_x_opts = false;
+
148 opt_buf = *non_x_opts;
+
149 }
+
150
+
151 if (buf_len < orig_len) {
+
152 strncat(opt_buf, ",", 2);
+
153 buf_len -= 1;
+
154 }
+
155
+
156 /* omits ',' */
+
157 if ((ssize_t)(buf_len - opt_len) < 0) {
+
158 /* This would be a bug */
+
159 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
160 __func__, original);
+
161 return -EIO;
+
162 }
+
163
+
164 strncat(opt_buf, opt, opt_end - opt);
+
165 buf_len -= opt_len;
+
166
+
167 if (is_x_opts)
+
168 x_opts_len = buf_len;
+
169 else
+
170 non_x_opts_len = buf_len;
+
171 }
+
172
+
173 return 0;
+
174}
+
175
+
176static const char *get_user_name(void)
+
177{
+
178 struct passwd *pw = getpwuid(getuid());
+
179 if (pw != NULL && pw->pw_name != NULL)
+
180 return pw->pw_name;
+
181 else {
+
182 fprintf(stderr, "%s: could not determine username\n", progname);
+
183 return NULL;
+
184 }
+
185}
+
186
+
187static uid_t oldfsuid;
+
188static gid_t oldfsgid;
+
189
+
190static void drop_privs(void)
+
191{
+
192 if (getuid() != 0) {
+
193 oldfsuid = setfsuid(getuid());
+
194 oldfsgid = setfsgid(getgid());
+
195 }
+
196}
+
197
+
198static void restore_privs(void)
+
199{
+
200 if (getuid() != 0) {
+
201 setfsuid(oldfsuid);
+
202 setfsgid(oldfsgid);
+
203 }
+
204}
+
205
+
206#ifndef IGNORE_MTAB
+
207/*
+
208 * Make sure that /etc/mtab is checked and updated atomically
+
209 */
+
210static int lock_umount(void)
+
211{
+
212 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
213 int mtablock;
+
214 int res;
+
215 struct stat mtab_stat;
+
216
+
217 /* /etc/mtab could be a symlink to /proc/mounts */
+
218 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
219 return -1;
+
220
+
221 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
222 if (mtablock == -1) {
+
223 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
224 progname, strerror(errno));
+
225 return -1;
+
226 }
+
227 res = lockf(mtablock, F_LOCK, 0);
+
228 if (res < 0) {
+
229 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
230 strerror(errno));
+
231 close(mtablock);
+
232 return -1;
+
233 }
+
234
+
235 return mtablock;
+
236}
+
237
+
238static void unlock_umount(int mtablock)
+
239{
+
240 if (mtablock >= 0) {
+
241 int res;
+
242
+
243 res = lockf(mtablock, F_ULOCK, 0);
+
244 if (res < 0) {
+
245 fprintf(stderr, "%s: error releasing lock: %s\n",
+
246 progname, strerror(errno));
+
247 }
+
248 close(mtablock);
+
249 }
+
250}
+
251
+
252static int add_mount(const char *source, const char *mnt, const char *type,
+
253 const char *opts)
+
254{
+
255 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
256}
+
257
+
258static int may_unmount(const char *mnt, int quiet)
+
259{
+
260 struct mntent *entp;
+
261 FILE *fp;
+
262 const char *user = NULL;
+
263 char uidstr[32];
+
264 unsigned uidlen = 0;
+
265 int found;
+
266 const char *mtab = _PATH_MOUNTED;
+
267
+
268 user = get_user_name();
+
269 if (user == NULL)
+
270 return -1;
+
271
+
272 fp = setmntent(mtab, "r");
+
273 if (fp == NULL) {
+
274 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
275 strerror(errno));
+
276 return -1;
+
277 }
+
278
+
279 uidlen = sprintf(uidstr, "%u", getuid());
+
280
+
281 found = 0;
+
282 while ((entp = GETMNTENT(fp)) != NULL) {
+
283 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
284 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
285 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
286 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
287 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
288 char *p = strstr(entp->mnt_opts, "user=");
+
289 if (p &&
+
290 (p == entp->mnt_opts || *(p-1) == ',') &&
+
291 strcmp(p + 5, user) == 0) {
+
292 found = 1;
+
293 break;
+
294 }
+
295 /* /etc/mtab is a link pointing to
+
296 /proc/mounts: */
+
297 else if ((p =
+
298 strstr(entp->mnt_opts, "user_id=")) &&
+
299 (p == entp->mnt_opts ||
+
300 *(p-1) == ',') &&
+
301 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
302 (*(p+8+uidlen) == ',' ||
+
303 *(p+8+uidlen) == '\0')) {
+
304 found = 1;
+
305 break;
+
306 }
+
307 }
+
308 }
+
309 endmntent(fp);
+
310
+
311 if (!found) {
+
312 if (!quiet)
+
313 fprintf(stderr,
+
314 "%s: entry for %s not found in %s\n",
+
315 progname, mnt, mtab);
+
316 return -1;
+
317 }
+
318
+
319 return 0;
+
320}
+
321#endif
+
322
+
323/*
+
324 * Check whether the file specified in "fusermount3 -u" is really a
+
325 * mountpoint and not a symlink. This is necessary otherwise the user
+
326 * could move the mountpoint away and replace it with a symlink
+
327 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
328 * unmounting that (umount(2) will follow symlinks).
+
329 *
+
330 * This is the child process running in a separate mount namespace, so
+
331 * we don't mess with the global namespace and if the process is
+
332 * killed for any reason, mounts are automatically cleaned up.
+
333 *
+
334 * First make sure nothing is propagated back into the parent
+
335 * namespace by marking all mounts "private".
+
336 *
+
337 * Then bind mount parent onto a stable base where the user can't move
+
338 * it around.
+
339 *
+
340 * Finally check /proc/mounts for an entry matching the requested
+
341 * mountpoint. If it's found then we are OK, and the user can't move
+
342 * it around within the parent directory as rename() will return
+
343 * EBUSY. Be careful to ignore any mounts that existed before the
+
344 * bind.
+
345 */
+
346static int check_is_mount_child(void *p)
+
347{
+
348 const char **a = p;
+
349 const char *last = a[0];
+
350 const char *mnt = a[1];
+
351 const char *type = a[2];
+
352 int res;
+
353 const char *procmounts = "/proc/mounts";
+
354 int found;
+
355 FILE *fp;
+
356 struct mntent *entp;
+
357 int count;
+
358
+
359 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
360 if (res == -1) {
+
361 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
362 progname, strerror(errno));
+
363 return 1;
+
364 }
+
365
+
366 fp = setmntent(procmounts, "r");
+
367 if (fp == NULL) {
+
368 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
369 procmounts, strerror(errno));
+
370 return 1;
+
371 }
+
372
+
373 count = 0;
+
374 while (GETMNTENT(fp) != NULL)
+
375 count++;
+
376 endmntent(fp);
+
377
+
378 fp = setmntent(procmounts, "r");
+
379 if (fp == NULL) {
+
380 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
381 procmounts, strerror(errno));
+
382 return 1;
+
383 }
+
384
+
385 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
386 if (res == -1) {
+
387 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
388 progname, strerror(errno));
+
389 return 1;
+
390 }
+
391
+
392 found = 0;
+
393 while ((entp = GETMNTENT(fp)) != NULL) {
+
394 if (count > 0) {
+
395 count--;
+
396 continue;
+
397 }
+
398 if (entp->mnt_dir[0] == '/' &&
+
399 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
400 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
401 found = 1;
+
402 break;
+
403 }
+
404 }
+
405 endmntent(fp);
+
406
+
407 if (!found) {
+
408 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
409 return 1;
+
410 }
+
411
+
412 return 0;
+
413}
+
414
+
415static pid_t clone_newns(void *a)
+
416{
+
417 char buf[131072];
+
418 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
419
+
420#ifdef __ia64__
+
421 extern int __clone2(int (*fn)(void *),
+
422 void *child_stack_base, size_t stack_size,
+
423 int flags, void *arg, pid_t *ptid,
+
424 void *tls, pid_t *ctid);
+
425
+
426 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
427 CLONE_NEWNS, a, NULL, NULL, NULL);
+
428#else
+
429 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
430#endif
+
431}
+
432
+
433static int check_is_mount(const char *last, const char *mnt, const char *type)
+
434{
+
435 pid_t pid, p;
+
436 int status;
+
437 const char *a[3] = { last, mnt, type };
+
438
+
439 pid = clone_newns((void *) a);
+
440 if (pid == (pid_t) -1) {
+
441 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
442 progname, strerror(errno));
+
443 return -1;
+
444 }
+
445 p = waitpid(pid, &status, __WCLONE);
+
446 if (p == (pid_t) -1) {
+
447 fprintf(stderr, "%s: waitpid failed: %s\n",
+
448 progname, strerror(errno));
+
449 return -1;
+
450 }
+
451 if (!WIFEXITED(status)) {
+
452 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
453 progname, status);
+
454 return -1;
+
455 }
+
456 if (WEXITSTATUS(status) != 0)
+
457 return -1;
+
458
+
459 return 0;
+
460}
+
461
+
462static int chdir_to_parent(char *copy, const char **lastp)
+
463{
+
464 char *tmp;
+
465 const char *parent;
+
466 char buf[65536];
+
467 int res;
+
468
+
469 tmp = strrchr(copy, '/');
+
470 if (tmp == NULL || tmp[1] == '\0') {
+
471 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
472 progname, copy);
+
473 return -1;
+
474 }
+
475 if (tmp != copy) {
+
476 *tmp = '\0';
+
477 parent = copy;
+
478 *lastp = tmp + 1;
+
479 } else if (tmp[1] != '\0') {
+
480 *lastp = tmp + 1;
+
481 parent = "/";
+
482 } else {
+
483 *lastp = ".";
+
484 parent = "/";
+
485 }
+
486
+
487 res = chdir(parent);
+
488 if (res == -1) {
+
489 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
490 progname, parent, strerror(errno));
+
491 return -1;
+
492 }
+
493
+
494 if (getcwd(buf, sizeof(buf)) == NULL) {
+
495 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
496 progname, strerror(errno));
+
497 return -1;
+
498 }
+
499 if (strcmp(buf, parent) != 0) {
+
500 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
501 parent, buf);
+
502 return -1;
+
503
+
504 }
+
505
+
506 return 0;
+
507}
+
508
+
509#ifndef IGNORE_MTAB
+
510static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
511{
+
512 int res;
+
513 char *copy;
+
514 const char *last;
+
515 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
516
+
517 if (getuid() != 0) {
+
518 res = may_unmount(mnt, quiet);
+
519 if (res == -1)
+
520 return -1;
+
521 }
+
522
+
523 copy = strdup(mnt);
+
524 if (copy == NULL) {
+
525 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
526 return -1;
+
527 }
+
528
+
529 drop_privs();
+
530 res = chdir_to_parent(copy, &last);
+
531 if (res == -1) {
+
532 restore_privs();
+
533 goto out;
+
534 }
+
535
+
536 res = umount2(last, umount_flags);
+
537 restore_privs();
+
538 if (res == -1 && !quiet) {
+
539 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
540 progname, mnt, strerror(errno));
+
541 }
+
542
+
543out:
+
544 free(copy);
+
545 if (res == -1)
+
546 return -1;
+
547
+
548 res = chdir("/");
+
549 if (res == -1) {
+
550 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
551 return -1;
+
552 }
+
553
+
554 return fuse_mnt_remove_mount(progname, mnt);
+
555}
+
556
+
557static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
558{
+
559 int res;
+
560 int mtablock = lock_umount();
+
561
+
562 res = unmount_fuse_locked(mnt, quiet, lazy);
+
563 unlock_umount(mtablock);
+
564
+
565 return res;
+
566}
+
567
+
568static int count_fuse_fs(void)
+
569{
+
570 struct mntent *entp;
+
571 int count = 0;
+
572 const char *mtab = _PATH_MOUNTED;
+
573 FILE *fp = setmntent(mtab, "r");
+
574 if (fp == NULL) {
+
575 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
576 strerror(errno));
+
577 return -1;
+
578 }
+
579 while ((entp = GETMNTENT(fp)) != NULL) {
+
580 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
581 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
582 count ++;
+
583 }
+
584 endmntent(fp);
+
585 return count;
+
586}
+
587
+
588
+
589#else /* IGNORE_MTAB */
+
590static int count_fuse_fs(void)
+
591{
+
592 return 0;
+
593}
+
594
+
595static int add_mount(const char *source, const char *mnt, const char *type,
+
596 const char *opts)
+
597{
+
598 (void) source;
+
599 (void) mnt;
+
600 (void) type;
+
601 (void) opts;
+
602 return 0;
+
603}
+
604
+
605static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
606{
+
607 (void) quiet;
+
608 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
609}
+
610#endif /* IGNORE_MTAB */
+
611
+
612static void strip_line(char *line)
+
613{
+
614 char *s = strchr(line, '#');
+
615 if (s != NULL)
+
616 s[0] = '\0';
+
617 for (s = line + strlen(line) - 1;
+
618 s >= line && isspace((unsigned char) *s); s--);
+
619 s[1] = '\0';
+
620 for (s = line; isspace((unsigned char) *s); s++);
+
621 if (s != line)
+
622 memmove(line, s, strlen(s)+1);
+
623}
+
624
+
625static void parse_line(char *line, int linenum)
+
626{
+
627 int tmp;
+
628 if (strcmp(line, "user_allow_other") == 0)
+
629 user_allow_other = 1;
+
630 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+
631 mount_max = tmp;
+
632 else if(line[0])
+
633 fprintf(stderr,
+
634 "%s: unknown parameter in %s at line %i: '%s'\n",
+
635 progname, FUSE_CONF, linenum, line);
+
636}
+
637
+
638static void read_conf(void)
+
639{
+
640 FILE *fp = fopen(FUSE_CONF, "r");
+
641 if (fp != NULL) {
+
642 int linenum = 1;
+
643 char line[256];
+
644 int isnewline = 1;
+
645 while (fgets(line, sizeof(line), fp) != NULL) {
+
646 if (isnewline) {
+
647 if (line[strlen(line)-1] == '\n') {
+
648 strip_line(line);
+
649 parse_line(line, linenum);
+
650 } else {
+
651 isnewline = 0;
+
652 }
+
653 } else if(line[strlen(line)-1] == '\n') {
+
654 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
655
+
656 isnewline = 1;
+
657 }
+
658 if (isnewline)
+
659 linenum ++;
+
660 }
+
661 if (!isnewline) {
+
662 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
663
+
664 }
+
665 if (ferror(fp)) {
+
666 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
667 exit(1);
+
668 }
+
669 fclose(fp);
+
670 } else if (errno != ENOENT) {
+
671 bool fatal = (errno != EACCES && errno != ELOOP &&
+
672 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
673 errno != EOVERFLOW);
+
674 fprintf(stderr, "%s: failed to open %s: %s\n",
+
675 progname, FUSE_CONF, strerror(errno));
+
676 if (fatal)
+
677 exit(1);
+
678 }
+
679}
+
680
+
681static int begins_with(const char *s, const char *beg)
+
682{
+
683 if (strncmp(s, beg, strlen(beg)) == 0)
+
684 return 1;
+
685 else
+
686 return 0;
+
687}
+
688
+
689struct mount_flags {
+
690 const char *opt;
+
691 unsigned long flag;
+
692 int on;
+
693 int safe;
+
694};
+
695
+
696static struct mount_flags mount_flags[] = {
+
697 {"rw", MS_RDONLY, 0, 1},
+
698 {"ro", MS_RDONLY, 1, 1},
+
699 {"suid", MS_NOSUID, 0, 0},
+
700 {"nosuid", MS_NOSUID, 1, 1},
+
701 {"dev", MS_NODEV, 0, 0},
+
702 {"nodev", MS_NODEV, 1, 1},
+
703 {"exec", MS_NOEXEC, 0, 1},
+
704 {"noexec", MS_NOEXEC, 1, 1},
+
705 {"async", MS_SYNCHRONOUS, 0, 1},
+
706 {"sync", MS_SYNCHRONOUS, 1, 1},
+
707 {"atime", MS_NOATIME, 0, 1},
+
708 {"noatime", MS_NOATIME, 1, 1},
+
709 {"diratime", MS_NODIRATIME, 0, 1},
+
710 {"nodiratime", MS_NODIRATIME, 1, 1},
+
711 {"lazytime", MS_LAZYTIME, 1, 1},
+
712 {"nolazytime", MS_LAZYTIME, 0, 1},
+
713 {"relatime", MS_RELATIME, 1, 1},
+
714 {"norelatime", MS_RELATIME, 0, 1},
+
715 {"strictatime", MS_STRICTATIME, 1, 1},
+
716 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
717 {"dirsync", MS_DIRSYNC, 1, 1},
+
718 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
719 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
720 {NULL, 0, 0, 0}
+
721};
+
722
+
723static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
724{
+
725 int i;
+
726
+
727 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
728 const char *opt = mount_flags[i].opt;
+
729 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
730 *on = mount_flags[i].on;
+
731 *flag = mount_flags[i].flag;
+
732 if (!mount_flags[i].safe && getuid() != 0) {
+
733 *flag = 0;
+
734 fprintf(stderr,
+
735 "%s: unsafe option %s ignored\n",
+
736 progname, opt);
+
737 }
+
738 return 1;
+
739 }
+
740 }
+
741 return 0;
+
742}
+
743
+
744static int add_option(char **optsp, const char *opt, unsigned expand)
+
745{
+
746 char *newopts;
+
747 if (*optsp == NULL)
+
748 newopts = strdup(opt);
+
749 else {
+
750 unsigned oldsize = strlen(*optsp);
+
751 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
752 newopts = (char *) realloc(*optsp, newsize);
+
753 if (newopts)
+
754 sprintf(newopts + oldsize, ",%s", opt);
+
755 }
+
756 if (newopts == NULL) {
+
757 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
758 return -1;
+
759 }
+
760 *optsp = newopts;
+
761 return 0;
+
762}
+
763
+
764static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
765{
+
766 int i;
+
767 int l;
+
768
+
769 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
770 return -1;
+
771
+
772 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
773 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
774 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
775 return -1;
+
776 }
+
777
+
778 if (add_option(mnt_optsp, opts, 0) == -1)
+
779 return -1;
+
780 /* remove comma from end of opts*/
+
781 l = strlen(*mnt_optsp);
+
782 if ((*mnt_optsp)[l-1] == ',')
+
783 (*mnt_optsp)[l-1] = '\0';
+
784 if (getuid() != 0) {
+
785 const char *user = get_user_name();
+
786 if (user == NULL)
+
787 return -1;
+
788
+
789 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
790 return -1;
+
791 strcat(*mnt_optsp, user);
+
792 }
+
793 return 0;
+
794}
+
795
+
796static int opt_eq(const char *s, unsigned len, const char *opt)
+
797{
+
798 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
799 return 1;
+
800 else
+
801 return 0;
+
802}
+
803
+
804static int get_string_opt(const char *s, unsigned len, const char *opt,
+
805 char **val)
+
806{
+
807 int i;
+
808 unsigned opt_len = strlen(opt);
+
809 char *d;
+
810
+
811 if (*val)
+
812 free(*val);
+
813 *val = (char *) malloc(len - opt_len + 1);
+
814 if (!*val) {
+
815 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
816 return 0;
+
817 }
+
818
+
819 d = *val;
+
820 s += opt_len;
+
821 len -= opt_len;
+
822 for (i = 0; i < len; i++) {
+
823 if (s[i] == '\\' && i + 1 < len)
+
824 i++;
+
825 *d++ = s[i];
+
826 }
+
827 *d = '\0';
+
828 return 1;
+
829}
+
830
+
831/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
832 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
833 * "group_id=1".
+
834 * This wrapper detects this case and bails out with an error.
+
835 */
+
836static int mount_notrunc(const char *source, const char *target,
+
837 const char *filesystemtype, unsigned long mountflags,
+
838 const char *data) {
+
839 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
840 fprintf(stderr, "%s: mount options too long\n", progname);
+
841 errno = EINVAL;
+
842 return -1;
+
843 }
+
844 return mount(source, target, filesystemtype, mountflags, data);
+
845}
+
846
+
847
+
848static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
849 int fd, const char *opts, const char *dev, char **sourcep,
+
850 char **mnt_optsp)
+
851{
+
852 int res;
+
853 int flags = MS_NOSUID | MS_NODEV;
+
854 char *optbuf;
+
855 char *mnt_opts = NULL;
+
856 const char *s;
+
857 char *d;
+
858 char *fsname = NULL;
+
859 char *subtype = NULL;
+
860 char *source = NULL;
+
861 char *type = NULL;
+
862 int blkdev = 0;
+
863
+
864 optbuf = (char *) malloc(strlen(opts) + 128);
+
865 if (!optbuf) {
+
866 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
867 return -1;
+
868 }
+
869
+
870 for (s = opts, d = optbuf; *s;) {
+
871 unsigned len;
+
872 const char *fsname_str = "fsname=";
+
873 const char *subtype_str = "subtype=";
+
874 bool escape_ok = begins_with(s, fsname_str) ||
+
875 begins_with(s, subtype_str);
+
876 for (len = 0; s[len]; len++) {
+
877 if (escape_ok && s[len] == '\\' && s[len + 1])
+
878 len++;
+
879 else if (s[len] == ',')
+
880 break;
+
881 }
+
882 if (begins_with(s, fsname_str)) {
+
883 if (!get_string_opt(s, len, fsname_str, &fsname))
+
884 goto err;
+
885 } else if (begins_with(s, subtype_str)) {
+
886 if (!get_string_opt(s, len, subtype_str, &subtype))
+
887 goto err;
+
888 } else if (opt_eq(s, len, "blkdev")) {
+
889 if (getuid() != 0) {
+
890 fprintf(stderr,
+
891 "%s: option blkdev is privileged\n",
+
892 progname);
+
893 goto err;
+
894 }
+
895 blkdev = 1;
+
896 } else if (opt_eq(s, len, "auto_unmount")) {
+
897 auto_unmount = 1;
+
898 } else if (!opt_eq(s, len, "nonempty") &&
+
899 !begins_with(s, "fd=") &&
+
900 !begins_with(s, "rootmode=") &&
+
901 !begins_with(s, "user_id=") &&
+
902 !begins_with(s, "group_id=")) {
+
903 int on;
+
904 int flag;
+
905 int skip_option = 0;
+
906 if (opt_eq(s, len, "large_read")) {
+
907 struct utsname utsname;
+
908 unsigned kmaj, kmin;
+
909 res = uname(&utsname);
+
910 if (res == 0 &&
+
911 sscanf(utsname.release, "%u.%u",
+
912 &kmaj, &kmin) == 2 &&
+
913 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
914 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
915 skip_option = 1;
+
916 }
+
917 }
+
918 if (getuid() != 0 && !user_allow_other &&
+
919 (opt_eq(s, len, "allow_other") ||
+
920 opt_eq(s, len, "allow_root"))) {
+
921 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
922 goto err;
+
923 }
+
924 if (!skip_option) {
+
925 if (find_mount_flag(s, len, &on, &flag)) {
+
926 if (on)
+
927 flags |= flag;
+
928 else
+
929 flags &= ~flag;
+
930 } else if (opt_eq(s, len, "default_permissions") ||
+
931 opt_eq(s, len, "allow_other") ||
+
932 begins_with(s, "max_read=") ||
+
933 begins_with(s, "blksize=")) {
+
934 memcpy(d, s, len);
+
935 d += len;
+
936 *d++ = ',';
+
937 } else {
+
938 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
939 exit(1);
+
940 }
+
941 }
+
942 }
+
943 s += len;
+
944 if (*s)
+
945 s++;
+
946 }
+
947 *d = '\0';
+
948 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
949 if (res == -1)
+
950 goto err;
+
951
+
952 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
953 fd, rootmode, getuid(), getgid());
+
954
+
955 source = malloc((fsname ? strlen(fsname) : 0) +
+
956 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
957
+
958 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
959 if (!type || !source) {
+
960 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
961 goto err;
+
962 }
+
963
+
964 if (subtype)
+
965 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
966 else
+
967 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
968
+
969 if (fsname)
+
970 strcpy(source, fsname);
+
971 else
+
972 strcpy(source, subtype ? subtype : dev);
+
973
+
974 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
975 if (res == -1 && errno == ENODEV && subtype) {
+
976 /* Probably missing subtype support */
+
977 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
978 if (fsname) {
+
979 if (!blkdev)
+
980 sprintf(source, "%s#%s", subtype, fsname);
+
981 } else {
+
982 strcpy(source, type);
+
983 }
+
984
+
985 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
986 }
+
987 if (res == -1 && errno == EINVAL) {
+
988 /* It could be an old version not supporting group_id */
+
989 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
990 fd, rootmode, getuid());
+
991 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
992 }
+
993 if (res == -1) {
+
994 int errno_save = errno;
+
995 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
996 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
997 progname);
+
998 else
+
999 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
1000 strerror(errno_save));
+
1001 goto err;
+
1002 }
+
1003 *sourcep = source;
+
1004 *typep = type;
+
1005 *mnt_optsp = mnt_opts;
+
1006 free(fsname);
+
1007 free(optbuf);
+
1008
+
1009 return 0;
+
1010
+
1011err:
+
1012 free(fsname);
+
1013 free(subtype);
+
1014 free(source);
+
1015 free(type);
+
1016 free(mnt_opts);
+
1017 free(optbuf);
+
1018 return -1;
+
1019}
+
1020
+
1021static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1022{
+
1023 int res;
+
1024 const char *mnt = *mntp;
+
1025 const char *origmnt = mnt;
+
1026 struct statfs fs_buf;
+
1027 size_t i;
+
1028
+
1029 res = lstat(mnt, stbuf);
+
1030 if (res == -1) {
+
1031 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1032 progname, mnt, strerror(errno));
+
1033 return -1;
+
1034 }
+
1035
+
1036 /* No permission checking is done for root */
+
1037 if (getuid() == 0)
+
1038 return 0;
+
1039
+
1040 if (S_ISDIR(stbuf->st_mode)) {
+
1041 res = chdir(mnt);
+
1042 if (res == -1) {
+
1043 fprintf(stderr,
+
1044 "%s: failed to chdir to mountpoint: %s\n",
+
1045 progname, strerror(errno));
+
1046 return -1;
+
1047 }
+
1048 mnt = *mntp = ".";
+
1049 res = lstat(mnt, stbuf);
+
1050 if (res == -1) {
+
1051 fprintf(stderr,
+
1052 "%s: failed to access mountpoint %s: %s\n",
+
1053 progname, origmnt, strerror(errno));
+
1054 return -1;
+
1055 }
+
1056
+
1057 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1058 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1059 progname, origmnt);
+
1060 return -1;
+
1061 }
+
1062
+
1063 res = access(mnt, W_OK);
+
1064 if (res == -1) {
+
1065 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1066 progname, origmnt);
+
1067 return -1;
+
1068 }
+
1069 } else if (S_ISREG(stbuf->st_mode)) {
+
1070 static char procfile[256];
+
1071 *mountpoint_fd = open(mnt, O_WRONLY);
+
1072 if (*mountpoint_fd == -1) {
+
1073 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1074 progname, mnt, strerror(errno));
+
1075 return -1;
+
1076 }
+
1077 res = fstat(*mountpoint_fd, stbuf);
+
1078 if (res == -1) {
+
1079 fprintf(stderr,
+
1080 "%s: failed to access mountpoint %s: %s\n",
+
1081 progname, mnt, strerror(errno));
+
1082 return -1;
+
1083 }
+
1084 if (!S_ISREG(stbuf->st_mode)) {
+
1085 fprintf(stderr,
+
1086 "%s: mountpoint %s is no longer a regular file\n",
+
1087 progname, mnt);
+
1088 return -1;
+
1089 }
+
1090
+
1091 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+
1092 *mntp = procfile;
+
1093 } else {
+
1094 fprintf(stderr,
+
1095 "%s: mountpoint %s is not a directory or a regular file\n",
+
1096 progname, mnt);
+
1097 return -1;
+
1098 }
+
1099
+
1100 /* Do not permit mounting over anything in procfs - it has a couple
+
1101 * places to which we have "write access" without being supposed to be
+
1102 * able to just put anything we want there.
+
1103 * Luckily, without allow_other, we can't get other users to actually
+
1104 * use any fake information we try to put there anyway.
+
1105 * Use a whitelist to be safe. */
+
1106 if (statfs(*mntp, &fs_buf)) {
+
1107 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1108 progname, mnt, strerror(errno));
+
1109 return -1;
+
1110 }
+
1111
+
1112 /* Define permitted filesystems for the mount target. This was
+
1113 * originally the same list as used by the ecryptfs mount helper
+
1114 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1115 * but got expanded as we found more filesystems that needed to be
+
1116 * overlaid. */
+
1117 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1118 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1119 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1120 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1121 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1122 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1123 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1124 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1125 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1126 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1127 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1128 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1129 0x01161970 /* GFS2_MAGIC */,
+
1130 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1131 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1132 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1133 0x3153464A /* JFS_SUPER_MAGIC */,
+
1134 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1135 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1136 0x0000564C /* NCP_SUPER_MAGIC */,
+
1137 0x00006969 /* NFS_SUPER_MAGIC */,
+
1138 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1139 0x5346544E /* NTFS_SB_MAGIC */,
+
1140 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1141 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1142 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1143 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
+
1144 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1145 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1146 0x73717368 /* SQUASHFS_MAGIC */,
+
1147 0x01021994 /* TMPFS_MAGIC */,
+
1148 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1149#if __SIZEOF_LONG__ > 4
+
1150 0x736675005346544e /* UFSD */,
+
1151#endif
+
1152 0x58465342 /* XFS_SB_MAGIC */,
+
1153 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1154 0x858458f6 /* RAMFS_MAGIC */,
+
1155 };
+
1156 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1157 if (f_type_whitelist[i] == fs_buf.f_type)
+
1158 return 0;
+
1159 }
+
1160
+
1161 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1162 progname, (unsigned long)fs_buf.f_type);
+
1163 return -1;
+
1164}
+
1165
+
1166static int try_open(const char *dev, char **devp, int silent)
+
1167{
+
1168 int fd = open(dev, O_RDWR);
+
1169 if (fd != -1) {
+
1170 *devp = strdup(dev);
+
1171 if (*devp == NULL) {
+
1172 fprintf(stderr, "%s: failed to allocate memory\n",
+
1173 progname);
+
1174 close(fd);
+
1175 fd = -1;
+
1176 }
+
1177 } else if (errno == ENODEV ||
+
1178 errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1179 return -2;
+
1180 else if (!silent) {
+
1181 fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
+
1182 strerror(errno));
+
1183 }
+
1184 return fd;
+
1185}
+
1186
+
1187static int try_open_fuse_device(char **devp)
+
1188{
+
1189 int fd;
+
1190
+
1191 drop_privs();
+
1192 fd = try_open(FUSE_DEV, devp, 0);
+
1193 restore_privs();
+
1194 return fd;
+
1195}
+
1196
+
1197static int open_fuse_device(char **devp)
+
1198{
+
1199 int fd = try_open_fuse_device(devp);
+
1200 if (fd >= -1)
+
1201 return fd;
+
1202
+
1203 fprintf(stderr,
+
1204 "%s: fuse device not found, try 'modprobe fuse' first\n",
+
1205 progname);
+
1206
+
1207 return -1;
+
1208}
+
1209
+
1210
+
1211static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1212{
+
1213 int res;
+
1214 int fd;
+
1215 char *dev;
+
1216 struct stat stbuf;
+
1217 char *source = NULL;
+
1218 char *mnt_opts = NULL;
+
1219 const char *real_mnt = mnt;
+
1220 int mountpoint_fd = -1;
+
1221 char *do_mount_opts = NULL;
+
1222 char *x_opts = NULL;
+
1223
+
1224 fd = open_fuse_device(&dev);
+
1225 if (fd == -1)
+
1226 return -1;
+
1227
+
1228 drop_privs();
+
1229 read_conf();
+
1230
+
1231 if (getuid() != 0 && mount_max != -1) {
+
1232 int mount_count = count_fuse_fs();
+
1233 if (mount_count >= mount_max) {
+
1234 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1235 goto fail_close_fd;
+
1236 }
+
1237 }
+
1238
+
1239 // Extract any options starting with "x-"
+
1240 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1241 if (res)
+
1242 goto fail_close_fd;
+
1243
+
1244 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1245 restore_privs();
+
1246 if (res != -1)
+
1247 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1248 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1249
+
1250 if (mountpoint_fd != -1)
+
1251 close(mountpoint_fd);
+
1252
+
1253 if (res == -1)
+
1254 goto fail_close_fd;
+
1255
+
1256 res = chdir("/");
+
1257 if (res == -1) {
+
1258 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1259 goto fail_close_fd;
+
1260 }
+
1261
+
1262 if (geteuid() == 0) {
+
1263 if (x_opts && strlen(x_opts) > 0) {
+
1264 /*
+
1265 * Add back the options starting with "x-" to opts from
+
1266 * do_mount. +2 for ',' and '\0'
+
1267 */
+
1268 size_t mnt_opts_len = strlen(mnt_opts);
+
1269 size_t x_mnt_opts_len = mnt_opts_len+
+
1270 strlen(x_opts) + 2;
+
1271 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1272
+
1273 if (mnt_opts_len) {
+
1274 strcpy(x_mnt_opts, mnt_opts);
+
1275 strncat(x_mnt_opts, ",", 2);
+
1276 }
+
1277
+
1278 strncat(x_mnt_opts, x_opts,
+
1279 x_mnt_opts_len - mnt_opts_len - 2);
+
1280
+
1281 free(mnt_opts);
+
1282 mnt_opts = x_mnt_opts;
+
1283 }
+
1284
+
1285 res = add_mount(source, mnt, *type, mnt_opts);
+
1286 if (res == -1) {
+
1287 /* Can't clean up mount in a non-racy way */
+
1288 goto fail_close_fd;
+
1289 }
+
1290 }
+
1291
+
1292out_free:
+
1293 free(source);
+
1294 free(mnt_opts);
+
1295 free(dev);
+
1296 free(x_opts);
+
1297 free(do_mount_opts);
+
1298
+
1299 return fd;
+
1300
+
1301fail_close_fd:
+
1302 close(fd);
+
1303 fd = -1;
+
1304 goto out_free;
+
1305}
+
1306
+
1307static int send_fd(int sock_fd, int fd)
+
1308{
+
1309 int retval;
+
1310 struct msghdr msg;
+
1311 struct cmsghdr *p_cmsg;
+
1312 struct iovec vec;
+
1313 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1314 int *p_fds;
+
1315 char sendchar = 0;
+
1316
+
1317 msg.msg_control = cmsgbuf;
+
1318 msg.msg_controllen = sizeof(cmsgbuf);
+
1319 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1320 p_cmsg->cmsg_level = SOL_SOCKET;
+
1321 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1322 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1323 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1324 *p_fds = fd;
+
1325 msg.msg_controllen = p_cmsg->cmsg_len;
+
1326 msg.msg_name = NULL;
+
1327 msg.msg_namelen = 0;
+
1328 msg.msg_iov = &vec;
+
1329 msg.msg_iovlen = 1;
+
1330 msg.msg_flags = 0;
+
1331 /* "To pass file descriptors or credentials you need to send/read at
+
1332 * least one byte" (man 7 unix) */
+
1333 vec.iov_base = &sendchar;
+
1334 vec.iov_len = sizeof(sendchar);
+
1335 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1336 if (retval != 1) {
+
1337 perror("sending file descriptor");
+
1338 return -1;
+
1339 }
+
1340 return 0;
+
1341}
+
1342
+
1343/* Helper for should_auto_unmount
+
1344 *
+
1345 * fusermount typically has the s-bit set - initial open of `mnt` was as root
+
1346 * and got EACCESS as 'allow_other' was not specified.
+
1347 * Try opening `mnt` again with uid and guid of the calling process.
+
1348 */
+
1349static int recheck_ENOTCONN_as_owner(const char *mnt)
+
1350{
+
1351 int pid = fork();
+
1352 if(pid == -1) {
+
1353 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1354 _exit(EXIT_FAILURE);
+
1355 } else if(pid == 0) {
+
1356 uid_t uid = getuid();
+
1357 gid_t gid = getgid();
+
1358 if(setresgid(gid, gid, gid) == -1) {
+
1359 perror("fuse: can't set resgid");
+
1360 _exit(EXIT_FAILURE);
+
1361 }
+
1362 if(setresuid(uid, uid, uid) == -1) {
+
1363 perror("fuse: can't set resuid");
+
1364 _exit(EXIT_FAILURE);
+
1365 }
+
1366
+
1367 int fd = open(mnt, O_RDONLY);
+
1368 if(fd == -1 && errno == ENOTCONN)
+
1369 _exit(EXIT_SUCCESS);
+
1370 else
+
1371 _exit(EXIT_FAILURE);
+
1372 } else {
+
1373 int status;
+
1374 int res = waitpid(pid, &status, 0);
+
1375 if (res == -1) {
+
1376 perror("fuse: waiting for child failed");
+
1377 _exit(EXIT_FAILURE);
+
1378 }
+
1379 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1380 }
+
1381}
+
1382
+
1383/* The parent fuse process has died: decide whether to auto_unmount.
+
1384 *
+
1385 * In the normal case (umount or fusermount -u), the filesystem
+
1386 * has already been unmounted. If we simply unmount again we can
+
1387 * cause problems with stacked mounts (e.g. autofs).
+
1388 *
+
1389 * So we unmount here only in abnormal case where fuse process has
+
1390 * died without unmount happening. To detect this, we first look in
+
1391 * the mount table to make sure the mountpoint is still mounted and
+
1392 * has proper type. If so, we then see if opening the mount dir is
+
1393 * returning 'Transport endpoint is not connected'.
+
1394 *
+
1395 * The order of these is important, because if autofs is in use,
+
1396 * opening the dir to check for ENOTCONN will cause a new mount
+
1397 * in the normal case where filesystem has been unmounted cleanly.
+
1398 */
+
1399static int should_auto_unmount(const char *mnt, const char *type)
+
1400{
+
1401 char *copy;
+
1402 const char *last;
+
1403 int result = 0;
+
1404 int fd;
+
1405
+
1406 copy = strdup(mnt);
+
1407 if (copy == NULL) {
+
1408 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1409 return 0;
+
1410 }
+
1411
+
1412 if (chdir_to_parent(copy, &last) == -1)
+
1413 goto out;
+
1414 if (check_is_mount(last, mnt, type) == -1)
+
1415 goto out;
+
1416
+
1417 fd = open(mnt, O_RDONLY);
+
1418
+
1419 if (fd != -1) {
+
1420 close(fd);
+
1421 } else {
+
1422 switch(errno) {
+
1423 case ENOTCONN:
+
1424 result = 1;
+
1425 break;
+
1426 case EACCES:
+
1427 result = recheck_ENOTCONN_as_owner(mnt);
+
1428 break;
+
1429 default:
+
1430 result = 0;
+
1431 break;
+
1432 }
+
1433 }
+
1434out:
+
1435 free(copy);
+
1436 return result;
+
1437}
+
1438
+
1439static void usage(void)
+
1440{
+
1441 printf("%s: [options] mountpoint\n"
+
1442 "Options:\n"
+
1443 " -h print help\n"
+
1444 " -V print version\n"
+
1445 " -o opt[,opt...] mount options\n"
+
1446 " -u unmount\n"
+
1447 " -q quiet\n"
+
1448 " -z lazy unmount\n",
+
1449 progname);
+
1450 exit(1);
+
1451}
+
1452
+
1453static void show_version(void)
+
1454{
+
1455 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1456 exit(0);
+
1457}
+
1458
+
1459static void close_range_loop(int min_fd, int max_fd, int cfd)
+
1460{
+
1461 for (int fd = min_fd; fd <= max_fd; fd++)
+
1462 if (fd != cfd)
+
1463 close(fd);
+
1464}
+
1465
+
1466/*
+
1467 * Close all inherited fds that are not needed
+
1468 * Ideally these wouldn't come up at all, applications should better
+
1469 * use FD_CLOEXEC / O_CLOEXEC
+
1470 */
+
1471static int close_inherited_fds(int cfd)
+
1472{
+
1473 int rc = -1;
+
1474 int nullfd;
+
1475
+
1476 /* We can't even report an error */
+
1477 if (cfd <= STDERR_FILENO)
+
1478 return -EINVAL;
+
1479
+
1480#ifdef HAVE_LINUX_CLOSE_RANGE_H
+
1481 if (cfd < STDERR_FILENO + 2) {
+
1482 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
+
1483 } else {
+
1484 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
+
1485 if (rc < 0)
+
1486 goto fallback;
+
1487 }
+
1488
+
1489 /* Close high range */
+
1490 rc = close_range(cfd + 1, ~0U, 0);
+
1491#else
+
1492 goto fallback; /* make use of fallback to avoid compiler warnings */
+
1493#endif
+
1494
+
1495fallback:
+
1496 if (rc < 0) {
+
1497 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
+
1498
+
1499 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
+
1500 }
+
1501
+
1502 nullfd = open("/dev/null", O_RDWR);
+
1503 if (nullfd < 0) {
+
1504 perror("fusermount: cannot open /dev/null");
+
1505 return -errno;
+
1506 }
+
1507
+
1508 /* Redirect stdin, stdout, stderr to /dev/null */
+
1509 dup2(nullfd, STDIN_FILENO);
+
1510 dup2(nullfd, STDOUT_FILENO);
+
1511 dup2(nullfd, STDERR_FILENO);
+
1512 if (nullfd > STDERR_FILENO)
+
1513 close(nullfd);
+
1514
+
1515 return 0;
+
1516}
+
1517
+
1518int main(int argc, char *argv[])
+
1519{
+
1520 sigset_t sigset;
+
1521 int ch;
+
1522 int fd;
+
1523 int res;
+
1524 char *origmnt;
+
1525 char *mnt;
+
1526 static int unmount = 0;
+
1527 static int lazy = 0;
+
1528 static int quiet = 0;
+
1529 char *commfd = NULL;
+
1530 long cfd;
+
1531 const char *opts = "";
+
1532 const char *type = NULL;
+
1533 int setup_auto_unmount_only = 0;
+
1534
+
1535 static const struct option long_opts[] = {
+
1536 {"unmount", no_argument, NULL, 'u'},
+
1537 {"lazy", no_argument, NULL, 'z'},
+
1538 {"quiet", no_argument, NULL, 'q'},
+
1539 {"help", no_argument, NULL, 'h'},
+
1540 {"version", no_argument, NULL, 'V'},
+
1541 {"options", required_argument, NULL, 'o'},
+
1542 // Note: auto-unmount and comm-fd don't have short versions.
+
1543 // They'ne meant for internal use by mount.c
+
1544 {"auto-unmount", no_argument, NULL, 'U'},
+
1545 {"comm-fd", required_argument, NULL, 'c'},
+
1546 {0, 0, 0, 0}};
+
1547
+
1548 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1549 if (progname == NULL) {
+
1550 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1551 exit(1);
+
1552 }
+
1553
+
1554 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1555 NULL)) != -1) {
+
1556 switch (ch) {
+
1557 case 'h':
+
1558 usage();
+
1559 break;
+
1560
+
1561 case 'V':
+
1562 show_version();
+
1563 break;
+
1564
+
1565 case 'o':
+
1566 opts = optarg;
+
1567 break;
+
1568
+
1569 case 'u':
+
1570 unmount = 1;
+
1571 break;
+
1572 case 'U':
+
1573 unmount = 1;
+
1574 auto_unmount = 1;
+
1575 setup_auto_unmount_only = 1;
+
1576 break;
+
1577 case 'c':
+
1578 commfd = optarg;
+
1579 break;
+
1580 case 'z':
+
1581 lazy = 1;
+
1582 break;
+
1583
+
1584 case 'q':
+
1585 quiet = 1;
+
1586 break;
+
1587
+
1588 default:
+
1589 exit(1);
+
1590 }
+
1591 }
+
1592
+
1593 if (lazy && !unmount) {
+
1594 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1595 exit(1);
+
1596 }
+
1597
+
1598 if (optind >= argc) {
+
1599 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1600 exit(1);
+
1601 } else if (argc > optind + 1) {
+
1602 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1603 progname);
+
1604 exit(1);
+
1605 }
+
1606
+
1607 origmnt = argv[optind];
+
1608
+
1609 drop_privs();
+
1610 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1611 if (mnt != NULL) {
+
1612 res = chdir("/");
+
1613 if (res == -1) {
+
1614 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1615 goto err_out;
+
1616 }
+
1617 }
+
1618 restore_privs();
+
1619 if (mnt == NULL)
+
1620 exit(1);
+
1621
+
1622 umask(033);
+
1623 if (!setup_auto_unmount_only && unmount)
+
1624 goto do_unmount;
+
1625
+
1626 if(commfd == NULL)
+
1627 commfd = getenv(FUSE_COMMFD_ENV);
+
1628 if (commfd == NULL) {
+
1629 fprintf(stderr, "%s: old style mounting not supported\n",
+
1630 progname);
+
1631 goto err_out;
+
1632 }
+
1633
+
1634 res = libfuse_strtol(commfd, &cfd);
+
1635 if (res) {
+
1636 fprintf(stderr,
+
1637 "%s: invalid _FUSE_COMMFD: %s\n",
+
1638 progname, commfd);
+
1639 goto err_out;
+
1640
+
1641 }
+
1642
+
1643 {
+
1644 struct stat statbuf;
+
1645 fstat(cfd, &statbuf);
+
1646 if(!S_ISSOCK(statbuf.st_mode)) {
+
1647 fprintf(stderr,
+
1648 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1649 progname, cfd);
+
1650 goto err_out;
+
1651 }
+
1652 }
+
1653
+
1654 if (setup_auto_unmount_only)
+
1655 goto wait_for_auto_unmount;
+
1656
+
1657 fd = mount_fuse(mnt, opts, &type);
+
1658 if (fd == -1)
+
1659 goto err_out;
+
1660
+
1661 res = send_fd(cfd, fd);
+
1662 if (res != 0) {
+
1663 umount2(mnt, MNT_DETACH); /* lazy umount */
+
1664 goto err_out;
+
1665 }
+
1666 close(fd);
+
1667
+
1668 if (!auto_unmount) {
+
1669 free(mnt);
+
1670 free((void*) type);
+
1671 return 0;
+
1672 }
+
1673
+
1674wait_for_auto_unmount:
+
1675 /* Become a daemon and wait for the parent to exit or die.
+
1676 ie For the control socket to get closed.
+
1677 Btw, we don't want to use daemon() function here because
+
1678 it forks and messes with the file descriptors. */
+
1679
+
1680 res = close_inherited_fds(cfd);
+
1681 if (res < 0)
+
1682 exit(EXIT_FAILURE);
+
1683
+
1684 setsid();
+
1685 res = chdir("/");
+
1686 if (res == -1) {
+
1687 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1688 goto err_out;
+
1689 }
+
1690
+
1691 sigfillset(&sigset);
+
1692 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1693
+
1694 lazy = 1;
+
1695 quiet = 1;
+
1696
+
1697 while (1) {
+
1698 unsigned char buf[16];
+
1699 int n = recv(cfd, buf, sizeof(buf), 0);
+
1700 if (!n)
+
1701 break;
+
1702
+
1703 if (n < 0) {
+
1704 if (errno == EINTR)
+
1705 continue;
+
1706 break;
+
1707 }
+
1708 }
+
1709
+
1710 if (!should_auto_unmount(mnt, type)) {
+
1711 goto success_out;
+
1712 }
+
1713
+
1714do_unmount:
+
1715 if (geteuid() == 0)
+
1716 res = unmount_fuse(mnt, quiet, lazy);
+
1717 else {
+
1718 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+
1719 if (res == -1 && !quiet)
+
1720 fprintf(stderr,
+
1721 "%s: failed to unmount %s: %s\n",
+
1722 progname, mnt, strerror(errno));
+
1723 }
+
1724 if (res == -1)
+
1725 goto err_out;
+
1726
+
1727success_out:
+
1728 free((void*) type);
+
1729 free(mnt);
+
1730 return 0;
+
1731
+
1732err_out:
+
1733 free((void*) type);
+
1734 free(mnt);
+
1735 exit(1);
+
1736}
+
+ + + + diff --git a/doc/html/globals.html b/doc/html/globals.html new file mode 100644 index 0000000..3c93eba --- /dev/null +++ b/doc/html/globals.html @@ -0,0 +1,201 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented functions, variables, defines, enums, and typedefs with links to the documentation:
+ +

- f -

+
+ + + + diff --git a/doc/html/globals_defs.html b/doc/html/globals_defs.html new file mode 100644 index 0000000..fbe1bd5 --- /dev/null +++ b/doc/html/globals_defs.html @@ -0,0 +1,91 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented macros with links to the documentation:
+ +

- f -

+
+ + + + diff --git a/doc/html/globals_enum.html b/doc/html/globals_enum.html new file mode 100644 index 0000000..2ea04d8 --- /dev/null +++ b/doc/html/globals_enum.html @@ -0,0 +1,56 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented enums with links to the documentation:
+
+ + + + diff --git a/doc/html/globals_eval.html b/doc/html/globals_eval.html new file mode 100644 index 0000000..4841fa6 --- /dev/null +++ b/doc/html/globals_eval.html @@ -0,0 +1,59 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented enum values with links to the documentation:
+
+ + + + diff --git a/doc/html/globals_func.html b/doc/html/globals_func.html new file mode 100644 index 0000000..1cdefb3 --- /dev/null +++ b/doc/html/globals_func.html @@ -0,0 +1,140 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented functions with links to the documentation:
+ +

- f -

+
+ + + + diff --git a/doc/html/globals_type.html b/doc/html/globals_type.html new file mode 100644 index 0000000..35c29d9 --- /dev/null +++ b/doc/html/globals_type.html @@ -0,0 +1,57 @@ + + + + + + + +libfuse: Globals + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
Here is a list of all documented typedefs with links to the documentation:
+
+ + + + diff --git a/doc/html/hello_8c.html b/doc/html/hello_8c.html new file mode 100644 index 0000000..080e5a3 --- /dev/null +++ b/doc/html/hello_8c.html @@ -0,0 +1,258 @@ + + + + + + + +libfuse: example/hello.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + + + diff --git a/doc/html/hello_8c_source.html b/doc/html/hello_8c_source.html new file mode 100644 index 0000000..1b04c95 --- /dev/null +++ b/doc/html/hello_8c_source.html @@ -0,0 +1,247 @@ + + + + + + + +libfuse: example/hello.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60 return NULL;
+
61}
+
62
+
63static int hello_getattr(const char *path, struct stat *stbuf,
+
64 struct fuse_file_info *fi)
+
65{
+
66 (void) fi;
+
67 int res = 0;
+
68
+
69 memset(stbuf, 0, sizeof(struct stat));
+
70 if (strcmp(path, "/") == 0) {
+
71 stbuf->st_mode = S_IFDIR | 0755;
+
72 stbuf->st_nlink = 2;
+
73 } else if (strcmp(path+1, options.filename) == 0) {
+
74 stbuf->st_mode = S_IFREG | 0444;
+
75 stbuf->st_nlink = 1;
+
76 stbuf->st_size = strlen(options.contents);
+
77 } else
+
78 res = -ENOENT;
+
79
+
80 return res;
+
81}
+
82
+
83static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
84 off_t offset, struct fuse_file_info *fi,
+
85 enum fuse_readdir_flags flags)
+
86{
+
87 (void) offset;
+
88 (void) fi;
+
89 (void) flags;
+
90
+
91 if (strcmp(path, "/") != 0)
+
92 return -ENOENT;
+
93
+
94 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
95 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
96 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
97
+
98 return 0;
+
99}
+
100
+
101static int hello_open(const char *path, struct fuse_file_info *fi)
+
102{
+
103 if (strcmp(path+1, options.filename) != 0)
+
104 return -ENOENT;
+
105
+
106 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
107 return -EACCES;
+
108
+
109 return 0;
+
110}
+
111
+
112static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
113 struct fuse_file_info *fi)
+
114{
+
115 size_t len;
+
116 (void) fi;
+
117 if(strcmp(path+1, options.filename) != 0)
+
118 return -ENOENT;
+
119
+
120 len = strlen(options.contents);
+
121 if (offset < len) {
+
122 if (offset + size > len)
+
123 size = len - offset;
+
124 memcpy(buf, options.contents + offset, size);
+
125 } else
+
126 size = 0;
+
127
+
128 return size;
+
129}
+
130
+
131static const struct fuse_operations hello_oper = {
+
132 .init = hello_init,
+
133 .getattr = hello_getattr,
+
134 .readdir = hello_readdir,
+
135 .open = hello_open,
+
136 .read = hello_read,
+
137};
+
138
+
139static void show_help(const char *progname)
+
140{
+
141 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
142 printf("File-system specific options:\n"
+
143 " --name=<s> Name of the \"hello\" file\n"
+
144 " (default: \"hello\")\n"
+
145 " --contents=<s> Contents \"hello\" file\n"
+
146 " (default \"Hello, World!\\n\")\n"
+
147 "\n");
+
148}
+
149
+
150int main(int argc, char *argv[])
+
151{
+
152 int ret;
+
153 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
154
+
155 /* Set defaults -- we have to use strdup so that
+
156 fuse_opt_parse can free the defaults if other
+
157 values are specified */
+
158 options.filename = strdup("hello");
+
159 options.contents = strdup("Hello World!\n");
+
160
+
161 /* Parse options */
+
162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
163 return 1;
+
164
+
165 /* When --help is specified, first print our own file-system
+
166 specific help text, then signal fuse_main to show
+
167 additional help (by adding `--help` to the options again)
+
168 without usage: line (by setting argv[0] to the empty
+
169 string) */
+
170 if (options.show_help) {
+
171 show_help(argv[0]);
+
172 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
173 args.argv[0][0] = '\0';
+
174 }
+
175
+
176 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
177 fuse_opt_free_args(&args);
+
178 return ret;
+
179}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:245
+ + + + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/hello__ll_8c.html b/doc/html/hello__ll_8c.html new file mode 100644 index 0000000..e66d6b0 --- /dev/null +++ b/doc/html/hello__ll_8c.html @@ -0,0 +1,389 @@ + + + + + + + +libfuse: example/hello_ll.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll.c.

+
+ + + + diff --git a/doc/html/hello__ll_8c_source.html b/doc/html/hello__ll_8c_source.html new file mode 100644 index 0000000..a9023bb --- /dev/null +++ b/doc/html/hello__ll_8c_source.html @@ -0,0 +1,377 @@ + + + + + + + +libfuse: example/hello_ll.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file COPYING.
+
7*/
+
8
+
21#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
22
+
23#include <fuse_lowlevel.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <unistd.h>
+
30#include <assert.h>
+
31
+
32static const char *hello_str = "Hello World!\n";
+
33static const char *hello_name = "hello";
+
34
+
35static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
36{
+
37 stbuf->st_ino = ino;
+
38 switch (ino) {
+
39 case 1:
+
40 stbuf->st_mode = S_IFDIR | 0755;
+
41 stbuf->st_nlink = 2;
+
42 break;
+
43
+
44 case 2:
+
45 stbuf->st_mode = S_IFREG | 0444;
+
46 stbuf->st_nlink = 1;
+
47 stbuf->st_size = strlen(hello_str);
+
48 break;
+
49
+
50 default:
+
51 return -1;
+
52 }
+
53 return 0;
+
54}
+
55
+
56static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
57{
+
58 (void)userdata;
+
59
+
60 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
61 conn->no_interrupt = 1;
+
62
+
63 /* Test setting flags the old way */
+ +
65 conn->want &= ~FUSE_CAP_ASYNC_READ;
+
66}
+
67
+
68static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
69 struct fuse_file_info *fi)
+
70{
+
71 struct stat stbuf;
+
72
+
73 (void) fi;
+
74
+
75 memset(&stbuf, 0, sizeof(stbuf));
+
76 if (hello_stat(ino, &stbuf) == -1)
+
77 fuse_reply_err(req, ENOENT);
+
78 else
+
79 fuse_reply_attr(req, &stbuf, 1.0);
+
80}
+
81
+
82static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
83{
+
84 struct fuse_entry_param e;
+
85
+
86 if (parent != 1 || strcmp(name, hello_name) != 0)
+
87 fuse_reply_err(req, ENOENT);
+
88 else {
+
89 memset(&e, 0, sizeof(e));
+
90 e.ino = 2;
+
91 e.attr_timeout = 1.0;
+
92 e.entry_timeout = 1.0;
+
93 hello_stat(e.ino, &e.attr);
+
94
+
95 fuse_reply_entry(req, &e);
+
96 }
+
97}
+
98
+
99struct dirbuf {
+
100 char *p;
+
101 size_t size;
+
102};
+
103
+
104static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
105 fuse_ino_t ino)
+
106{
+
107 struct stat stbuf;
+
108 size_t oldsize = b->size;
+
109 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
110 b->p = (char *) realloc(b->p, b->size);
+
111 memset(&stbuf, 0, sizeof(stbuf));
+
112 stbuf.st_ino = ino;
+
113 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
114 b->size);
+
115}
+
116
+
117#define min(x, y) ((x) < (y) ? (x) : (y))
+
118
+
119static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
120 off_t off, size_t maxsize)
+
121{
+
122 if (off < bufsize)
+
123 return fuse_reply_buf(req, buf + off,
+
124 min(bufsize - off, maxsize));
+
125 else
+
126 return fuse_reply_buf(req, NULL, 0);
+
127}
+
128
+
129static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
130 off_t off, struct fuse_file_info *fi)
+
131{
+
132 (void) fi;
+
133
+
134 if (ino != 1)
+
135 fuse_reply_err(req, ENOTDIR);
+
136 else {
+
137 struct dirbuf b;
+
138
+
139 memset(&b, 0, sizeof(b));
+
140 dirbuf_add(req, &b, ".", 1);
+
141 dirbuf_add(req, &b, "..", 1);
+
142 dirbuf_add(req, &b, hello_name, 2);
+
143 reply_buf_limited(req, b.p, b.size, off, size);
+
144 free(b.p);
+
145 }
+
146}
+
147
+
148static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
149 struct fuse_file_info *fi)
+
150{
+
151 if (ino != 2)
+
152 fuse_reply_err(req, EISDIR);
+
153 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
154 fuse_reply_err(req, EACCES);
+
155 else
+
156 fuse_reply_open(req, fi);
+
157}
+
158
+
159static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
160 off_t off, struct fuse_file_info *fi)
+
161{
+
162 (void) fi;
+
163
+
164 assert(ino == 2);
+
165 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
166}
+
167
+
168static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
169 size_t size)
+
170{
+
171 (void)size;
+
172 assert(ino == 1 || ino == 2);
+
173 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
174 {
+
175 const char *buf = "hello_ll_getxattr_value";
+
176 fuse_reply_buf(req, buf, strlen(buf));
+
177 }
+
178 else
+
179 {
+
180 fuse_reply_err(req, ENOTSUP);
+
181 }
+
182}
+
183
+
184static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
185 const char *value, size_t size, int flags)
+
186{
+
187 (void)flags;
+
188 (void)size;
+
189 assert(ino == 1 || ino == 2);
+
190 const char* exp_val = "hello_ll_setxattr_value";
+
191 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
192 strlen(exp_val) == size &&
+
193 strncmp(value, exp_val, size) == 0)
+
194 {
+
195 fuse_reply_err(req, 0);
+
196 }
+
197 else
+
198 {
+
199 fuse_reply_err(req, ENOTSUP);
+
200 }
+
201}
+
202
+
203static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
204{
+
205 assert(ino == 1 || ino == 2);
+
206 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
207 {
+
208 fuse_reply_err(req, 0);
+
209 }
+
210 else
+
211 {
+
212 fuse_reply_err(req, ENOTSUP);
+
213 }
+
214}
+
215
+
216static const struct fuse_lowlevel_ops hello_ll_oper = {
+
217 .init = hello_ll_init,
+
218 .lookup = hello_ll_lookup,
+
219 .getattr = hello_ll_getattr,
+
220 .readdir = hello_ll_readdir,
+
221 .open = hello_ll_open,
+
222 .read = hello_ll_read,
+
223 .setxattr = hello_ll_setxattr,
+
224 .getxattr = hello_ll_getxattr,
+
225 .removexattr = hello_ll_removexattr,
+
226};
+
227
+
228int main(int argc, char *argv[])
+
229{
+
230 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
231 struct fuse_session *se;
+
232 struct fuse_cmdline_opts opts;
+
233 struct fuse_loop_config *config;
+
234 int ret = -1;
+
235
+
236 if (fuse_parse_cmdline(&args, &opts) != 0)
+
237 return 1;
+
238 if (opts.show_help) {
+
239 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
242 ret = 0;
+
243 goto err_out1;
+
244 } else if (opts.show_version) {
+
245 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
247 ret = 0;
+
248 goto err_out1;
+
249 }
+
250
+
251 if(opts.mountpoint == NULL) {
+
252 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
253 printf(" %s --help\n", argv[0]);
+
254 ret = 1;
+
255 goto err_out1;
+
256 }
+
257
+
258 se = fuse_session_new(&args, &hello_ll_oper,
+
259 sizeof(hello_ll_oper), NULL);
+
260 if (se == NULL)
+
261 goto err_out1;
+
262
+
263 if (fuse_set_signal_handlers(se) != 0)
+
264 goto err_out2;
+
265
+
266 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
267 goto err_out3;
+
268
+
269 fuse_daemonize(opts.foreground);
+
270
+
271 /* Block until ctrl+c or fusermount -u */
+
272 if (opts.singlethread)
+
273 ret = fuse_session_loop(se);
+
274 else {
+
275 config = fuse_loop_cfg_create();
+
276 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
277 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
278 ret = fuse_session_loop_mt(se, config);
+
279 fuse_loop_cfg_destroy(config);
+
280 config = NULL;
+
281 }
+
282
+ +
284err_out3:
+ +
286err_out2:
+ +
288err_out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291
+
292 return ret ? 1 : 0;
+
293}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/hello__ll__uds_8c.html b/doc/html/hello__ll__uds_8c.html new file mode 100644 index 0000000..30b26a8 --- /dev/null +++ b/doc/html/hello__ll__uds_8c.html @@ -0,0 +1,392 @@ + + + + + + + +libfuse: example/hello_ll_uds.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ +
conn->want &= ~FUSE_CAP_ASYNC_READ;
+
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+

Definition in file hello_ll_uds.c.

+
+ + + + diff --git a/doc/html/hello__ll__uds_8c_source.html b/doc/html/hello__ll__uds_8c_source.html new file mode 100644 index 0000000..638404d --- /dev/null +++ b/doc/html/hello__ll__uds_8c_source.html @@ -0,0 +1,443 @@ + + + + + + + +libfuse: example/hello_ll_uds.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
46{
+
47 stbuf->st_ino = ino;
+
48 switch (ino) {
+
49 case 1:
+
50 stbuf->st_mode = S_IFDIR | 0755;
+
51 stbuf->st_nlink = 2;
+
52 break;
+
53
+
54 case 2:
+
55 stbuf->st_mode = S_IFREG | 0444;
+
56 stbuf->st_nlink = 1;
+
57 stbuf->st_size = strlen(hello_str);
+
58 break;
+
59
+
60 default:
+
61 return -1;
+
62 }
+
63 return 0;
+
64}
+
65
+
66static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 struct stat stbuf;
+
70
+
71 (void) fi;
+
72
+
73 memset(&stbuf, 0, sizeof(stbuf));
+
74 if (hello_stat(ino, &stbuf) == -1)
+
75 fuse_reply_err(req, ENOENT);
+
76 else
+
77 fuse_reply_attr(req, &stbuf, 1.0);
+
78}
+
79
+
80static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
81{
+
82 (void)userdata;
+
83
+
84 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
85 conn->no_interrupt = 1;
+
86}
+
87
+
88static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
89{
+
90 struct fuse_entry_param e;
+
91
+
92 if (parent != 1 || strcmp(name, hello_name) != 0)
+
93 fuse_reply_err(req, ENOENT);
+
94 else {
+
95 memset(&e, 0, sizeof(e));
+
96 e.ino = 2;
+
97 e.attr_timeout = 1.0;
+
98 e.entry_timeout = 1.0;
+
99 hello_stat(e.ino, &e.attr);
+
100
+
101 fuse_reply_entry(req, &e);
+
102 }
+
103}
+
104
+
105struct dirbuf {
+
106 char *p;
+
107 size_t size;
+
108};
+
109
+
110static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
111 fuse_ino_t ino)
+
112{
+
113 struct stat stbuf;
+
114 size_t oldsize = b->size;
+
115 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
116 b->p = (char *) realloc(b->p, b->size);
+
117 memset(&stbuf, 0, sizeof(stbuf));
+
118 stbuf.st_ino = ino;
+
119 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
120 b->size);
+
121}
+
122
+
123#define min(x, y) ((x) < (y) ? (x) : (y))
+
124
+
125static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
126 off_t off, size_t maxsize)
+
127{
+
128 if (off < bufsize)
+
129 return fuse_reply_buf(req, buf + off,
+
130 min(bufsize - off, maxsize));
+
131 else
+
132 return fuse_reply_buf(req, NULL, 0);
+
133}
+
134
+
135static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
136 off_t off, struct fuse_file_info *fi)
+
137{
+
138 (void) fi;
+
139
+
140 if (ino != 1)
+
141 fuse_reply_err(req, ENOTDIR);
+
142 else {
+
143 struct dirbuf b;
+
144
+
145 memset(&b, 0, sizeof(b));
+
146 dirbuf_add(req, &b, ".", 1);
+
147 dirbuf_add(req, &b, "..", 1);
+
148 dirbuf_add(req, &b, hello_name, 2);
+
149 reply_buf_limited(req, b.p, b.size, off, size);
+
150 free(b.p);
+
151 }
+
152}
+
153
+
154static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
155 struct fuse_file_info *fi)
+
156{
+
157 if (ino != 2)
+
158 fuse_reply_err(req, EISDIR);
+
159 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
160 fuse_reply_err(req, EACCES);
+
161 else
+
162 fuse_reply_open(req, fi);
+
163}
+
164
+
165static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
166 off_t off, struct fuse_file_info *fi)
+
167{
+
168 (void) fi;
+
169
+
170 assert(ino == 2);
+
171 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
172}
+
173
+
174static const struct fuse_lowlevel_ops hello_ll_oper = {
+
175 .init = hello_ll_init,
+
176 .lookup = hello_ll_lookup,
+
177 .getattr = hello_ll_getattr,
+
178 .readdir = hello_ll_readdir,
+
179 .open = hello_ll_open,
+
180 .read = hello_ll_read,
+
181};
+
182
+
183static int create_socket(const char *socket_path) {
+
184 struct sockaddr_un addr;
+
185
+
186 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
187 sizeof(addr.sun_path)) {
+
188 printf("Socket path may not be longer than %zu characters\n",
+
189 sizeof(addr.sun_path) - 1);
+
190 return -1;
+
191 }
+
192
+
193 if (remove(socket_path) == -1 && errno != ENOENT) {
+
194 printf("Could not delete previous socket file entry at %s. Error: "
+
195 "%s\n", socket_path, strerror(errno));
+
196 return -1;
+
197 }
+
198
+
199 memset(&addr, 0, sizeof(struct sockaddr_un));
+
200 strcpy(addr.sun_path, socket_path);
+
201
+
202 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
203 if (sfd == -1) {
+
204 printf("Could not create socket. Error: %s\n", strerror(errno));
+
205 return -1;
+
206 }
+
207
+
208 addr.sun_family = AF_UNIX;
+
209 if (bind(sfd, (struct sockaddr *) &addr,
+
210 sizeof(struct sockaddr_un)) == -1) {
+
211 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
212 return -1;
+
213 }
+
214
+
215 if (listen(sfd, 1) == -1)
+
216 return -1;
+
217
+
218 printf("Awaiting connection on socket at %s...\n", socket_path);
+
219 int cfd = accept(sfd, NULL, NULL);
+
220 if (cfd == -1) {
+
221 printf("Could not accept connection. Error: %s\n",
+
222 strerror(errno));
+
223 return -1;
+
224 } else {
+
225 printf("Accepted connection!\n");
+
226 }
+
227 return cfd;
+
228}
+
229
+
230static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
231 void *userdata) {
+
232 (void)userdata;
+
233
+
234 ssize_t written = 0;
+
235 int cur = 0;
+
236 for (;;) {
+
237 written = writev(fd, iov+cur, count-cur);
+
238 if (written < 0)
+
239 return written;
+
240
+
241 while (cur < count && written >= iov[cur].iov_len)
+
242 written -= iov[cur++].iov_len;
+
243 if (cur == count)
+
244 break;
+
245
+
246 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
247 iov[cur].iov_len -= written;
+
248 }
+
249 return written;
+
250}
+
251
+
252
+
253static ssize_t readall(int fd, void *buf, size_t len) {
+
254 size_t count = 0;
+
255
+
256 while (count < len) {
+
257 int i = read(fd, (char *)buf + count, len - count);
+
258 if (!i)
+
259 break;
+
260
+
261 if (i < 0)
+
262 return i;
+
263
+
264 count += i;
+
265 }
+
266 return count;
+
267}
+
268
+
269static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
270 (void)userdata;
+
271
+
272 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
273 if (res == -1)
+
274 return res;
+
275
+
276
+
277 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
278 if (packet_len > buf_len)
+
279 return -1;
+
280
+
281 int prev_res = res;
+
282
+
283 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
284 packet_len - sizeof(struct fuse_in_header));
+
285
+
286 return (res == -1) ? res : (res + prev_res);
+
287}
+
288
+
289static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
290 off_t *offout, size_t len,
+
291 unsigned int flags, void *userdata) {
+
292 (void)userdata;
+
293
+
294 size_t count = 0;
+
295 while (count < len) {
+
296 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
297 if (i < 1)
+
298 return i;
+
299
+
300 count += i;
+
301 }
+
302 return count;
+
303}
+
304
+
305static void fuse_cmdline_help_uds(void)
+
306{
+
307 printf(" -h --help print help\n"
+
308 " -V --version print version\n"
+
309 " -d -o debug enable debug output (implies -f)\n");
+
310}
+
311
+
312int main(int argc, char *argv[])
+
313{
+
314 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
315 struct fuse_session *se;
+
316 struct fuse_cmdline_opts opts;
+
317 const struct fuse_custom_io io = {
+
318 .writev = stream_writev,
+
319 .read = stream_read,
+
320 .splice_receive = NULL,
+
321 .splice_send = stream_splice_send,
+
322 };
+
323 int cfd = -1;
+
324 int ret = -1;
+
325
+
326 if (fuse_parse_cmdline(&args, &opts) != 0)
+
327 return 1;
+
328 if (opts.show_help) {
+
329 printf("usage: %s [options]\n\n", argv[0]);
+
330 fuse_cmdline_help_uds();
+ +
332 ret = 0;
+
333 goto err_out1;
+
334 } else if (opts.show_version) {
+
335 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
337 ret = 0;
+
338 goto err_out1;
+
339 }
+
340
+
341 se = fuse_session_new(&args, &hello_ll_oper,
+
342 sizeof(hello_ll_oper), NULL);
+
343 if (se == NULL)
+
344 goto err_out1;
+
345
+
346 if (fuse_set_signal_handlers(se) != 0)
+
347 goto err_out2;
+
348
+
349 cfd = create_socket("/tmp/libfuse-hello-ll.sock");
+
350 if (cfd == -1)
+
351 goto err_out3;
+
352
+
353 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
354 goto err_out3;
+
355
+
356 /* Block until ctrl+c */
+
357 ret = fuse_session_loop(se);
+
358err_out3:
+ +
360err_out2:
+ +
362err_out1:
+
363 free(opts.mountpoint);
+
364 fuse_opt_free_args(&args);
+
365
+
366 return ret ? 1 : 0;
+
367}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + +
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
+ + + + diff --git a/doc/html/helper_8c_source.html b/doc/html/helper_8c_source.html new file mode 100644 index 0000000..abe6345 --- /dev/null +++ b/doc/html/helper_8c_source.html @@ -0,0 +1,615 @@ + + + + + + + +libfuse: lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
+ +
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
+ +
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
+
252
+
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
+
306
+
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond,cap) \
+
427 if (cond) conn->want_ext |= (cap)
+
428#define LL_DISABLE(cond,cap) \
+
429 if (cond) conn->want_ext &= ~(cap)
+
430
+
431 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
432 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
433
+
434 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
435 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
436
+
437 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
438 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
439
+
440 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
441 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
442
+
443 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
444 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
445
+
446 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
447 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
448
+
449 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
450 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
451
+
452 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
453 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
454
+
455 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
456 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
457}
+
+
458
+
+
459struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
460{
+
461 struct fuse_conn_info_opts *opts;
+
462
+
463 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
464 if(opts == NULL) {
+
465 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
466 return NULL;
+
467 }
+
468 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
469 free(opts);
+
470 return NULL;
+
471 }
+
472 return opts;
+
473}
+
+
474
+
+
475int fuse_open_channel(const char *mountpoint, const char* options)
+
476{
+
477 struct mount_opts *opts = NULL;
+
478 int fd = -1;
+
479 const char *argv[] = { "", "-o", options };
+
480 int argc = sizeof(argv) / sizeof(argv[0]);
+
481 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
482
+
483 opts = parse_mount_opts(&args);
+
484 if (opts == NULL)
+
485 return -1;
+
486
+
487 fd = fuse_kern_mount(mountpoint, opts);
+
488 destroy_mount_opts(opts);
+
489
+
490 return fd;
+
491}
+
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5222
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5171
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:475
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5227
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:459
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+ +
void fuse_cmdline_help(void)
Definition helper.c:130
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
void fuse_lowlevel_version(void)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/iconv_8c_source.html b/doc/html/iconv_8c_source.html new file mode 100644 index 0000000..de9c100 --- /dev/null +++ b/doc/html/iconv_8c_source.html @@ -0,0 +1,817 @@ + + + + + + + +libfuse: lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571static void *iconv_init(struct fuse_conn_info *conn,
+
572 struct fuse_config *cfg)
+
573{
+
574 struct iconv *ic = iconv_get();
+
575 fuse_fs_init(ic->next, conn, cfg);
+
576 /* Don't touch cfg->nullpath_ok, we can work with
+
577 either */
+
578 return ic;
+
579}
+
580
+
581static void iconv_destroy(void *data)
+
582{
+
583 struct iconv *ic = data;
+
584 fuse_fs_destroy(ic->next);
+
585 iconv_close(ic->tofs);
+
586 iconv_close(ic->fromfs);
+
587 pthread_mutex_destroy(&ic->lock);
+
588 free(ic->from_code);
+
589 free(ic->to_code);
+
590 free(ic);
+
591}
+
592
+
593static const struct fuse_operations iconv_oper = {
+
594 .destroy = iconv_destroy,
+
595 .init = iconv_init,
+
596 .getattr = iconv_getattr,
+
597 .access = iconv_access,
+
598 .readlink = iconv_readlink,
+
599 .opendir = iconv_opendir,
+
600 .readdir = iconv_readdir,
+
601 .releasedir = iconv_releasedir,
+
602 .mknod = iconv_mknod,
+
603 .mkdir = iconv_mkdir,
+
604 .symlink = iconv_symlink,
+
605 .unlink = iconv_unlink,
+
606 .rmdir = iconv_rmdir,
+
607 .rename = iconv_rename,
+
608 .link = iconv_link,
+
609 .chmod = iconv_chmod,
+
610 .chown = iconv_chown,
+
611 .truncate = iconv_truncate,
+
612 .utimens = iconv_utimens,
+
613 .create = iconv_create,
+
614 .open = iconv_open_file,
+
615 .read_buf = iconv_read_buf,
+
616 .write_buf = iconv_write_buf,
+
617 .statfs = iconv_statfs,
+
618 .flush = iconv_flush,
+
619 .release = iconv_release,
+
620 .fsync = iconv_fsync,
+
621 .fsyncdir = iconv_fsyncdir,
+
622 .setxattr = iconv_setxattr,
+
623 .getxattr = iconv_getxattr,
+
624 .listxattr = iconv_listxattr,
+
625 .removexattr = iconv_removexattr,
+
626 .lock = iconv_lock,
+
627 .flock = iconv_flock,
+
628 .bmap = iconv_bmap,
+
629 .lseek = iconv_lseek,
+
630};
+
631
+
632static const struct fuse_opt iconv_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
636 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
638};
+
639
+
640static void iconv_help(void)
+
641{
+
642 char *charmap;
+
643 const char *old = setlocale(LC_CTYPE, "");
+
644
+
645 charmap = strdup(nl_langinfo(CODESET));
+
646 if (old)
+
647 setlocale(LC_CTYPE, old);
+
648 else
+
649 perror("setlocale");
+
650
+
651 printf(
+
652" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
653" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
654 charmap);
+
655 free(charmap);
+
656}
+
657
+
658static int iconv_opt_proc(void *data, const char *arg, int key,
+
659 struct fuse_args *outargs)
+
660{
+
661 (void) data; (void) arg; (void) outargs;
+
662
+
663 if (!key) {
+
664 iconv_help();
+
665 return -1;
+
666 }
+
667
+
668 return 1;
+
669}
+
670
+
671static struct fuse_fs *iconv_new(struct fuse_args *args,
+
672 struct fuse_fs *next[])
+
673{
+
674 struct fuse_fs *fs;
+
675 struct iconv *ic;
+
676 const char *old = NULL;
+
677 const char *from;
+
678 const char *to;
+
679
+
680 ic = calloc(1, sizeof(struct iconv));
+
681 if (ic == NULL) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
683 return NULL;
+
684 }
+
685
+
686 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
687 goto out_free;
+
688
+
689 if (!next[0] || next[1]) {
+
690 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
691 goto out_free;
+
692 }
+
693
+
694 from = ic->from_code ? ic->from_code : "UTF-8";
+
695 to = ic->to_code ? ic->to_code : "";
+
696 /* FIXME: detect charset equivalence? */
+
697 if (!to[0])
+
698 old = setlocale(LC_CTYPE, "");
+
699 ic->tofs = iconv_open(from, to);
+
700 if (ic->tofs == (iconv_t) -1) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
702 to, from);
+
703 goto out_free;
+
704 }
+
705 ic->fromfs = iconv_open(to, from);
+
706 if (ic->tofs == (iconv_t) -1) {
+
707 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
708 from, to);
+
709 goto out_iconv_close_to;
+
710 }
+
711 if (old) {
+
712 setlocale(LC_CTYPE, old);
+
713 old = NULL;
+
714 }
+
715
+
716 ic->next = next[0];
+
717 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
718 if (!fs)
+
719 goto out_iconv_close_from;
+
720
+
721 return fs;
+
722
+
723out_iconv_close_from:
+
724 iconv_close(ic->fromfs);
+
725out_iconv_close_to:
+
726 iconv_close(ic->tofs);
+
727out_free:
+
728 free(ic->from_code);
+
729 free(ic->to_code);
+
730 free(ic);
+
731 if (old) {
+
732 setlocale(LC_CTYPE, old);
+
733 }
+
734 return NULL;
+
735}
+
736
+
737FUSE_REGISTER_MODULE(iconv, iconv_new);
+ +
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1395
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/include_2cuse__lowlevel_8h_source.html b/doc/html/include_2cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..4eb3405 --- /dev/null +++ b/doc/html/include_2cuse__lowlevel_8h_source.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: include/cuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+
struct fuse_req * fuse_req_t
+ + + + +
+ + + + diff --git a/doc/html/include_2fuse_8h.html b/doc/html/include_2fuse_8h.html new file mode 100644 index 0000000..ecc0ae3 --- /dev/null +++ b/doc/html/include_2fuse_8h.html @@ -0,0 +1,866 @@ + + + + + + + +libfuse: include/fuse.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_config
 
struct  fuse_operations
 
struct  fuse_context
 
+ + + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
 
+ + + + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
 
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
 
+ + + + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
 
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
 
void fuse_lib_help (struct fuse_args *args)
 
int fuse_mount (struct fuse *f, const char *mountpoint)
 
void fuse_unmount (struct fuse *f)
 
void fuse_destroy (struct fuse *f)
 
int fuse_loop (struct fuse *f)
 
void fuse_exit (struct fuse *f)
 
struct fuse_contextfuse_get_context (void)
 
int fuse_getgroups (int size, gid_t list[])
 
int fuse_interrupted (void)
 
int fuse_invalidate_path (struct fuse *f, const char *path)
 
int fuse_start_cleanup_thread (struct fuse *fuse)
 
void fuse_stop_cleanup_thread (struct fuse *fuse)
 
int fuse_clean_cache (struct fuse *fuse)
 
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
 
struct fuse_session * fuse_get_session (struct fuse *f)
 
int fuse_open_channel (const char *mountpoint, const char *options)
 
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
 factory_ 
)    fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1395 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 87 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1366 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: all file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+
+ +

Definition at line 58 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 42 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4458 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5169 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4664 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operationsop,
size_t op_size,
void * private_data 
)
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4879 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4669 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4545 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4679 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4688 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path 
)
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4698 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + + +
void fuse_lib_help (struct fuse_argsargs)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4769 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4602 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operationsop,
size_t op_size,
struct libfuse_versionversion,
void * user_data 
)
+
+

The real main function

+

Do not call this directly, use fuse_main()

+

The real main function

+

Do not call this directly, use fuse_main()

+

Main function of FUSE.

+

This is for the lazy. This is all that has to be called from the main() function.

+

This function does the following:

    +
  • parses command line options, and handles –help and –version
  • +
  • installs signal handlers for INT, HUP, TERM and PIPE
  • +
  • registers an exit handler to unmount the filesystem on program exit
  • +
  • creates a fuse handle
  • +
  • registers the operations
  • +
  • calls either the single-threaded or the multi-threaded event loop
  • +
+

Most file systems will have to parse some file-system specific arguments before calling this function. It is recommended to do this with fuse_opt_parse() and a processing function that passes through any unknown options (this can also be achieved by just passing NULL as the processing function). That way, the remaining options can be passed directly to fuse_main().

+

fuse_main() accepts all options that can be passed to fuse_parse_cmdline(), fuse_new(), or fuse_session_new().

+

Option parsing skips argv[0], which is assumed to contain the program name. This element must always be present and is used to construct a basic usage: message for the –help output. argv[0] may also be set to the empty string. In this case the usage message is suppressed. This can be used by file systems to print their own usage line first. See hello.c for an example of how to do this.

+

Note: this is currently implemented as a macro.

+

The following error codes may be returned from fuse_main(): 1: Invalid option arguments 2: No mount point specified 3: FUSE setup failed 4: Mounting failed 5: Failed to daemonize (detach from session) 6: Failed to set up signal handlers 7: An error occurred during the life of the file system

+
Parameters
+ + + + + +
argcthe argument counter passed to the main() function
argvthe argument vector passed to the main() function
opthe file system operation
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
0 on success, nonzero on failure
+

Example usage, see hello.c

+ +

Definition at line 307 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5220 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options 
)
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 475 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4929 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4937 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5225 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse_8h_source.html b/doc/html/include_2fuse_8h_source.html new file mode 100644 index 0000000..6c44db7 --- /dev/null +++ b/doc/html/include_2fuse_8h_source.html @@ -0,0 +1,641 @@ + + + + + + + +libfuse: include/fuse.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
37struct fuse;
+
38
+
+ + +
52 FUSE_READDIR_PLUS = (1 << 0)
+
53};
+
+
54
+
+ + +
69 FUSE_FILL_DIR_PLUS = (1 << 1)
+
70};
+
+
71
+
87typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
88 const struct stat *stbuf, off_t off,
+
89 enum fuse_fill_dir_flags flags);
+
101struct fuse_config {
+
106 int32_t set_gid;
+
107 uint32_t gid;
+
108
+
113 int32_t set_uid;
+
114 uint32_t uid;
+
115
+
120 int32_t set_mode;
+
121 uint32_t umask;
+
122
+
127 double entry_timeout;
+
128
+
137 double negative_timeout;
+
138
+
143 double attr_timeout;
+
144
+
148 int32_t intr;
+
149
+
155 int32_t intr_signal;
+
156
+
167 int32_t remember;
+
168
+
185 int32_t hard_remove;
+
186
+
198 int32_t use_ino;
+
199
+
207 int32_t readdir_ino;
+
208
+
226 int32_t direct_io;
+
227
+
245 int32_t kernel_cache;
+
246
+
253 int32_t auto_cache;
+
254
+
255 /*
+
256 * The timeout in seconds for which file attributes are cached
+
257 * for the purpose of checking if auto_cache should flush the
+
258 * file data on open.
+
259 */
+
260 int32_t ac_attr_timeout_set;
+
261 double ac_attr_timeout;
+
262
+
273 int32_t nullpath_ok;
+
274
+
279 int32_t show_help;
+
280 char *modules;
+
281 int32_t debug;
+
282
+
288 uint32_t fmask;
+
289 uint32_t dmask;
+
290
+
297 int32_t no_rofd_flush;
+
298
+ +
313
+
314
+
318 uint32_t flags;
+
319
+
323 uint64_t reserved[48];
+
324};
+
325
+
326
+
349struct fuse_operations {
+
361 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
362
+
371 int (*readlink) (const char *, char *, size_t);
+
372
+
379 int (*mknod) (const char *, mode_t, dev_t);
+
380
+
387 int (*mkdir) (const char *, mode_t);
+
388
+
390 int (*unlink) (const char *);
+
391
+
393 int (*rmdir) (const char *);
+
394
+
396 int (*symlink) (const char *, const char *);
+
397
+
407 int (*rename) (const char *, const char *, unsigned int flags);
+
408
+
410 int (*link) (const char *, const char *);
+
411
+
417 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
418
+
427 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
428
+
437 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
438
+
486 int (*open) (const char *, struct fuse_file_info *);
+
487
+
497 int (*read) (const char *, char *, size_t, off_t,
+
498 struct fuse_file_info *);
+
499
+
509 int (*write) (const char *, const char *, size_t, off_t,
+
510 struct fuse_file_info *);
+
511
+
516 int (*statfs) (const char *, struct statvfs *);
+
517
+
546 int (*flush) (const char *, struct fuse_file_info *);
+
547
+
560 int (*release) (const char *, struct fuse_file_info *);
+
561
+
567 int (*fsync) (const char *, int, struct fuse_file_info *);
+
568
+
570 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
571
+
573 int (*getxattr) (const char *, const char *, char *, size_t);
+
574
+
576 int (*listxattr) (const char *, char *, size_t);
+
577
+
579 int (*removexattr) (const char *, const char *);
+
580
+
589 int (*opendir) (const char *, struct fuse_file_info *);
+
590
+
613 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
614 struct fuse_file_info *, enum fuse_readdir_flags);
+
615
+
621 int (*releasedir) (const char *, struct fuse_file_info *);
+
622
+
631 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
632
+
641 void *(*init) (struct fuse_conn_info *conn,
+
642 struct fuse_config *cfg);
+
643
+
649 void (*destroy) (void *private_data);
+
650
+
660 int (*access) (const char *, int);
+
661
+
672 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
673
+
704 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
705 struct flock *);
+
706
+
719 int (*utimens) (const char *, const struct timespec tv[2],
+
720 struct fuse_file_info *fi);
+
721
+
728 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
729
+
730#if FUSE_USE_VERSION < 35
+
731 int (*ioctl) (const char *, int cmd, void *arg,
+
732 struct fuse_file_info *, unsigned int flags, void *data);
+
733#else
+
750 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
751 struct fuse_file_info *, unsigned int flags, void *data);
+
752#endif
+
753
+
769 int (*poll) (const char *, struct fuse_file_info *,
+
770 struct fuse_pollhandle *ph, unsigned *reventsp);
+
771
+
781 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
782 struct fuse_file_info *);
+
783
+
798 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
799 size_t size, off_t off, struct fuse_file_info *);
+
818 int (*flock) (const char *, struct fuse_file_info *, int op);
+
819
+
828 int (*fallocate) (const char *, int, off_t, off_t,
+
829 struct fuse_file_info *);
+
830
+
843 ssize_t (*copy_file_range) (const char *path_in,
+
844 struct fuse_file_info *fi_in,
+
845 off_t offset_in, const char *path_out,
+
846 struct fuse_file_info *fi_out,
+
847 off_t offset_out, size_t size, int flags);
+
848
+
852 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
853};
+
854
+
860struct fuse_context {
+
862 struct fuse *fuse;
+
863
+
865 uid_t uid;
+
866
+
868 gid_t gid;
+
869
+
871 pid_t pid;
+
872
+
874 void *private_data;
+
875
+
877 mode_t umask;
+
878};
+
879
+
885int fuse_main_real_versioned(int argc, char *argv[],
+
886 const struct fuse_operations *op, size_t op_size,
+
887 struct libfuse_version *version, void *user_data);
+
888static inline int fuse_main_real(int argc, char *argv[],
+
889 const struct fuse_operations *op,
+
890 size_t op_size, void *user_data)
+
891{
+
892 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
893 .minor = FUSE_MINOR_VERSION,
+
894 .hotfix = FUSE_HOTFIX_VERSION,
+
895 .padding = 0 };
+
896
+
897 fuse_log(FUSE_LOG_ERR,
+
898 "%s is a libfuse internal function, please use fuse_main()\n",
+
899 __func__);
+
900
+
901 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
902 user_data);
+
903}
+
904
+
959static inline int fuse_main_fn(int argc, char *argv[],
+
960 const struct fuse_operations *op,
+
961 void *user_data)
+
962{
+
963 struct libfuse_version version = {
+
964 .major = FUSE_MAJOR_VERSION,
+
965 .minor = FUSE_MINOR_VERSION,
+
966 .hotfix = FUSE_HOTFIX_VERSION,
+
967 .padding = 0
+
968 };
+
969
+
970 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
971 user_data);
+
972}
+
973#define fuse_main(argc, argv, op, user_data) \
+
974 fuse_main_fn(argc, argv, op, user_data)
+
975
+
976/* ----------------------------------------------------------- *
+
977 * More detailed API *
+
978 * ----------------------------------------------------------- */
+
979
+
991void fuse_lib_help(struct fuse_args *args);
+
992
+
993/* Do not call this directly, use fuse_new() instead */
+
994struct fuse *_fuse_new_30(struct fuse_args *args,
+
995 const struct fuse_operations *op, size_t op_size,
+
996 struct libfuse_version *version, void *user_data);
+
997struct fuse *_fuse_new_31(struct fuse_args *args,
+
998 const struct fuse_operations *op, size_t op_size,
+
999 struct libfuse_version *version, void *user_data);
+
1000
+
1028#if FUSE_USE_VERSION == 30
+
1029static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1030 const struct fuse_operations *op,
+
1031 size_t op_size, void *user_data)
+
1032{
+
1033 struct libfuse_version version = {
+
1034 .major = FUSE_MAJOR_VERSION,
+
1035 .minor = FUSE_MINOR_VERSION,
+
1036 .hotfix = FUSE_HOTFIX_VERSION,
+
1037 .padding = 0
+
1038 };
+
1039
+
1040 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1041}
+
1042#else /* FUSE_USE_VERSION */
+
1043static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1044 const struct fuse_operations *op,
+
1045 size_t op_size, void *user_data)
+
1046{
+
1047 struct libfuse_version version = {
+
1048 .major = FUSE_MAJOR_VERSION,
+
1049 .minor = FUSE_MINOR_VERSION,
+
1050 .hotfix = FUSE_HOTFIX_VERSION,
+
1051 .padding = 0
+
1052 };
+
1053
+
1054 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1055}
+
1056#endif
+
1057#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1058
+
1067int fuse_mount(struct fuse *f, const char *mountpoint);
+
1068
+
1076void fuse_unmount(struct fuse *f);
+
1077
+
1086void fuse_destroy(struct fuse *f);
+
1087
+
1103int fuse_loop(struct fuse *f);
+
1104
+
1113void fuse_exit(struct fuse *f);
+
1114
+
1115#if FUSE_USE_VERSION < 32
+
1116int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1117#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1118#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1119int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1120#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1121#else
+
1153#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1154int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1155#else
+
1156#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1157#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1158#endif
+
1159
+
1160
+
1169struct fuse_context *fuse_get_context(void);
+
1170
+
1189int fuse_getgroups(int size, gid_t list[]);
+
1190
+
1196int fuse_interrupted(void);
+
1197
+
1209int fuse_invalidate_path(struct fuse *f, const char *path);
+
1210
+ +
1219
+
1226void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1227
+
1237int fuse_clean_cache(struct fuse *fuse);
+
1238
+
1239/*
+
1240 * Stacking API
+
1241 */
+
1242
+
1248struct fuse_fs;
+
1249
+
1250/*
+
1251 * These functions call the relevant filesystem operation, and return
+
1252 * the result.
+
1253 *
+
1254 * If the operation is not defined, they return -ENOSYS, with the
+
1255 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1256 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1257 */
+
1258
+
1259int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1260 struct fuse_file_info *fi);
+
1261int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1262 const char *newpath, unsigned int flags);
+
1263int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1264int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1265int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1266 const char *path);
+
1267int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1268int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1269 struct fuse_file_info *fi);
+
1270int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1271 struct fuse_file_info *fi);
+
1272int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1273 off_t off, struct fuse_file_info *fi);
+
1274int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1275 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1276 struct fuse_file_info *fi);
+
1277int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1278 size_t size, off_t off, struct fuse_file_info *fi);
+
1279int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1280 struct fuse_bufvec *buf, off_t off,
+
1281 struct fuse_file_info *fi);
+
1282int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1283 struct fuse_file_info *fi);
+
1284int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1285 struct fuse_file_info *fi);
+
1286int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1287int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1288 struct fuse_file_info *fi);
+
1289int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1290 fuse_fill_dir_t filler, off_t off,
+
1291 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1292int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1293 struct fuse_file_info *fi);
+
1294int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1295 struct fuse_file_info *fi);
+
1296int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1297 struct fuse_file_info *fi);
+
1298int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1299 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1300int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1301 struct fuse_file_info *fi, int op);
+
1302int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1303 struct fuse_file_info *fi);
+
1304int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1305 struct fuse_file_info *fi);
+
1306int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1307 struct fuse_file_info *fi);
+
1308int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1309 const struct timespec tv[2], struct fuse_file_info *fi);
+
1310int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1311int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1312 size_t len);
+
1313int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1314 dev_t rdev);
+
1315int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1316int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1317 const char *value, size_t size, int flags);
+
1318int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1319 char *value, size_t size);
+
1320int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1321 size_t size);
+
1322int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1323 const char *name);
+
1324int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1325 uint64_t *idx);
+
1326#if FUSE_USE_VERSION < 35
+
1327int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1328 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1329 void *data);
+
1330#else
+
1331int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1332 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1333 void *data);
+
1334#endif
+
1335int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1336 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1337 unsigned *reventsp);
+
1338int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1339 off_t offset, off_t length, struct fuse_file_info *fi);
+
1340ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1341 struct fuse_file_info *fi_in, off_t off_in,
+
1342 const char *path_out,
+
1343 struct fuse_file_info *fi_out, off_t off_out,
+
1344 size_t len, int flags);
+
1345off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1346 struct fuse_file_info *fi);
+
1347void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1348 struct fuse_config *cfg);
+
1349void fuse_fs_destroy(struct fuse_fs *fs);
+
1350
+
1351int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1352
+
1366struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1367 void *private_data);
+
1368
+
1383typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1384 struct fuse_fs *fs[]);
+
+
1395#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1396 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1397
+
1399struct fuse_session *fuse_get_session(struct fuse *f);
+
1400
+
1409int fuse_open_channel(const char *mountpoint, const char *options);
+
1410
+
1411#ifdef __cplusplus
+
1412}
+
1413#endif
+
1414
+
1415#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4679
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
int fuse_interrupted(void)
Definition fuse.c:4688
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4929
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4664
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4458
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:475
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4937
+
fuse_fill_dir_flags
Definition fuse.h:58
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:51
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
fuse_readdir_flags
Definition fuse.h:42
+ + + + +
uint32_t fmask
Definition fuse.h:288
+
int32_t show_help
Definition fuse.h:279
+
uint32_t flags
Definition fuse.h:318
+
int32_t direct_io
Definition fuse.h:226
+
int32_t no_rofd_flush
Definition fuse.h:297
+
int32_t nullpath_ok
Definition fuse.h:273
+
int32_t parallel_direct_writes
Definition fuse.h:312
+
int32_t intr_signal
Definition fuse.h:155
+
int32_t remember
Definition fuse.h:167
+
int32_t kernel_cache
Definition fuse.h:245
+
int32_t readdir_ino
Definition fuse.h:207
+
int32_t use_ino
Definition fuse.h:198
+
double entry_timeout
Definition fuse.h:127
+
int32_t set_mode
Definition fuse.h:120
+
int32_t auto_cache
Definition fuse.h:253
+
uint64_t reserved[48]
Definition fuse.h:323
+
int32_t intr
Definition fuse.h:148
+
double negative_timeout
Definition fuse.h:137
+
int32_t set_gid
Definition fuse.h:106
+
int32_t set_uid
Definition fuse.h:113
+
int32_t hard_remove
Definition fuse.h:185
+
double attr_timeout
Definition fuse.h:143
+ + +
void * private_data
Definition fuse.h:874
+
uid_t uid
Definition fuse.h:865
+
pid_t pid
Definition fuse.h:871
+
struct fuse * fuse
Definition fuse.h:862
+
gid_t gid
Definition fuse.h:868
+
mode_t umask
Definition fuse.h:877
+ + + +
int(* mkdir)(const char *, mode_t)
Definition fuse.h:387
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:437
+
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:379
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:719
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:486
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:589
+
int(* link)(const char *, const char *)
Definition fuse.h:410
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:704
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:798
+
int(* access)(const char *, int)
Definition fuse.h:660
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:497
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:769
+
void(* destroy)(void *private_data)
Definition fuse.h:649
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:516
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:828
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:579
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:621
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:407
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:852
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:509
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:781
+
int(* unlink)(const char *)
Definition fuse.h:390
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:843
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:567
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:672
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:570
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:576
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:371
+
int(* symlink)(const char *, const char *)
Definition fuse.h:396
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:631
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:560
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:427
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:417
+
int(* rmdir)(const char *)
Definition fuse.h:393
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:546
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:818
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:750
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:613
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:573
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:728
+ +
+ + + + diff --git a/doc/html/include_2fuse__common_8h.html b/doc/html/include_2fuse__common_8h.html new file mode 100644 index 0000000..9364ae0 --- /dev/null +++ b/doc/html/include_2fuse__common_8h.html @@ -0,0 +1,1139 @@ + + + + + + + +libfuse: include/fuse_common.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_common.h File Reference
+
+
+
#include <stdbool.h>
+#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + + + + + +

+Data Structures

struct  fuse_file_info
 
struct  fuse_loop_config
 
struct  fuse_conn_info
 
struct  fuse_buf
 
struct  fuse_bufvec
 
struct  libfuse_version
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1 << 0)
 
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
 
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
 
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
 
#define FUSE_CAP_DONT_MASK   (1 << 6)
 
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
 
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
 
#define FUSE_CAP_SPLICE_READ   (1 << 9)
 
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
 
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
 
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
 
#define FUSE_CAP_READDIRPLUS   (1 << 13)
 
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
 
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
 
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
 
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
 
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
 
#define FUSE_CAP_POSIX_ACL   (1 << 19)
 
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
 
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
 
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
 
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
 
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
 
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
 
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
 
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
 
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
 
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
 
#define FUSE_IOCTL_COMPAT   (1 << 0)
 
#define FUSE_BACKING_STACKED_UNDER   (0)
 
+ + + + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
 
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
 
+ + + + + + + + + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
 
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
 
int fuse_daemonize (int foreground)
 
int fuse_version (void)
 
const char * fuse_pkgversion (void)
 
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
 
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
 
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
 
int fuse_set_signal_handlers (struct fuse_session *se)
 
int fuse_set_fail_signal_handlers (struct fuse_session *se)
 
void fuse_remove_signal_handlers (struct fuse_session *se)
 
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 673 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1 << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 336 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1 << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 185 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1 << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 202 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1 << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 289 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1 << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 426 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1 << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 496 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1 << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 222 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1 << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 480 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1 << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 464 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1 << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 214 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1 << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 260 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1 << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 396 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1 << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 413 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1 << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 267 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1 << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 516 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1 << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 360 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1 << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 441 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1 << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 368 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1 << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 508 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1 << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 387 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1 << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 193 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1 << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 297 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1 << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 325 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1 << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 487 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1 << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 238 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1 << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 247 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1 << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 230 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1 << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 345 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 528 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 839 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 808 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_infoconn 
)
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 412 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvecdst,
struct fuse_bufvecsrc,
enum fuse_buf_copy_flags flags 
)
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvecbufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_argsargs)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 459 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5234 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 1907 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 180 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 158 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 138 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5229 of file fuse.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__common_8h_source.html b/doc/html/include_2fuse__common_8h_source.html new file mode 100644 index 0000000..5b856e4 --- /dev/null +++ b/doc/html/include_2fuse__common_8h_source.html @@ -0,0 +1,481 @@ + + + + + + + +libfuse: include/fuse_common.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file COPYING.LIB.
+
6*/
+
7
+
10#include <stdbool.h>
+
11#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
12#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
13#endif
+
14
+
15#ifndef FUSE_COMMON_H_
+
16#define FUSE_COMMON_H_
+
17
+
18#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
19#include "fuse_config.h"
+
20#endif
+
21
+
22#include "libfuse_config.h"
+
23
+
24#include "fuse_opt.h"
+
25#include "fuse_log.h"
+
26#include <stdint.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#ifdef HAVE_STATIC_ASSERT
+
34#define fuse_static_assert(condition, message) static_assert(condition, message)
+
35#else
+
36#define fuse_static_assert(condition, message)
+
37#endif
+
38
+
39#ifdef __cplusplus
+
40extern "C" {
+
41#endif
+
42
+
56struct fuse_file_info {
+
58 int32_t flags;
+
59
+
66 uint32_t writepage : 1;
+
67
+
69 uint32_t direct_io : 1;
+
70
+
75 uint32_t keep_cache : 1;
+
76
+
80 uint32_t flush : 1;
+
81
+
84 uint32_t nonseekable : 1;
+
85
+
86 /* Indicates that flock locks for this file should be
+
87 released. If set, lock_owner shall contain a valid value.
+
88 May only be set in ->release(). */
+
89 uint32_t flock_release : 1;
+
90
+
95 uint32_t cache_readdir : 1;
+
96
+
99 uint32_t noflush : 1;
+
100
+
103 uint32_t parallel_direct_writes : 1;
+
104
+
106 uint32_t padding : 23;
+
107 uint32_t padding2 : 32;
+
108 uint32_t padding3 : 32;
+
109
+
113 uint64_t fh;
+
114
+
116 uint64_t lock_owner;
+
117
+
120 uint32_t poll_events;
+
121
+
125 int32_t backing_id;
+
126
+
128 uint64_t compat_flags;
+
129
+
130 uint64_t reserved[2];
+
131};
+
132fuse_static_assert(sizeof(struct fuse_file_info) == 64,
+
133 "fuse_file_info size mismatch");
+
134
+
145#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
146struct fuse_loop_config_v1; /* forward declaration */
+
147struct fuse_loop_config {
+
148#else
+
149struct fuse_loop_config_v1 {
+
150#endif
+
155 int clone_fd;
+
156
+
167 unsigned int max_idle_threads;
+
168};
+
169
+
170
+
171/**************************************************************************
+
172 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
173 **************************************************************************/
+
174
+
185#define FUSE_CAP_ASYNC_READ (1 << 0)
+
186
+
193#define FUSE_CAP_POSIX_LOCKS (1 << 1)
+
194
+
202#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
+
203
+
214#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
+
215
+
222#define FUSE_CAP_DONT_MASK (1 << 6)
+
223
+
230#define FUSE_CAP_SPLICE_WRITE (1 << 7)
+
231
+
238#define FUSE_CAP_SPLICE_MOVE (1 << 8)
+
239
+
247#define FUSE_CAP_SPLICE_READ (1 << 9)
+
248
+
260#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
+
261
+
267#define FUSE_CAP_IOCTL_DIR (1 << 11)
+
268
+
289#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12)
+
290
+
297#define FUSE_CAP_READDIRPLUS (1 << 13)
+
298
+
325#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14)
+
326
+
336#define FUSE_CAP_ASYNC_DIO (1 << 15)
+
337
+
345#define FUSE_CAP_WRITEBACK_CACHE (1 << 16)
+
346
+
360#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17)
+
361
+
368#define FUSE_CAP_PARALLEL_DIROPS (1 << 18)
+
369
+
387#define FUSE_CAP_POSIX_ACL (1 << 19)
+
388
+
396#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20)
+
397
+
413#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21)
+
414
+
426#define FUSE_CAP_CACHE_SYMLINKS (1 << 23)
+
427
+
441#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
+
442
+
464#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25)
+
465
+
480#define FUSE_CAP_EXPIRE_ONLY (1 << 26)
+
481
+
487#define FUSE_CAP_SETXATTR_EXT (1 << 27)
+
488
+
496#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1 << 28)
+
497
+
508#define FUSE_CAP_PASSTHROUGH (1 << 29)
+
509
+
516#define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30)
+
517
+
528#define FUSE_IOCTL_COMPAT (1 << 0)
+
529#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
530#define FUSE_IOCTL_RETRY (1 << 2)
+
531#define FUSE_IOCTL_DIR (1 << 4)
+
532
+
533#define FUSE_IOCTL_MAX_IOV 256
+
534
+
546struct fuse_conn_info {
+
550 uint32_t proto_major;
+
551
+
555 uint32_t proto_minor;
+
556
+
560 uint32_t max_write;
+
561
+
574 uint32_t max_read;
+
575
+
579 uint32_t max_readahead;
+
580
+
586 uint32_t capable;
+
587
+
598 uint32_t want;
+
599
+
628 uint32_t max_background;
+
629
+
638 uint32_t congestion_threshold;
+
639
+
655 uint32_t time_gran;
+
656
+
673#define FUSE_BACKING_STACKED_UNDER (0)
+
674#define FUSE_BACKING_STACKED_OVER (1)
+
675 uint32_t max_backing_stack_depth;
+
676
+
685 uint32_t no_interrupt : 1;
+
686
+
687 /* reserved bits for future use */
+
688 uint32_t padding : 31;
+
689
+
694 uint64_t capable_ext;
+
695
+
704 uint64_t want_ext;
+
705
+
709 uint32_t reserved[16];
+
710};
+
711fuse_static_assert(sizeof(struct fuse_conn_info) == 128,
+
712 "Size of struct fuse_conn_info must be 128 bytes");
+
713
+
714struct fuse_session;
+
715struct fuse_pollhandle;
+
716struct fuse_conn_info_opts;
+
717
+
760struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
761
+
769void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
770 struct fuse_conn_info *conn);
+
771
+
778int fuse_daemonize(int foreground);
+
779
+
785int fuse_version(void);
+
786
+
792const char *fuse_pkgversion(void);
+
793
+
799void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
800
+
801/* ----------------------------------------------------------- *
+
802 * Data buffer *
+
803 * ----------------------------------------------------------- */
+
804
+
+ +
815 FUSE_BUF_IS_FD = (1 << 1),
+
816
+ +
825
+
833 FUSE_BUF_FD_RETRY = (1 << 3)
+ +
+
835
+
+ + +
850
+ +
858
+ +
867
+ + +
+
877
+
884struct fuse_buf {
+
888 size_t size;
+
889
+
893 enum fuse_buf_flags flags;
+
894
+
900 void *mem;
+
901
+
907 int fd;
+
908
+
914 off_t pos;
+
915
+
922 size_t mem_size;
+
923};
+
924
+
933struct fuse_bufvec {
+
937 size_t count;
+
938
+
942 size_t idx;
+
943
+
947 size_t off;
+
948
+
952 struct fuse_buf buf[1];
+
953};
+
954
+
959struct libfuse_version
+
960{
+
961 uint32_t major;
+
962 uint32_t minor;
+
963 uint32_t hotfix;
+
964 uint32_t padding;
+
965};
+
966
+
967/* Initialize bufvec with a single buffer of given size */
+
968#define FUSE_BUFVEC_INIT(size__) \
+
969 ((struct fuse_bufvec) { \
+
970 /* .count= */ 1, \
+
971 /* .idx = */ 0, \
+
972 /* .off = */ 0, \
+
973 /* .buf = */ { /* [0] = */ { \
+
974 /* .size = */ (size__), \
+
975 /* .flags = */ (enum fuse_buf_flags) 0, \
+
976 /* .mem = */ NULL, \
+
977 /* .fd = */ -1, \
+
978 /* .pos = */ 0, \
+
979 /* .mem_size = */ 0, \
+
980 } } \
+
981 } )
+
982
+
989size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
990
+
999ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1000 enum fuse_buf_copy_flags flags);
+
1001
+
1002/* ----------------------------------------------------------- *
+
1003 * Signal handling *
+
1004 * ----------------------------------------------------------- */
+
1005
+
1021int fuse_set_signal_handlers(struct fuse_session *se);
+
1022
+
1038int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1039
+
1051void fuse_remove_signal_handlers(struct fuse_session *se);
+
1052
+
1058#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1064struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1065
+
1069void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1070
+
1074void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1075 unsigned int value);
+
1076
+
1080void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1081 unsigned int value);
+
1082
+
1086void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1087 unsigned int value);
+
1088
+
1095void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1096 struct fuse_loop_config_v1 *v1_conf);
+
1097#endif
+
1098
+
1099
+
1100static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn,
+
1101 uint64_t flag)
+
1102{
+
1103 if (conn->capable_ext & flag) {
+
1104 conn->want_ext |= flag;
+
1105 return true;
+
1106 }
+
1107 return false;
+
1108}
+
1109
+
1110static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn,
+
1111 uint64_t flag)
+
1112{
+
1113 conn->want_ext &= ~flag;
+
1114}
+
1115
+
1116static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn,
+
1117 uint64_t flag)
+
1118{
+
1119 return conn->capable_ext & flag ? true : false;
+
1120}
+
1121
+
1122/* ----------------------------------------------------------- *
+
1123 * Compatibility stuff *
+
1124 * ----------------------------------------------------------- */
+
1125
+
1126#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1127# error only API version 30 or greater is supported
+
1128#endif
+
1129
+
1130#ifdef __cplusplus
+
1131}
+
1132#endif
+
1133
+
1134
+
1135/*
+
1136 * This interface uses 64 bit off_t.
+
1137 *
+
1138 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1139 */
+
1140
+
1141#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1142_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1143#else
+
1144struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1145 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1146#endif
+
1147
+
1148#endif /* FUSE_COMMON_H_ */
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:459
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5229
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ + + + + + +
uint64_t capable_ext
+
uint64_t want_ext
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t padding
+
uint32_t noflush
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + +
+ + + + diff --git a/doc/html/include_2fuse__kernel_8h_source.html b/doc/html/include_2fuse__kernel_8h_source.html new file mode 100644 index 0000000..86ffd1d --- /dev/null +++ b/doc/html/include_2fuse__kernel_8h_source.html @@ -0,0 +1,1093 @@ + + + + + + + +libfuse: include/fuse_kernel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_kernel.h
+
+
+
1/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+
2/*
+
3 This file defines the kernel interface of FUSE
+
4 Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
+
5
+
6 This program can be distributed under the terms of the GNU GPL.
+
7 See the file COPYING.
+
8
+
9 This -- and only this -- header file may also be distributed under
+
10 the terms of the BSD Licence as follows:
+
11
+
12 Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
13
+
14 Redistribution and use in source and binary forms, with or without
+
15 modification, are permitted provided that the following conditions
+
16 are met:
+
17 1. Redistributions of source code must retain the above copyright
+
18 notice, this list of conditions and the following disclaimer.
+
19 2. Redistributions in binary form must reproduce the above copyright
+
20 notice, this list of conditions and the following disclaimer in the
+
21 documentation and/or other materials provided with the distribution.
+
22
+
23 THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+
24 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
26 ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
29 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
32 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
33 SUCH DAMAGE.
+
34*/
+
35
+
36/*
+
37 * This file defines the kernel interface of FUSE
+
38 *
+
39 * Protocol changelog:
+
40 *
+
41 * 7.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 */
+
221
+
222#ifndef _LINUX_FUSE_H
+
223#define _LINUX_FUSE_H
+
224
+
225#ifdef __KERNEL__
+
226#include <linux/types.h>
+
227#else
+
228#include <stdint.h>
+
229#endif
+
230
+
231/*
+
232 * Version negotiation:
+
233 *
+
234 * Both the kernel and userspace send the version they support in the
+
235 * INIT request and reply respectively.
+
236 *
+
237 * If the major versions match then both shall use the smallest
+
238 * of the two minor versions for communication.
+
239 *
+
240 * If the kernel supports a larger major version, then userspace shall
+
241 * reply with the major version it supports, ignore the rest of the
+
242 * INIT message and expect a new INIT message from the kernel with a
+
243 * matching major version.
+
244 *
+
245 * If the library supports a larger major version, then it shall fall
+
246 * back to the major protocol version sent by the kernel for
+
247 * communication and reply with that major version (and an arbitrary
+
248 * supported minor version).
+
249 */
+
250
+
252#define FUSE_KERNEL_VERSION 7
+
253
+
255#define FUSE_KERNEL_MINOR_VERSION 40
+
256
+
258#define FUSE_ROOT_ID 1
+
259
+
260/* Make sure all structures are padded to 64bit boundary, so 32bit
+
261 userspace works under 64bit kernels */
+
262
+
263struct fuse_attr {
+
264 uint64_t ino;
+
265 uint64_t size;
+
266 uint64_t blocks;
+
267 uint64_t atime;
+
268 uint64_t mtime;
+
269 uint64_t ctime;
+
270 uint32_t atimensec;
+
271 uint32_t mtimensec;
+
272 uint32_t ctimensec;
+
273 uint32_t mode;
+
274 uint32_t nlink;
+
275 uint32_t uid;
+
276 uint32_t gid;
+
277 uint32_t rdev;
+
278 uint32_t blksize;
+
279 uint32_t flags;
+
280};
+
281
+
282/*
+
283 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
284 * Linux.
+
285 */
+
286struct fuse_sx_time {
+
287 int64_t tv_sec;
+
288 uint32_t tv_nsec;
+
289 int32_t __reserved;
+
290};
+
291
+
292struct fuse_statx {
+
293 uint32_t mask;
+
294 uint32_t blksize;
+
295 uint64_t attributes;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint16_t mode;
+
300 uint16_t __spare0[1];
+
301 uint64_t ino;
+
302 uint64_t size;
+
303 uint64_t blocks;
+
304 uint64_t attributes_mask;
+
305 struct fuse_sx_time atime;
+
306 struct fuse_sx_time btime;
+
307 struct fuse_sx_time ctime;
+
308 struct fuse_sx_time mtime;
+
309 uint32_t rdev_major;
+
310 uint32_t rdev_minor;
+
311 uint32_t dev_major;
+
312 uint32_t dev_minor;
+
313 uint64_t __spare2[14];
+
314};
+
315
+
316struct fuse_kstatfs {
+
317 uint64_t blocks;
+
318 uint64_t bfree;
+
319 uint64_t bavail;
+
320 uint64_t files;
+
321 uint64_t ffree;
+
322 uint32_t bsize;
+
323 uint32_t namelen;
+
324 uint32_t frsize;
+
325 uint32_t padding;
+
326 uint32_t spare[6];
+
327};
+
328
+
329struct fuse_file_lock {
+
330 uint64_t start;
+
331 uint64_t end;
+
332 uint32_t type;
+
333 uint32_t pid; /* tgid */
+
334};
+
335
+
339#define FATTR_MODE (1 << 0)
+
340#define FATTR_UID (1 << 1)
+
341#define FATTR_GID (1 << 2)
+
342#define FATTR_SIZE (1 << 3)
+
343#define FATTR_ATIME (1 << 4)
+
344#define FATTR_MTIME (1 << 5)
+
345#define FATTR_FH (1 << 6)
+
346#define FATTR_ATIME_NOW (1 << 7)
+
347#define FATTR_MTIME_NOW (1 << 8)
+
348#define FATTR_LOCKOWNER (1 << 9)
+
349#define FATTR_CTIME (1 << 10)
+
350#define FATTR_KILL_SUIDGID (1 << 11)
+
351
+
364#define FOPEN_DIRECT_IO (1 << 0)
+
365#define FOPEN_KEEP_CACHE (1 << 1)
+
366#define FOPEN_NONSEEKABLE (1 << 2)
+
367#define FOPEN_CACHE_DIR (1 << 3)
+
368#define FOPEN_STREAM (1 << 4)
+
369#define FOPEN_NOFLUSH (1 << 5)
+
370#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
371#define FOPEN_PASSTHROUGH (1 << 7)
+
372
+
425#define FUSE_ASYNC_READ (1 << 0)
+
426#define FUSE_POSIX_LOCKS (1 << 1)
+
427#define FUSE_FILE_OPS (1 << 2)
+
428#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
429#define FUSE_EXPORT_SUPPORT (1 << 4)
+
430#define FUSE_BIG_WRITES (1 << 5)
+
431#define FUSE_DONT_MASK (1 << 6)
+
432#define FUSE_SPLICE_WRITE (1 << 7)
+
433#define FUSE_SPLICE_MOVE (1 << 8)
+
434#define FUSE_SPLICE_READ (1 << 9)
+
435#define FUSE_FLOCK_LOCKS (1 << 10)
+
436#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
437#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
438#define FUSE_DO_READDIRPLUS (1 << 13)
+
439#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
440#define FUSE_ASYNC_DIO (1 << 15)
+
441#define FUSE_WRITEBACK_CACHE (1 << 16)
+
442#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
443#define FUSE_PARALLEL_DIROPS (1 << 18)
+
444#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
445#define FUSE_POSIX_ACL (1 << 20)
+
446#define FUSE_ABORT_ERROR (1 << 21)
+
447#define FUSE_MAX_PAGES (1 << 22)
+
448#define FUSE_CACHE_SYMLINKS (1 << 23)
+
449#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
450#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
451#define FUSE_MAP_ALIGNMENT (1 << 26)
+
452#define FUSE_SUBMOUNTS (1 << 27)
+
453#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
454#define FUSE_SETXATTR_EXT (1 << 29)
+
455#define FUSE_INIT_EXT (1 << 30)
+
456#define FUSE_INIT_RESERVED (1 << 31)
+
457/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
458#define FUSE_SECURITY_CTX (1ULL << 32)
+
459#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
460#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
461#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
462#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
463#define FUSE_PASSTHROUGH (1ULL << 37)
+
464#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
465#define FUSE_HAS_RESEND (1ULL << 39)
+
466
+
467/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
468#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
469
+
475#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
476
+
480#define FUSE_RELEASE_FLUSH (1 << 0)
+
481#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
482
+
486#define FUSE_GETATTR_FH (1 << 0)
+
487
+
491#define FUSE_LK_FLOCK (1 << 0)
+
492
+
500#define FUSE_WRITE_CACHE (1 << 0)
+
501#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
502#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
503
+
504/* Obsolete alias; this flag implies killing suid/sgid only. */
+
505#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
506
+
510#define FUSE_READ_LOCKOWNER (1 << 1)
+
511
+
524#define FUSE_IOCTL_COMPAT (1 << 0)
+
525#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
526#define FUSE_IOCTL_RETRY (1 << 2)
+
527#define FUSE_IOCTL_32BIT (1 << 3)
+
528#define FUSE_IOCTL_DIR (1 << 4)
+
529#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
530
+
531#define FUSE_IOCTL_MAX_IOV 256
+
532
+
538#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
539
+
545#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
546
+
553#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
554#define FUSE_ATTR_DAX (1 << 1)
+
555
+
560#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
561
+
566#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
567
+
572#define FUSE_EXPIRE_ONLY (1 << 0)
+
573
+
579enum fuse_ext_type {
+
580 /* Types 0..31 are reserved for fuse_secctx_header */
+
581 FUSE_MAX_NR_SECCTX = 31,
+
582 FUSE_EXT_GROUPS = 32,
+
583};
+
584
+
585enum fuse_opcode {
+
586 FUSE_LOOKUP = 1,
+
587 FUSE_FORGET = 2, /* no reply */
+
588 FUSE_GETATTR = 3,
+
589 FUSE_SETATTR = 4,
+
590 FUSE_READLINK = 5,
+
591 FUSE_SYMLINK = 6,
+
592 FUSE_MKNOD = 8,
+
593 FUSE_MKDIR = 9,
+
594 FUSE_UNLINK = 10,
+
595 FUSE_RMDIR = 11,
+
596 FUSE_RENAME = 12,
+
597 FUSE_LINK = 13,
+
598 FUSE_OPEN = 14,
+
599 FUSE_READ = 15,
+
600 FUSE_WRITE = 16,
+
601 FUSE_STATFS = 17,
+
602 FUSE_RELEASE = 18,
+
603 FUSE_FSYNC = 20,
+
604 FUSE_SETXATTR = 21,
+
605 FUSE_GETXATTR = 22,
+
606 FUSE_LISTXATTR = 23,
+
607 FUSE_REMOVEXATTR = 24,
+
608 FUSE_FLUSH = 25,
+
609 FUSE_INIT = 26,
+
610 FUSE_OPENDIR = 27,
+
611 FUSE_READDIR = 28,
+
612 FUSE_RELEASEDIR = 29,
+
613 FUSE_FSYNCDIR = 30,
+
614 FUSE_GETLK = 31,
+
615 FUSE_SETLK = 32,
+
616 FUSE_SETLKW = 33,
+
617 FUSE_ACCESS = 34,
+
618 FUSE_CREATE = 35,
+
619 FUSE_INTERRUPT = 36,
+
620 FUSE_BMAP = 37,
+
621 FUSE_DESTROY = 38,
+
622 FUSE_IOCTL = 39,
+
623 FUSE_POLL = 40,
+
624 FUSE_NOTIFY_REPLY = 41,
+
625 FUSE_BATCH_FORGET = 42,
+
626 FUSE_FALLOCATE = 43,
+
627 FUSE_READDIRPLUS = 44,
+
628 FUSE_RENAME2 = 45,
+
629 FUSE_LSEEK = 46,
+
630 FUSE_COPY_FILE_RANGE = 47,
+
631 FUSE_SETUPMAPPING = 48,
+
632 FUSE_REMOVEMAPPING = 49,
+
633 FUSE_SYNCFS = 50,
+
634 FUSE_TMPFILE = 51,
+
635 FUSE_STATX = 52,
+
636
+
637 /* CUSE specific operations */
+
638 CUSE_INIT = 4096,
+
639
+
640 /* Reserved opcodes: helpful to detect structure endian-ness */
+
641 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
642 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
643};
+
644
+
645enum fuse_notify_code {
+
646 FUSE_NOTIFY_POLL = 1,
+
647 FUSE_NOTIFY_INVAL_INODE = 2,
+
648 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
649 FUSE_NOTIFY_STORE = 4,
+
650 FUSE_NOTIFY_RETRIEVE = 5,
+
651 FUSE_NOTIFY_DELETE = 6,
+
652 FUSE_NOTIFY_RESEND = 7,
+
653 FUSE_NOTIFY_CODE_MAX,
+
654};
+
655
+
656/* The read buffer is required to be at least 8k, but may be much larger */
+
657#define FUSE_MIN_READ_BUFFER 8192
+
658
+
659#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
660
+
661struct fuse_entry_out {
+
662 uint64_t nodeid; /* Inode ID */
+
663 uint64_t generation; /* Inode generation: nodeid:gen must
+
664 be unique for the fs's lifetime */
+
665 uint64_t entry_valid; /* Cache timeout for the name */
+
666 uint64_t attr_valid; /* Cache timeout for the attributes */
+
667 uint32_t entry_valid_nsec;
+
668 uint32_t attr_valid_nsec;
+
669 struct fuse_attr attr;
+
670};
+
671
+
672struct fuse_forget_in {
+
673 uint64_t nlookup;
+
674};
+
675
+
676struct fuse_forget_one {
+
677 uint64_t nodeid;
+
678 uint64_t nlookup;
+
679};
+
680
+
681struct fuse_batch_forget_in {
+
682 uint32_t count;
+
683 uint32_t dummy;
+
684};
+
685
+
686struct fuse_getattr_in {
+
687 uint32_t getattr_flags;
+
688 uint32_t dummy;
+
689 uint64_t fh;
+
690};
+
691
+
692#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
693
+
694struct fuse_attr_out {
+
695 uint64_t attr_valid; /* Cache timeout for the attributes */
+
696 uint32_t attr_valid_nsec;
+
697 uint32_t dummy;
+
698 struct fuse_attr attr;
+
699};
+
700
+
701struct fuse_statx_in {
+
702 uint32_t getattr_flags;
+
703 uint32_t reserved;
+
704 uint64_t fh;
+
705 uint32_t sx_flags;
+
706 uint32_t sx_mask;
+
707};
+
708
+
709struct fuse_statx_out {
+
710 uint64_t attr_valid; /* Cache timeout for the attributes */
+
711 uint32_t attr_valid_nsec;
+
712 uint32_t flags;
+
713 uint64_t spare[2];
+
714 struct fuse_statx stat;
+
715};
+
716
+
717#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
718
+
719struct fuse_mknod_in {
+
720 uint32_t mode;
+
721 uint32_t rdev;
+
722 uint32_t umask;
+
723 uint32_t padding;
+
724};
+
725
+
726struct fuse_mkdir_in {
+
727 uint32_t mode;
+
728 uint32_t umask;
+
729};
+
730
+
731struct fuse_rename_in {
+
732 uint64_t newdir;
+
733};
+
734
+
735struct fuse_rename2_in {
+
736 uint64_t newdir;
+
737 uint32_t flags;
+
738 uint32_t padding;
+
739};
+
740
+
741struct fuse_link_in {
+
742 uint64_t oldnodeid;
+
743};
+
744
+
745struct fuse_setattr_in {
+
746 uint32_t valid;
+
747 uint32_t padding;
+
748 uint64_t fh;
+
749 uint64_t size;
+
750 uint64_t lock_owner;
+
751 uint64_t atime;
+
752 uint64_t mtime;
+
753 uint64_t ctime;
+
754 uint32_t atimensec;
+
755 uint32_t mtimensec;
+
756 uint32_t ctimensec;
+
757 uint32_t mode;
+
758 uint32_t unused4;
+
759 uint32_t uid;
+
760 uint32_t gid;
+
761 uint32_t unused5;
+
762};
+
763
+
764struct fuse_open_in {
+
765 uint32_t flags;
+
766 uint32_t open_flags; /* FUSE_OPEN_... */
+
767};
+
768
+
769struct fuse_create_in {
+
770 uint32_t flags;
+
771 uint32_t mode;
+
772 uint32_t umask;
+
773 uint32_t open_flags; /* FUSE_OPEN_... */
+
774};
+
775
+
776struct fuse_open_out {
+
777 uint64_t fh;
+
778 uint32_t open_flags;
+
779 int32_t backing_id;
+
780};
+
781
+
782struct fuse_release_in {
+
783 uint64_t fh;
+
784 uint32_t flags;
+
785 uint32_t release_flags;
+
786 uint64_t lock_owner;
+
787};
+
788
+
789struct fuse_flush_in {
+
790 uint64_t fh;
+
791 uint32_t unused;
+
792 uint32_t padding;
+
793 uint64_t lock_owner;
+
794};
+
795
+
796struct fuse_read_in {
+
797 uint64_t fh;
+
798 uint64_t offset;
+
799 uint32_t size;
+
800 uint32_t read_flags;
+
801 uint64_t lock_owner;
+
802 uint32_t flags;
+
803 uint32_t padding;
+
804};
+
805
+
806#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
807
+
808struct fuse_write_in {
+
809 uint64_t fh;
+
810 uint64_t offset;
+
811 uint32_t size;
+
812 uint32_t write_flags;
+
813 uint64_t lock_owner;
+
814 uint32_t flags;
+
815 uint32_t padding;
+
816};
+
817
+
818struct fuse_write_out {
+
819 uint32_t size;
+
820 uint32_t padding;
+
821};
+
822
+
823#define FUSE_COMPAT_STATFS_SIZE 48
+
824
+
825struct fuse_statfs_out {
+
826 struct fuse_kstatfs st;
+
827};
+
828
+
829struct fuse_fsync_in {
+
830 uint64_t fh;
+
831 uint32_t fsync_flags;
+
832 uint32_t padding;
+
833};
+
834
+
835#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
836
+
837struct fuse_setxattr_in {
+
838 uint32_t size;
+
839 uint32_t flags;
+
840 uint32_t setxattr_flags;
+
841 uint32_t padding;
+
842};
+
843
+
844struct fuse_getxattr_in {
+
845 uint32_t size;
+
846 uint32_t padding;
+
847};
+
848
+
849struct fuse_getxattr_out {
+
850 uint32_t size;
+
851 uint32_t padding;
+
852};
+
853
+
854struct fuse_lk_in {
+
855 uint64_t fh;
+
856 uint64_t owner;
+
857 struct fuse_file_lock lk;
+
858 uint32_t lk_flags;
+
859 uint32_t padding;
+
860};
+
861
+
862struct fuse_lk_out {
+
863 struct fuse_file_lock lk;
+
864};
+
865
+
866struct fuse_access_in {
+
867 uint32_t mask;
+
868 uint32_t padding;
+
869};
+
870
+
871struct fuse_init_in {
+
872 uint32_t major;
+
873 uint32_t minor;
+
874 uint32_t max_readahead;
+
875 uint32_t flags;
+
876 uint32_t flags2;
+
877 uint32_t unused[11];
+
878};
+
879
+
880#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
881#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
882
+
883struct fuse_init_out {
+
884 uint32_t major;
+
885 uint32_t minor;
+
886 uint32_t max_readahead;
+
887 uint32_t flags;
+
888 uint16_t max_background;
+
889 uint16_t congestion_threshold;
+
890 uint32_t max_write;
+
891 uint32_t time_gran;
+
892 uint16_t max_pages;
+
893 uint16_t map_alignment;
+
894 uint32_t flags2;
+
895 uint32_t max_stack_depth;
+
896 uint32_t unused[6];
+
897};
+
898
+
899#define CUSE_INIT_INFO_MAX 4096
+
900
+
901struct cuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t unused;
+
905 uint32_t flags;
+
906};
+
907
+
908struct cuse_init_out {
+
909 uint32_t major;
+
910 uint32_t minor;
+
911 uint32_t unused;
+
912 uint32_t flags;
+
913 uint32_t max_read;
+
914 uint32_t max_write;
+
915 uint32_t dev_major; /* chardev major */
+
916 uint32_t dev_minor; /* chardev minor */
+
917 uint32_t spare[10];
+
918};
+
919
+
920struct fuse_interrupt_in {
+
921 uint64_t unique;
+
922};
+
923
+
924struct fuse_bmap_in {
+
925 uint64_t block;
+
926 uint32_t blocksize;
+
927 uint32_t padding;
+
928};
+
929
+
930struct fuse_bmap_out {
+
931 uint64_t block;
+
932};
+
933
+
934struct fuse_ioctl_in {
+
935 uint64_t fh;
+
936 uint32_t flags;
+
937 uint32_t cmd;
+
938 uint64_t arg;
+
939 uint32_t in_size;
+
940 uint32_t out_size;
+
941};
+
942
+
943struct fuse_ioctl_iovec {
+
944 uint64_t base;
+
945 uint64_t len;
+
946};
+
947
+
948struct fuse_ioctl_out {
+
949 int32_t result;
+
950 uint32_t flags;
+
951 uint32_t in_iovs;
+
952 uint32_t out_iovs;
+
953};
+
954
+
955struct fuse_poll_in {
+
956 uint64_t fh;
+
957 uint64_t kh;
+
958 uint32_t flags;
+
959 uint32_t events;
+
960};
+
961
+
962struct fuse_poll_out {
+
963 uint32_t revents;
+
964 uint32_t padding;
+
965};
+
966
+
967struct fuse_notify_poll_wakeup_out {
+
968 uint64_t kh;
+
969};
+
970
+
971struct fuse_fallocate_in {
+
972 uint64_t fh;
+
973 uint64_t offset;
+
974 uint64_t length;
+
975 uint32_t mode;
+
976 uint32_t padding;
+
977};
+
978
+
985#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
986
+
987struct fuse_in_header {
+
988 uint32_t len;
+
989 uint32_t opcode;
+
990 uint64_t unique;
+
991 uint64_t nodeid;
+
992 uint32_t uid;
+
993 uint32_t gid;
+
994 uint32_t pid;
+
995 uint16_t total_extlen; /* length of extensions in 8byte units */
+
996 uint16_t padding;
+
997};
+
998
+
999struct fuse_out_header {
+
1000 uint32_t len;
+
1001 int32_t error;
+
1002 uint64_t unique;
+
1003};
+
1004
+
1005struct fuse_dirent {
+
1006 uint64_t ino;
+
1007 uint64_t off;
+
1008 uint32_t namelen;
+
1009 uint32_t type;
+
1010 char name[];
+
1011};
+
1012
+
1013/* Align variable length records to 64bit boundary */
+
1014#define FUSE_REC_ALIGN(x) \
+
1015 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1016
+
1017#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1018#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1019#define FUSE_DIRENT_SIZE(d) \
+
1020 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1021
+
1022struct fuse_direntplus {
+
1023 struct fuse_entry_out entry_out;
+
1024 struct fuse_dirent dirent;
+
1025};
+
1026
+
1027#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1028 offsetof(struct fuse_direntplus, dirent.name)
+
1029#define FUSE_DIRENTPLUS_SIZE(d) \
+
1030 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1031
+
1032struct fuse_notify_inval_inode_out {
+
1033 uint64_t ino;
+
1034 int64_t off;
+
1035 int64_t len;
+
1036};
+
1037
+
1038struct fuse_notify_inval_entry_out {
+
1039 uint64_t parent;
+
1040 uint32_t namelen;
+
1041 uint32_t flags;
+
1042};
+
1043
+
1044struct fuse_notify_delete_out {
+
1045 uint64_t parent;
+
1046 uint64_t child;
+
1047 uint32_t namelen;
+
1048 uint32_t padding;
+
1049};
+
1050
+
1051struct fuse_notify_store_out {
+
1052 uint64_t nodeid;
+
1053 uint64_t offset;
+
1054 uint32_t size;
+
1055 uint32_t padding;
+
1056};
+
1057
+
1058struct fuse_notify_retrieve_out {
+
1059 uint64_t notify_unique;
+
1060 uint64_t nodeid;
+
1061 uint64_t offset;
+
1062 uint32_t size;
+
1063 uint32_t padding;
+
1064};
+
1065
+
1066/* Matches the size of fuse_write_in */
+
1067struct fuse_notify_retrieve_in {
+
1068 uint64_t dummy1;
+
1069 uint64_t offset;
+
1070 uint32_t size;
+
1071 uint32_t dummy2;
+
1072 uint64_t dummy3;
+
1073 uint64_t dummy4;
+
1074};
+
1075
+
1076struct fuse_backing_map {
+
1077 int32_t fd;
+
1078 uint32_t flags;
+
1079 uint64_t padding;
+
1080};
+
1081
+
1082/* Device ioctls: */
+
1083#define FUSE_DEV_IOC_MAGIC 229
+
1084#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1085#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1086 struct fuse_backing_map)
+
1087#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1088
+
1089struct fuse_lseek_in {
+
1090 uint64_t fh;
+
1091 uint64_t offset;
+
1092 uint32_t whence;
+
1093 uint32_t padding;
+
1094};
+
1095
+
1096struct fuse_lseek_out {
+
1097 uint64_t offset;
+
1098};
+
1099
+
1100struct fuse_copy_file_range_in {
+
1101 uint64_t fh_in;
+
1102 uint64_t off_in;
+
1103 uint64_t nodeid_out;
+
1104 uint64_t fh_out;
+
1105 uint64_t off_out;
+
1106 uint64_t len;
+
1107 uint64_t flags;
+
1108};
+
1109
+
1110#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1111#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1112struct fuse_setupmapping_in {
+
1113 /* An already open handle */
+
1114 uint64_t fh;
+
1115 /* Offset into the file to start the mapping */
+
1116 uint64_t foffset;
+
1117 /* Length of mapping required */
+
1118 uint64_t len;
+
1119 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1120 uint64_t flags;
+
1121 /* Offset in Memory Window */
+
1122 uint64_t moffset;
+
1123};
+
1124
+
1125struct fuse_removemapping_in {
+
1126 /* number of fuse_removemapping_one follows */
+
1127 uint32_t count;
+
1128};
+
1129
+
1130struct fuse_removemapping_one {
+
1131 /* Offset into the dax window start the unmapping */
+
1132 uint64_t moffset;
+
1133 /* Length of mapping required */
+
1134 uint64_t len;
+
1135};
+
1136
+
1137#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1138 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1139
+
1140struct fuse_syncfs_in {
+
1141 uint64_t padding;
+
1142};
+
1143
+
1144/*
+
1145 * For each security context, send fuse_secctx with size of security context
+
1146 * fuse_secctx will be followed by security context name and this in turn
+
1147 * will be followed by actual context label.
+
1148 * fuse_secctx, name, context
+
1149 */
+
1150struct fuse_secctx {
+
1151 uint32_t size;
+
1152 uint32_t padding;
+
1153};
+
1154
+
1155/*
+
1156 * Contains the information about how many fuse_secctx structures are being
+
1157 * sent and what's the total size of all security contexts (including
+
1158 * size of fuse_secctx_header).
+
1159 *
+
1160 */
+
1161struct fuse_secctx_header {
+
1162 uint32_t size;
+
1163 uint32_t nr_secctx;
+
1164};
+
1165
+
1174struct fuse_ext_header {
+
1175 uint32_t size;
+
1176 uint32_t type;
+
1177};
+
1178
+
1184struct fuse_supp_groups {
+
1185 uint32_t nr_groups;
+
1186 uint32_t groups[];
+
1187};
+
1188
+
1189#endif /* _LINUX_FUSE_H */
+ + +
+ + + + diff --git a/doc/html/include_2fuse__log_8h.html b/doc/html/include_2fuse__log_8h.html new file mode 100644 index 0000000..9bfe1f1 --- /dev/null +++ b/doc/html/include_2fuse__log_8h.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: include/fuse_log.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
 
+ + + +

+Enumerations

enum  fuse_log_level
 
+ + + + + + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
 
void fuse_log (enum fuse_log_level level, const char *fmt,...)
 
void fuse_log_enable_syslog (const char *ident, int option, int facility)
 
void fuse_log_close_syslog (void)
 
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
 ... 
)
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +

Definition at line 77 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 93 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_log_enable_syslog (const char * ident,
int option,
int facility 
)
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 86 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 69 of file fuse_log.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__log_8h_source.html b/doc/html/include_2fuse__log_8h_source.html new file mode 100644 index 0000000..b4027ee --- /dev/null +++ b/doc/html/include_2fuse__log_8h_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: include/fuse_log.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...);
+
77
+
83void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
84
+
88void fuse_log_close_syslog(void);
+
89
+
90#ifdef __cplusplus
+
91}
+
92#endif
+
93
+
94#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/include_2fuse__lowlevel_8h.html b/doc/html/include_2fuse__lowlevel_8h.html new file mode 100644 index 0000000..da4cd0b --- /dev/null +++ b/doc/html/include_2fuse__lowlevel_8h.html @@ -0,0 +1,2363 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + + + + + +

+Data Structures

struct  fuse_entry_param
 
struct  fuse_ctx
 
struct  fuse_lowlevel_ops
 
struct  fuse_cmdline_opts
 
+ + + +

+Macros

#define FUSE_ROOT_ID   1
 
+ + + + + + + +

+Typedefs

typedef uint64_t fuse_ino_t
 
typedef struct fuse_req * fuse_req_t
 
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
 
+ + + +

+Enumerations

enum  fuse_notify_entry_flags
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
 
void fuse_reply_none (fuse_req_t req)
 
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
 
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
 
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
 
int fuse_reply_readlink (fuse_req_t req, const char *link)
 
int fuse_passthrough_open (fuse_req_t req, int fd)
 
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
 
int fuse_reply_write (fuse_req_t req, size_t count)
 
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
 
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
 
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
 
int fuse_reply_xattr (fuse_req_t req, size_t count)
 
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
 
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
 
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
 
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
 
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
 
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
 
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
 
int fuse_reply_poll (fuse_req_t req, unsigned revents)
 
int fuse_reply_lseek (fuse_req_t req, off_t off)
 
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
 
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
 
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
 
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
 
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
 
void * fuse_req_userdata (fuse_req_t req)
 
const struct fuse_ctxfuse_req_ctx (fuse_req_t req)
 
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
 
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
 
int fuse_req_interrupted (fuse_req_t req)
 
void fuse_lowlevel_version (void)
 
void fuse_lowlevel_help (void)
 
void fuse_cmdline_help (void)
 
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
 
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
 
int fuse_session_loop (struct fuse_session *se)
 
void fuse_session_exit (struct fuse_session *se)
 
void fuse_session_reset (struct fuse_session *se)
 
int fuse_session_exited (struct fuse_session *se)
 
void fuse_session_unmount (struct fuse_session *se)
 
void fuse_session_destroy (struct fuse_session *se)
 
int fuse_session_fd (struct fuse_session *se)
 
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
 
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
 
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1948 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 148 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off 
)
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 290 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_parame,
off_t off 
)
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 380 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 2941 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen 
)
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2503 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 2490 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen 
)
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2484 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len 
)
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2416 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 2399 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie 
)
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2609 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 2529 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 2934 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_argsargs,
struct fuse_cmdline_optsopts 
)
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd 
)
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 484 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout 
)
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 464 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx 
)
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 977 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size 
)
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 528 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_parame,
const struct fuse_file_infofi 
)
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 448 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvecbufv,
enum fuse_buf_copy_flags flags 
)
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 916 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_parame 
)
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 432 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err 
)
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 335 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size 
)
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1075 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count 
)
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1096 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count 
)
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1005 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count 
)
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 269 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock 
)
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 960 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off 
)
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1130 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 340 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_infofi 
)
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 509 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents 
)
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1120 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link 
)
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 479 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf 
)
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 938 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count 
)
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 518 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count 
)
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 950 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 2659 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] 
)
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 3533 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data 
)
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 2664 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 2677 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 2654 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 2951 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loops to terminate on the next opportunity. If this function is called by a thread that is not a FUSE worker thread, the next opportunity will be when FUSE a request is received (which may be far in the future if the filesystem is not currently being used by any clients). One way to avoid this delay is to afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE will cause the main thread to wake-up but otherwise be ignored).

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 3454 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint 
)
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 3399 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_bufbuf 
)
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 2771 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_bufbuf 
)
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 3218 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3459 of file fuse_lowlevel.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__lowlevel_8h_source.html b/doc/html/include_2fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..eac942c --- /dev/null +++ b/doc/html/include_2fuse__lowlevel_8h_source.html @@ -0,0 +1,673 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
57struct fuse_session;
+
58
+
60struct fuse_entry_param {
+ +
69
+
80 uint64_t generation;
+
81
+
89 struct stat attr;
+
90
+
95 double attr_timeout;
+
96
+
101 double entry_timeout;
+
102};
+
103
+
112struct fuse_ctx {
+
114 uid_t uid;
+
115
+
117 gid_t gid;
+
118
+
120 pid_t pid;
+
121
+
123 mode_t umask;
+
124};
+
125
+
126struct fuse_forget_data {
+
127 fuse_ino_t ino;
+
128 uint64_t nlookup;
+
129};
+
130
+
131struct fuse_custom_io {
+
132 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
133 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
134 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
135 off_t *offout, size_t len,
+
136 unsigned int flags, void *userdata);
+
137 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 int (*clone_fd)(int master_fd);
+
141};
+
142
+
+ +
149 FUSE_LL_INVALIDATE = 0,
+
150 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
151};
+
+
152
+
153/* 'to_set' flags in setattr */
+
154#define FUSE_SET_ATTR_MODE (1 << 0)
+
155#define FUSE_SET_ATTR_UID (1 << 1)
+
156#define FUSE_SET_ATTR_GID (1 << 2)
+
157#define FUSE_SET_ATTR_SIZE (1 << 3)
+
158#define FUSE_SET_ATTR_ATIME (1 << 4)
+
159#define FUSE_SET_ATTR_MTIME (1 << 5)
+
160#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
161#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
162#define FUSE_SET_ATTR_FORCE (1 << 9)
+
163#define FUSE_SET_ATTR_CTIME (1 << 10)
+
164#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
165#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
166#define FUSE_SET_ATTR_FILE (1 << 13)
+
167#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
168#define FUSE_SET_ATTR_OPEN (1 << 15)
+
169#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
170#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
171
+
172/* ----------------------------------------------------------- *
+
173 * Request methods and replies *
+
174 * ----------------------------------------------------------- */
+
175
+
206struct fuse_lowlevel_ops {
+
223 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
224
+
236 void (*destroy) (void *userdata);
+
237
+
249 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
250
+
287 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
288
+
308 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
309 struct fuse_file_info *fi);
+
310
+
345 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
346 int to_set, struct fuse_file_info *fi);
+
347
+
358 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
359
+
376 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
377 mode_t mode, dev_t rdev);
+
378
+
391 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
392 mode_t mode);
+
393
+
409 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
410
+
426 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
427
+
440 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
441 const char *name);
+
442
+
472 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
473 fuse_ino_t newparent, const char *newname,
+
474 unsigned int flags);
+
475
+
488 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
489 const char *newname);
+
490
+
554 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
555 struct fuse_file_info *fi);
+
556
+
582 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
583 struct fuse_file_info *fi);
+
584
+
611 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
612 size_t size, off_t off, struct fuse_file_info *fi);
+
613
+
652 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
653 struct fuse_file_info *fi);
+
654
+
680 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
681 struct fuse_file_info *fi);
+
682
+
702 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
703 struct fuse_file_info *fi);
+
704
+
734 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
735 struct fuse_file_info *fi);
+
736
+
780 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
781 struct fuse_file_info *fi);
+
782
+
799 void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+
800 struct fuse_file_info *fi);
+
801
+
824 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
825 struct fuse_file_info *fi);
+
826
+
837 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
838
+
850 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
851 const char *value, size_t size, int flags);
+
852
+
881 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
882 size_t size);
+
883
+
912 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
913
+
929 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
930
+
951 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
952
+
980 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
981 mode_t mode, struct fuse_file_info *fi);
+
982
+
995 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
996 struct fuse_file_info *fi, struct flock *lock);
+
997
+
1020 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1021 struct fuse_file_info *fi,
+
1022 struct flock *lock, int sleep);
+
1023
+
1044 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1045 uint64_t idx);
+
1046
+
1047#if FUSE_USE_VERSION < 35
+
1048 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1049 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1050 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1051#else
+
1080 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1081 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1082 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1083#endif
+
1084
+
1117 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1118 struct fuse_pollhandle *ph);
+
1119
+
1147 void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+
1148 struct fuse_bufvec *bufv, off_t off,
+
1149 struct fuse_file_info *fi);
+
1150
+
1163 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1164 off_t offset, struct fuse_bufvec *bufv);
+
1165
+
1177 void (*forget_multi) (fuse_req_t req, size_t count,
+
1178 struct fuse_forget_data *forgets);
+
1179
+
1195 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1196 struct fuse_file_info *fi, int op);
+
1197
+
1218 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1219 off_t offset, off_t length, struct fuse_file_info *fi);
+
1220
+
1246 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1247 struct fuse_file_info *fi);
+
1248
+
1279 void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in,
+
1280 off_t off_in, struct fuse_file_info *fi_in,
+
1281 fuse_ino_t ino_out, off_t off_out,
+
1282 struct fuse_file_info *fi_out, size_t len,
+
1283 int flags);
+
1284
+
1303 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1304 struct fuse_file_info *fi);
+
1305
+
1306
+
1325 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1326 mode_t mode, struct fuse_file_info *fi);
+
1327
+
1328};
+
1329
+
1351int fuse_reply_err(fuse_req_t req, int err);
+
1352
+
1363void fuse_reply_none(fuse_req_t req);
+
1364
+
1378int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1379
+
1398int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1399 const struct fuse_file_info *fi);
+
1400
+
1412int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1413 double attr_timeout);
+
1414
+
1425int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1426
+
1439int fuse_passthrough_open(fuse_req_t req, int fd);
+
1440int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1441
+
1456int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1457
+
1468int fuse_reply_write(fuse_req_t req, size_t count);
+
1469
+
1481int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1482
+
1526int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1528
+
1540int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1541
+
1552int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1553
+
1564int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1565
+
1576int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1577
+
1588int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1589
+
1590/* ----------------------------------------------------------- *
+
1591 * Filling a buffer in readdir *
+
1592 * ----------------------------------------------------------- */
+
1593
+
1621size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1622 const char *name, const struct stat *stbuf,
+
1623 off_t off);
+
1624
+
1638size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1639 const char *name,
+
1640 const struct fuse_entry_param *e, off_t off);
+
1641
+ +
1658 const struct iovec *in_iov, size_t in_count,
+
1659 const struct iovec *out_iov, size_t out_count);
+
1660
+
1672int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1673
+
1685int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1686 int count);
+
1687
+
1694int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1695
+
1706int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1707
+
1708/* ----------------------------------------------------------- *
+
1709 * Notification *
+
1710 * ----------------------------------------------------------- */
+
1711
+
1719int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1720
+
1744int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1745 off_t off, off_t len);
+
1746
+
1771int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1772 const char *name, size_t namelen);
+
1773
+
1802int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1803 const char *name, size_t namelen);
+
1804
+
1833int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1834 fuse_ino_t parent, fuse_ino_t child,
+
1835 const char *name, size_t namelen);
+
1836
+
1862int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1863 off_t offset, struct fuse_bufvec *bufv,
+ +
1894int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1895 size_t size, off_t offset, void *cookie);
+
1896
+
1897
+
1898/* ----------------------------------------------------------- *
+
1899 * Utility functions *
+
1900 * ----------------------------------------------------------- */
+
1901
+
1908void *fuse_req_userdata(fuse_req_t req);
+
1909
+
1919const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1920
+
1940int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1941
+
1948typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1949
+ +
1962 void *data);
+
1963
+ +
1971
+
1972
+
1973/* ----------------------------------------------------------- *
+
1974 * Inquiry functions *
+
1975 * ----------------------------------------------------------- */
+
1976
+
1980void fuse_lowlevel_version(void);
+
1981
+
1987void fuse_lowlevel_help(void);
+
1988
+
1992void fuse_cmdline_help(void);
+
1993
+
1994/* ----------------------------------------------------------- *
+
1995 * Filesystem setup & teardown *
+
1996 * ----------------------------------------------------------- */
+
1997
+
2003struct fuse_cmdline_opts {
+
2004 int singlethread;
+
2005 int foreground;
+
2006 int debug;
+
2007 int nodefault_subtype;
+
2008 char *mountpoint;
+
2009 int show_version;
+
2010 int show_help;
+
2011 int clone_fd;
+
2012 unsigned int max_idle_threads; /* discouraged, due to thread
+
2013 * destruct overhead */
+
2014
+
2015 /* Added in libfuse-3.12 */
+
2016 unsigned int max_threads;
+
2017};
+
2018
+
2037#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2038int fuse_parse_cmdline(struct fuse_args *args,
+
2039 struct fuse_cmdline_opts *opts);
+
2040#else
+
2041#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2042int fuse_parse_cmdline_30(struct fuse_args *args,
+
2043 struct fuse_cmdline_opts *opts);
+
2044#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2045#else
+
2046int fuse_parse_cmdline_312(struct fuse_args *args,
+
2047 struct fuse_cmdline_opts *opts);
+
2048#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2049#endif
+
2050#endif
+
2051
+
2052/* Do not call this directly, use fuse_session_new() instead */
+
2053struct fuse_session *
+
2054fuse_session_new_versioned(struct fuse_args *args,
+
2055 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2056 struct libfuse_version *version, void *userdata);
+
2057
+
2086static inline struct fuse_session *
+
2087fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2088 size_t op_size, void *userdata)
+
2089{
+
2090 struct libfuse_version version = {
+
2091 .major = FUSE_MAJOR_VERSION,
+
2092 .minor = FUSE_MINOR_VERSION,
+
2093 .hotfix = FUSE_HOTFIX_VERSION,
+
2094 .padding = 0
+
2095 };
+
2096
+
2097 return fuse_session_new_versioned(args, op, op_size, &version,
+
2098 userdata);
+
2099}
+
2100#define fuse_session_new(args, op, op_size, userdata) \
+
2101 fuse_session_new_fn(args, op, op_size, userdata)
+
2102
+
2103/*
+
2104 * This should mostly not be called directly, but instead the
+
2105 * fuse_session_custom_io() should be used.
+
2106 */
+
2107int fuse_session_custom_io_317(struct fuse_session *se,
+
2108 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2109
+
2137#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2138static inline int fuse_session_custom_io(struct fuse_session *se,
+
2139 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2140{
+
2141 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2142}
+
2143#else
+
2144static inline int fuse_session_custom_io(struct fuse_session *se,
+
2145 const struct fuse_custom_io *io, int fd)
+
2146{
+
2147 return fuse_session_custom_io_317(se, io,
+
2148 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2149}
+
2150#endif
+
2151
+
2160int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2161
+
2184int fuse_session_loop(struct fuse_session *se);
+
2185
+
2186#if FUSE_USE_VERSION < 32
+
2187 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2188 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2189#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2190 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2191 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2192#else
+
2193 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2205 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2206 #else
+
2207 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2208 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2209 #endif
+
2210#endif
+
2211
+
2224void fuse_session_exit(struct fuse_session *se);
+
2225
+
2231void fuse_session_reset(struct fuse_session *se);
+
2232
+
2239int fuse_session_exited(struct fuse_session *se);
+
2240
+
2265void fuse_session_unmount(struct fuse_session *se);
+
2266
+
2272void fuse_session_destroy(struct fuse_session *se);
+
2273
+
2274/* ----------------------------------------------------------- *
+
2275 * Custom event loop support *
+
2276 * ----------------------------------------------------------- */
+
2277
+
2292int fuse_session_fd(struct fuse_session *se);
+
2293
+
2302void fuse_session_process_buf(struct fuse_session *se,
+
2303 const struct fuse_buf *buf);
+
2304
+
2316int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2317
+
2318#ifdef __cplusplus
+
2319}
+
2320#endif
+
2321
+
2322#endif /* FUSE_LOWLEVEL_H_ */
+
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ + + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+ +
+ + + + diff --git a/doc/html/include_2fuse__mount__compat_8h_source.html b/doc/html/include_2fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..5741ac8 --- /dev/null +++ b/doc/html/include_2fuse__mount__compat_8h_source.html @@ -0,0 +1,109 @@ + + + + + + + +libfuse: include/fuse_mount_compat.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + + + diff --git a/doc/html/include_2fuse__opt_8h.html b/doc/html/include_2fuse__opt_8h.html new file mode 100644 index 0000000..e4d538d --- /dev/null +++ b/doc/html/include_2fuse__opt_8h.html @@ -0,0 +1,587 @@ + + + + + + + +libfuse: include/fuse_opt.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_opt
 
struct  fuse_args
 
+ + + + + + + + + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)   { templ, -1U, key }
 
#define FUSE_OPT_END   { NULL, 0, 0 }
 
#define FUSE_ARGS_INIT(argc, argv)   { argc, argv, 0 }
 
#define FUSE_OPT_KEY_OPT   -1
 
#define FUSE_OPT_KEY_NONOPT   -2
 
#define FUSE_OPT_KEY_KEEP   -3
 
#define FUSE_OPT_KEY_DISCARD   -4
 
+ + + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
 
+ + + + + + + + + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
 
int fuse_opt_add_opt (char **opts, const char *opt)
 
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
 
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
 
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
 
void fuse_opt_free_args (struct fuse_args *args)
 
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
 
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
 argv 
)   { argc, argv, 0 }
+
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
 key 
)   { templ, -1U, key }
+
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_argsargs,
const char * arg 
)
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt 
)
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt 
)
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + + +
void fuse_opt_free_args (struct fuse_argsargs)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_argsargs,
int pos,
const char * arg 
)
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt 
)
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_argsargs,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc 
)
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + + + diff --git a/doc/html/include_2fuse__opt_8h_source.html b/doc/html/include_2fuse__opt_8h_source.html new file mode 100644 index 0000000..f260207 --- /dev/null +++ b/doc/html/include_2fuse__opt_8h_source.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: include/fuse_opt.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+
117 int allocated;
+
118};
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/index.html b/doc/html/index.html new file mode 100644 index 0000000..c0a42dc --- /dev/null +++ b/doc/html/index.html @@ -0,0 +1,69 @@ + + + + + + + +libfuse: libfuse API documentation + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + +
+
+
libfuse API documentation
+
+
+

FUSE (Filesystem in Userspace) is an interface for userspace programs to export a filesystem to the Linux kernel. The FUSE project consists of two components: the fuse kernel module (maintained in the regular kernel repositories) and the libfuse userspace library. libfuse provides the reference implementation for communicating with the FUSE kernel module.

+

A FUSE file system is typically implemented as a standalone application that links with libfuse. libfuse provides functions to mount the file system, unmount it, read requests from the kernel, and send responses back.

+

+Getting started

+

libfuse offers two APIs: a "high-level", synchronous API, and a "low-level" asynchronous API. In both cases, incoming requests from the kernel are passed to the main program using callbacks. When using the high-level API, the callbacks may work with file names and paths instead of inodes, and processing of a request finishes when the callback function returns. When using the low-level API, the callbacks must work with inodes and responses must be sent explicitly using a separate set of API functions.

+

The high-level API that is primarily specified in fuse.h. The low-level API that is primarily documented in fuse_lowlevel.h.

+

+Examples

+

FUSE comes with several examples in the examples directory. A good starting point are hello.c (for the high-level API) and hello_ll.c (for the low-level API).

+

+FUSE internals

+

The authoritative source of information about libfuse internals (including the protocol used for communication with the FUSE kernel module) is the source code.

+

However, some people have kindly documented different aspects of FUSE in a more beginner friendly way. While this information is increasingly out of date, it still provides a good overview:

+ +
+
+ + + + diff --git a/doc/html/invalidate__path_8c.html b/doc/html/invalidate__path_8c.html new file mode 100644 index 0000000..caa7845 --- /dev/null +++ b/doc/html/invalidate__path_8c.html @@ -0,0 +1,392 @@ + + + + + + + +libfuse: example/invalidate_path.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime(&t);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+ +
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5222
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5171
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5227
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:75
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + + + diff --git a/doc/html/invalidate__path_8c_source.html b/doc/html/invalidate__path_8c_source.html new file mode 100644 index 0000000..dcd0a03 --- /dev/null +++ b/doc/html/invalidate__path_8c_source.html @@ -0,0 +1,372 @@ + + + + + + + +libfuse: example/invalidate_path.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8 */
+
9
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm *now;
+
167 time_t t;
+
168 t = time(NULL);
+
169 now = localtime(&t);
+
170 assert(now != NULL);
+
171
+
172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
173 "The current time is %H:%M:%S\n", now);
+
174 assert(time_file_size != 0);
+
175
+
176 grow_file_size = count++;
+
177}
+
178
+
179static int invalidate(struct fuse *fuse, const char *path) {
+
180 int status = fuse_invalidate_path(fuse, path);
+
181 if (status == -ENOENT) {
+
182 return 0;
+
183 } else {
+
184 return status;
+
185 }
+
186}
+
187
+
188static void* update_fs_loop(void *data) {
+
189 struct fuse *fuse = (struct fuse*) data;
+
190
+
191 while (1) {
+
192 update_fs();
+
193 if (!options.no_notify) {
+
194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
196 }
+
197 sleep(options.update_interval);
+
198 }
+
199 return NULL;
+
200}
+
201
+
202static void show_help(const char *progname)
+
203{
+
204 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
205 printf("File-system specific options:\n"
+
206 " --update-interval=<secs> Update-rate of file system contents\n"
+
207 " --no-notify Disable kernel notifications\n"
+
208 "\n");
+
209}
+
210
+
211int main(int argc, char *argv[]) {
+
212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
213 struct fuse *fuse;
+
214 struct fuse_cmdline_opts opts;
+
215 struct fuse_loop_config config;
+
216 int res;
+
217
+
218 /* Initialize the files */
+
219 update_fs();
+
220
+
221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
222 return 1;
+
223
+
224 if (fuse_parse_cmdline(&args, &opts) != 0)
+
225 return 1;
+
226
+
227 if (opts.show_version) {
+
228 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
230 res = 0;
+
231 goto out1;
+
232 } else if (opts.show_help) {
+
233 show_help(argv[0]);
+ +
235 fuse_lib_help(&args);
+
236 res = 0;
+
237 goto out1;
+
238 } else if (!opts.mountpoint) {
+
239 fprintf(stderr, "error: no mountpoint specified\n");
+
240 res = 1;
+
241 goto out1;
+
242 }
+
243
+
244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
245 if (fuse == NULL) {
+
246 res = 1;
+
247 goto out1;
+
248 }
+
249
+
250 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
251 res = 1;
+
252 goto out2;
+
253 }
+
254
+
255 if (fuse_daemonize(opts.foreground) != 0) {
+
256 res = 1;
+
257 goto out3;
+
258 }
+
259
+
260 pthread_t updater; /* Start thread to update file contents */
+
261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
262 if (ret != 0) {
+
263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
264 return 1;
+
265 };
+
266
+
267 struct fuse_session *se = fuse_get_session(fuse);
+
268 if (fuse_set_signal_handlers(se) != 0) {
+
269 res = 1;
+
270 goto out3;
+
271 }
+
272
+
273 if (opts.singlethread)
+
274 res = fuse_loop(fuse);
+
275 else {
+
276 config.clone_fd = opts.clone_fd;
+
277 config.max_idle_threads = opts.max_idle_threads;
+
278 res = fuse_loop_mt(fuse, &config);
+
279 }
+
280 if (res)
+
281 res = 1;
+
282
+ +
284out3:
+
285 fuse_unmount(fuse);
+
286out2:
+
287 fuse_destroy(fuse);
+
288out1:
+
289 free(opts.mountpoint);
+
290 fuse_opt_free_args(&args);
+
291 return res;
+
292}
+ +
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5222
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5171
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5227
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5236
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:127
+
double negative_timeout
Definition fuse.h:137
+
double attr_timeout
Definition fuse.h:143
+ + +
uint32_t keep_cache
Definition fuse_common.h:75
+ + +
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:641
+ +
unsigned long offset
Definition fuse_opt.h:85
+
+ + + + diff --git a/doc/html/ioctl_8c.html b/doc/html/ioctl_8c.html new file mode 100644 index 0000000..5547251 --- /dev/null +++ b/doc/html/ioctl_8c.html @@ -0,0 +1,296 @@ + + + + + + + +libfuse: example/ioctl.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+

Definition in file ioctl.c.

+
+ + + + diff --git a/doc/html/ioctl_8c_source.html b/doc/html/ioctl_8c_source.html new file mode 100644 index 0000000..a892e89 --- /dev/null +++ b/doc/html/ioctl_8c_source.html @@ -0,0 +1,284 @@ + + + + + + + +libfuse: example/ioctl.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:68
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_IOCTL_COMPAT
+ + + +
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:361
+
+ + + + diff --git a/doc/html/ioctl_8h.html b/doc/html/ioctl_8h.html new file mode 100644 index 0000000..d2949ee --- /dev/null +++ b/doc/html/ioctl_8h.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: example/ioctl.h File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + + + diff --git a/doc/html/ioctl_8h_source.html b/doc/html/ioctl_8h_source.html new file mode 100644 index 0000000..c5fd6ac --- /dev/null +++ b/doc/html/ioctl_8h_source.html @@ -0,0 +1,92 @@ + + + + + + + +libfuse: example/ioctl.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file COPYING.
+
8*/
+
9
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + + + diff --git a/doc/html/ioctl__client_8c.html b/doc/html/ioctl__client_8c.html new file mode 100644 index 0000000..e29907d --- /dev/null +++ b/doc/html/ioctl__client_8c.html @@ -0,0 +1,139 @@ + + + + + + + +libfuse: example/ioctl_client.c File Reference + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program tests the ioctl.c example file systsem.
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file COPYING.
+
*/
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+ +
+

Definition in file ioctl_client.c.

+
+ + + + diff --git a/doc/html/ioctl__client_8c_source.html b/doc/html/ioctl__client_8c_source.html new file mode 100644 index 0000000..a2c56b5 --- /dev/null +++ b/doc/html/ioctl__client_8c_source.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: example/ioctl_client.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program tests the ioctl.c example file systsem.
+
7
+
8 This program can be distributed under the terms of the GNU GPLv2.
+
9 See the file COPYING.
+
10*/
+
11
+
24#include <sys/types.h>
+
25#include <fcntl.h>
+
26#include <sys/stat.h>
+
27#include <sys/ioctl.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <ctype.h>
+
31#include <errno.h>
+
32#include <unistd.h>
+
33#include "ioctl.h"
+
34
+
35const char *usage =
+
36"Usage: fioclient FIOC_FILE [size]\n"
+
37"\n"
+
38"Get size if <size> is omitted, set size otherwise\n"
+
39"\n";
+
40
+
41int main(int argc, char **argv)
+
42{
+
43 size_t size;
+
44 int fd;
+
45 int ret = 0;
+
46
+
47 if (argc < 2) {
+
48 fprintf(stderr, "%s", usage);
+
49 return 1;
+
50 }
+
51
+
52 fd = open(argv[1], O_RDWR);
+
53 if (fd < 0) {
+
54 perror("open");
+
55 return 1;
+
56 }
+
57
+
58 if (argc == 2) {
+
59 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
60 perror("ioctl");
+
61 ret = 1;
+
62 goto out;
+
63 }
+
64 printf("%zu\n", size);
+
65 } else {
+
66 size = strtoul(argv[2], NULL, 0);
+
67 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
68 perror("ioctl");
+
69 ret = 1;
+
70 goto out;
+
71 }
+
72 }
+
73out:
+
74 close(fd);
+
75 return ret;
+
76}
+ +
+ + + + diff --git a/doc/html/jquery.js b/doc/html/jquery.js new file mode 100644 index 0000000..1dffb65 --- /dev/null +++ b/doc/html/jquery.js @@ -0,0 +1,34 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=y(e||this.defaultElement||this)[0],this.element=y(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=y(),this.hoverable=y(),this.focusable=y(),this.classesElementLookup={},e!==this&&(y.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=y(e.style?e.ownerDocument:e.document||e),this.window=y(this.document[0].defaultView||this.document[0].parentWindow)),this.options=y.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:y.noop,_create:y.noop,_init:y.noop,destroy:function(){var i=this;this._destroy(),y.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:y.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return y.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=y.widget.extend({},this.options[t]),n=0;n
"),i=e.children()[0];return y("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i},getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.widthx(D(s),D(n))?o.important="horizontal":o.important="vertical",p.using.call(this,t,o)}),h.offset(y.extend(l,{using:t}))})},y.ui.position={fit:{left:function(t,e){var i=e.within,s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,h=s-o,a=o+e.collisionWidth-n-s;e.collisionWidth>n?0n?0=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),y.ui.plugin={add:function(t,e,i){var s,n=y.ui[t].prototype;for(s in i)n.plugins[s]=n.plugins[s]||[],n.plugins[s].push([e,i[s]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;n").css({overflow:"hidden",position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,t={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(t),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(t),this._proportionallyResize()),this._setupHandles(),e.autoHide&&y(this.element).on("mouseenter",function(){e.disabled||(i._removeClass("ui-resizable-autohide"),i._handles.show())}).on("mouseleave",function(){e.disabled||i.resizing||(i._addClass("ui-resizable-autohide"),i._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy(),this._addedHandles.remove();function t(t){y(t).removeData("resizable").removeData("ui-resizable").off(".resizable")}var e;return this.elementIsWrapper&&(t(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;case"aspectRatio":this._aspectRatio=!!e}},_setupHandles:function(){var t,e,i,s,n,o=this.options,h=this;if(this.handles=o.handles||(y(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=y(),this._addedHandles=y(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),i=this.handles.split(","),this.handles={},e=0;e"),this._addClass(n,"ui-resizable-handle "+s),n.css({zIndex:o.zIndex}),this.handles[t]=".ui-resizable-"+t,this.element.children(this.handles[t]).length||(this.element.append(n),this._addedHandles=this._addedHandles.add(n));this._renderAxis=function(t){var e,i,s;for(e in t=t||this.element,this.handles)this.handles[e].constructor===String?this.handles[e]=this.element.children(this.handles[e]).first().show():(this.handles[e].jquery||this.handles[e].nodeType)&&(this.handles[e]=y(this.handles[e]),this._on(this.handles[e],{mousedown:h._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(i=y(this.handles[e],this.element),s=/sw|ne|nw|se|n|s/.test(e)?i.outerHeight():i.outerWidth(),i=["padding",/ne|nw|n/.test(e)?"Top":/se|sw|s/.test(e)?"Bottom":/^e$/.test(e)?"Right":"Left"].join(""),t.css(i,s),this._proportionallyResize()),this._handles=this._handles.add(this.handles[e])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){h.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),h.axis=n&&n[1]?n[1]:"se")}),o.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._addedHandles.remove()},_mouseCapture:function(t){var e,i,s=!1;for(e in this.handles)(i=y(this.handles[e])[0])!==t.target&&!y.contains(i,t.target)||(s=!0);return!this.options.disabled&&s},_mouseStart:function(t){var e,i,s=this.options,n=this.element;return this.resizing=!0,this._renderProxy(),e=this._num(this.helper.css("left")),i=this._num(this.helper.css("top")),s.containment&&(e+=y(s.containment).scrollLeft()||0,i+=y(s.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:e,top:i},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:n.width(),height:n.height()},this.originalSize=this._helper?{width:n.outerWidth(),height:n.outerHeight()}:{width:n.width(),height:n.height()},this.sizeDiff={width:n.outerWidth()-n.width(),height:n.outerHeight()-n.height()},this.originalPosition={left:e,top:i},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof s.aspectRatio?s.aspectRatio:this.originalSize.width/this.originalSize.height||1,s=y(".ui-resizable-"+this.axis).css("cursor"),y("body").css("cursor","auto"===s?this.axis+"-resize":s),this._addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var e=this.originalMousePosition,i=this.axis,s=t.pageX-e.left||0,e=t.pageY-e.top||0,i=this._change[i];return this._updatePrevProperties(),i&&(e=i.apply(this,[t,s,e]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(e=this._updateRatio(e,t)),e=this._respectSize(e,t),this._updateCache(e),this._propagate("resize",t),e=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),y.isEmptyObject(e)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges())),!1},_mouseStop:function(t){this.resizing=!1;var e,i,s,n=this.options,o=this;return this._helper&&(s=(e=(i=this._proportionallyResizeElements).length&&/textarea/i.test(i[0].nodeName))&&this._hasScroll(i[0],"left")?0:o.sizeDiff.height,i=e?0:o.sizeDiff.width,e={width:o.helper.width()-i,height:o.helper.height()-s},i=parseFloat(o.element.css("left"))+(o.position.left-o.originalPosition.left)||null,s=parseFloat(o.element.css("top"))+(o.position.top-o.originalPosition.top)||null,n.animate||this.element.css(y.extend(e,{top:s,left:i})),o.helper.height(o.size.height),o.helper.width(o.size.width),this._helper&&!n.animate&&this._proportionallyResize()),y("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s=this.options,n={minWidth:this._isNumber(s.minWidth)?s.minWidth:0,maxWidth:this._isNumber(s.maxWidth)?s.maxWidth:1/0,minHeight:this._isNumber(s.minHeight)?s.minHeight:0,maxHeight:this._isNumber(s.maxHeight)?s.maxHeight:1/0};(this._aspectRatio||t)&&(e=n.minHeight*this.aspectRatio,i=n.minWidth/this.aspectRatio,s=n.maxHeight*this.aspectRatio,t=n.maxWidth/this.aspectRatio,e>n.minWidth&&(n.minWidth=e),i>n.minHeight&&(n.minHeight=i),st.width,h=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,a=this.originalPosition.left+this.originalSize.width,r=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),i=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),h&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=a-e.minWidth),s&&l&&(t.left=a-e.maxWidth),h&&i&&(t.top=r-e.minHeight),n&&i&&(t.top=r-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];e<4;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;e").css({overflow:"hidden"}),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++e.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize;return{left:this.originalPosition.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize;return{top:this.originalPosition.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(t,e,i){return y.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},sw:function(t,e,i){return y.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,e,i]))},ne:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},nw:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,e,i]))}},_propagate:function(t,e){y.ui.plugin.call(this,t,[e,this.ui()]),"resize"!==t&&this._trigger(t,e,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),y.ui.plugin.add("resizable","animate",{stop:function(e){var i=y(this).resizable("instance"),t=i.options,s=i._proportionallyResizeElements,n=s.length&&/textarea/i.test(s[0].nodeName),o=n&&i._hasScroll(s[0],"left")?0:i.sizeDiff.height,h=n?0:i.sizeDiff.width,n={width:i.size.width-h,height:i.size.height-o},h=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,o=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(y.extend(n,o&&h?{top:o,left:h}:{}),{duration:t.animateDuration,easing:t.animateEasing,step:function(){var t={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};s&&s.length&&y(s[0]).css({width:t.width,height:t.height}),i._updateCache(t),i._propagate("resize",e)}})}}),y.ui.plugin.add("resizable","containment",{start:function(){var i,s,n=y(this).resizable("instance"),t=n.options,e=n.element,o=t.containment,h=o instanceof y?o.get(0):/parent/.test(o)?e.parent().get(0):o;h&&(n.containerElement=y(h),/document/.test(o)||o===document?(n.containerOffset={left:0,top:0},n.containerPosition={left:0,top:0},n.parentData={element:y(document),left:0,top:0,width:y(document).width(),height:y(document).height()||document.body.parentNode.scrollHeight}):(i=y(h),s=[],y(["Top","Right","Left","Bottom"]).each(function(t,e){s[t]=n._num(i.css("padding"+e))}),n.containerOffset=i.offset(),n.containerPosition=i.position(),n.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},t=n.containerOffset,e=n.containerSize.height,o=n.containerSize.width,o=n._hasScroll(h,"left")?h.scrollWidth:o,e=n._hasScroll(h)?h.scrollHeight:e,n.parentData={element:h,left:t.left,top:t.top,width:o,height:e}))},resize:function(t){var e=y(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.position,o=e._aspectRatio||t.shiftKey,h={top:0,left:0},a=e.containerElement,t=!0;a[0]!==document&&/static/.test(a.css("position"))&&(h=s),n.left<(e._helper?s.left:0)&&(e.size.width=e.size.width+(e._helper?e.position.left-s.left:e.position.left-h.left),o&&(e.size.height=e.size.width/e.aspectRatio,t=!1),e.position.left=i.helper?s.left:0),n.top<(e._helper?s.top:0)&&(e.size.height=e.size.height+(e._helper?e.position.top-s.top:e.position.top),o&&(e.size.width=e.size.height*e.aspectRatio,t=!1),e.position.top=e._helper?s.top:0),i=e.containerElement.get(0)===e.element.parent().get(0),n=/relative|absolute/.test(e.containerElement.css("position")),i&&n?(e.offset.left=e.parentData.left+e.position.left,e.offset.top=e.parentData.top+e.position.top):(e.offset.left=e.element.offset().left,e.offset.top=e.element.offset().top),n=Math.abs(e.sizeDiff.width+(e._helper?e.offset.left-h.left:e.offset.left-s.left)),s=Math.abs(e.sizeDiff.height+(e._helper?e.offset.top-h.top:e.offset.top-s.top)),n+e.size.width>=e.parentData.width&&(e.size.width=e.parentData.width-n,o&&(e.size.height=e.size.width/e.aspectRatio,t=!1)),s+e.size.height>=e.parentData.height&&(e.size.height=e.parentData.height-s,o&&(e.size.width=e.size.height*e.aspectRatio,t=!1)),t||(e.position.left=e.prevPosition.left,e.position.top=e.prevPosition.top,e.size.width=e.prevSize.width,e.size.height=e.prevSize.height)},stop:function(){var t=y(this).resizable("instance"),e=t.options,i=t.containerOffset,s=t.containerPosition,n=t.containerElement,o=y(t.helper),h=o.offset(),a=o.outerWidth()-t.sizeDiff.width,o=o.outerHeight()-t.sizeDiff.height;t._helper&&!e.animate&&/relative/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o}),t._helper&&!e.animate&&/static/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o})}}),y.ui.plugin.add("resizable","alsoResize",{start:function(){var t=y(this).resizable("instance").options;y(t.alsoResize).each(function(){var t=y(this);t.data("ui-resizable-alsoresize",{width:parseFloat(t.width()),height:parseFloat(t.height()),left:parseFloat(t.css("left")),top:parseFloat(t.css("top"))})})},resize:function(t,i){var e=y(this).resizable("instance"),s=e.options,n=e.originalSize,o=e.originalPosition,h={height:e.size.height-n.height||0,width:e.size.width-n.width||0,top:e.position.top-o.top||0,left:e.position.left-o.left||0};y(s.alsoResize).each(function(){var t=y(this),s=y(this).data("ui-resizable-alsoresize"),n={},e=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];y.each(e,function(t,e){var i=(s[e]||0)+(h[e]||0);i&&0<=i&&(n[e]=i||null)}),t.css(n)})},stop:function(){y(this).removeData("ui-resizable-alsoresize")}}),y.ui.plugin.add("resizable","ghost",{start:function(){var t=y(this).resizable("instance"),e=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}),t._addClass(t.ghost,"ui-resizable-ghost"),!1!==y.uiBackCompat&&"string"==typeof t.options.ghost&&t.ghost.addClass(this.options.ghost),t.ghost.appendTo(t.helper)},resize:function(){var t=y(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=y(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),y.ui.plugin.add("resizable","grid",{resize:function(){var t,e=y(this).resizable("instance"),i=e.options,s=e.size,n=e.originalSize,o=e.originalPosition,h=e.axis,a="number"==typeof i.grid?[i.grid,i.grid]:i.grid,r=a[0]||1,l=a[1]||1,u=Math.round((s.width-n.width)/r)*r,p=Math.round((s.height-n.height)/l)*l,d=n.width+u,c=n.height+p,f=i.maxWidth&&i.maxWidthd,s=i.minHeight&&i.minHeight>c;i.grid=a,m&&(d+=r),s&&(c+=l),f&&(d-=r),g&&(c-=l),/^(se|s|e)$/.test(h)?(e.size.width=d,e.size.height=c):/^(ne)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.top=o.top-p):/^(sw)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.left=o.left-u):((c-l<=0||d-r<=0)&&(t=e._getPaddingPlusBorderDimensions(this)),0=f[g]?0:Math.min(f[g],n));!a&&1-1){targetElements.on(evt+EVENT_NAMESPACE,function elementToggle(event){$.powerTip.toggle(this,event)})}else{targetElements.on(evt+EVENT_NAMESPACE,function elementOpen(event){$.powerTip.show(this,event)})}});$.each(options.closeEvents,function(idx,evt){if($.inArray(evt,options.openEvents)<0){targetElements.on(evt+EVENT_NAMESPACE,function elementClose(event){$.powerTip.hide(this,!isMouseEvent(event))})}});targetElements.on("keydown"+EVENT_NAMESPACE,function elementKeyDown(event){if(event.keyCode===27){$.powerTip.hide(this,true)}})}return targetElements};$.fn.powerTip.defaults={fadeInTime:200,fadeOutTime:100,followMouse:false,popupId:"powerTip",popupClass:null,intentSensitivity:7,intentPollInterval:100,closeDelay:100,placement:"n",smartPlacement:false,offset:10,mouseOnToPopup:false,manual:false,openEvents:["mouseenter","focus"],closeEvents:["mouseleave","blur"]};$.fn.powerTip.smartPlacementLists={n:["n","ne","nw","s"],e:["e","ne","se","w","nw","sw","n","s","e"],s:["s","se","sw","n"],w:["w","nw","sw","e","ne","se","n","s","w"],nw:["nw","w","sw","n","s","se","nw"],ne:["ne","e","se","n","s","sw","ne"],sw:["sw","w","nw","s","n","ne","sw"],se:["se","e","ne","s","n","nw","se"],"nw-alt":["nw-alt","n","ne-alt","sw-alt","s","se-alt","w","e"],"ne-alt":["ne-alt","n","nw-alt","se-alt","s","sw-alt","e","w"],"sw-alt":["sw-alt","s","se-alt","nw-alt","n","ne-alt","w","e"],"se-alt":["se-alt","s","sw-alt","ne-alt","n","nw-alt","e","w"]};$.powerTip={show:function apiShowTip(element,event){if(isMouseEvent(event)){trackMouse(event);session.previousX=event.pageX;session.previousY=event.pageY;$(element).data(DATA_DISPLAYCONTROLLER).show()}else{$(element).first().data(DATA_DISPLAYCONTROLLER).show(true,true)}return element},reposition:function apiResetPosition(element){$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();return element},hide:function apiCloseTip(element,immediate){var displayController;immediate=element?immediate:true;if(element){displayController=$(element).first().data(DATA_DISPLAYCONTROLLER)}else if(session.activeHover){displayController=session.activeHover.data(DATA_DISPLAYCONTROLLER)}if(displayController){displayController.hide(immediate)}return element},toggle:function apiToggle(element,event){if(session.activeHover&&session.activeHover.is(element)){$.powerTip.hide(element,!isMouseEvent(event))}else{$.powerTip.show(element,event)}return element}};$.powerTip.showTip=$.powerTip.show;$.powerTip.closeTip=$.powerTip.hide;function CSSCoordinates(){var me=this;me.top="auto";me.left="auto";me.right="auto";me.bottom="auto";me.set=function(property,value){if($.isNumeric(value)){me[property]=Math.round(value)}}}function DisplayController(element,options,tipController){var hoverTimer=null,myCloseDelay=null;function openTooltip(immediate,forceOpen){cancelTimer();if(!element.data(DATA_HASACTIVEHOVER)){if(!immediate){session.tipOpenImminent=true;hoverTimer=setTimeout(function intentDelay(){hoverTimer=null;checkForIntent()},options.intentPollInterval)}else{if(forceOpen){element.data(DATA_FORCEDOPEN,true)}closeAnyDelayed();tipController.showTip(element)}}else{cancelClose()}}function closeTooltip(disableDelay){if(myCloseDelay){myCloseDelay=session.closeDelayTimeout=clearTimeout(myCloseDelay);session.delayInProgress=false}cancelTimer();session.tipOpenImminent=false;if(element.data(DATA_HASACTIVEHOVER)){element.data(DATA_FORCEDOPEN,false);if(!disableDelay){session.delayInProgress=true;session.closeDelayTimeout=setTimeout(function closeDelay(){session.closeDelayTimeout=null;tipController.hideTip(element);session.delayInProgress=false;myCloseDelay=null},options.closeDelay);myCloseDelay=session.closeDelayTimeout}else{tipController.hideTip(element)}}}function checkForIntent(){var xDifference=Math.abs(session.previousX-session.currentX),yDifference=Math.abs(session.previousY-session.currentY),totalDifference=xDifference+yDifference;if(totalDifference",{id:options.popupId});if($body.length===0){$body=$("body")}$body.append(tipElement);session.tooltips=session.tooltips?session.tooltips.add(tipElement):tipElement}if(options.followMouse){if(!tipElement.data(DATA_HASMOUSEMOVE)){$document.on("mousemove"+EVENT_NAMESPACE,positionTipOnCursor);$window.on("scroll"+EVENT_NAMESPACE,positionTipOnCursor);tipElement.data(DATA_HASMOUSEMOVE,true)}}function beginShowTip(element){element.data(DATA_HASACTIVEHOVER,true);tipElement.queue(function queueTipInit(next){showTip(element);next()})}function showTip(element){var tipContent;if(!element.data(DATA_HASACTIVEHOVER)){return}if(session.isTipOpen){if(!session.isClosing){hideTip(session.activeHover)}tipElement.delay(100).queue(function queueTipAgain(next){showTip(element);next()});return}element.trigger("powerTipPreRender");tipContent=getTooltipContent(element);if(tipContent){tipElement.empty().append(tipContent)}else{return}element.trigger("powerTipRender");session.activeHover=element;session.isTipOpen=true;tipElement.data(DATA_MOUSEONTOTIP,options.mouseOnToPopup);tipElement.addClass(options.popupClass);if(!options.followMouse||element.data(DATA_FORCEDOPEN)){positionTipOnElement(element);session.isFixedTipOpen=true}else{positionTipOnCursor()}if(!element.data(DATA_FORCEDOPEN)&&!options.followMouse){$document.on("click"+EVENT_NAMESPACE,function documentClick(event){var target=event.target;if(target!==element[0]){if(options.mouseOnToPopup){if(target!==tipElement[0]&&!$.contains(tipElement[0],target)){$.powerTip.hide()}}else{$.powerTip.hide()}}})}if(options.mouseOnToPopup&&!options.manual){tipElement.on("mouseenter"+EVENT_NAMESPACE,function tipMouseEnter(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel()}});tipElement.on("mouseleave"+EVENT_NAMESPACE,function tipMouseLeave(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).hide()}})}tipElement.fadeIn(options.fadeInTime,function fadeInCallback(){if(!session.desyncTimeout){session.desyncTimeout=setInterval(closeDesyncedTip,500)}element.trigger("powerTipOpen")})}function hideTip(element){session.isClosing=true;session.isTipOpen=false;session.desyncTimeout=clearInterval(session.desyncTimeout);element.data(DATA_HASACTIVEHOVER,false);element.data(DATA_FORCEDOPEN,false);$document.off("click"+EVENT_NAMESPACE);tipElement.off(EVENT_NAMESPACE);tipElement.fadeOut(options.fadeOutTime,function fadeOutCallback(){var coords=new CSSCoordinates;session.activeHover=null;session.isClosing=false;session.isFixedTipOpen=false;tipElement.removeClass();coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset);tipElement.css(coords);element.trigger("powerTipClose")})}function positionTipOnCursor(){var tipWidth,tipHeight,coords,collisions,collisionCount;if(!session.isFixedTipOpen&&(session.isTipOpen||session.tipOpenImminent&&tipElement.data(DATA_HASMOUSEMOVE))){tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=new CSSCoordinates;coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset);collisions=getViewportCollisions(coords,tipWidth,tipHeight);if(collisions!==Collision.none){collisionCount=countFlags(collisions);if(collisionCount===1){if(collisions===Collision.right){coords.set("left",session.scrollLeft+session.windowWidth-tipWidth)}else if(collisions===Collision.bottom){coords.set("top",session.scrollTop+session.windowHeight-tipHeight)}}else{coords.set("left",session.currentX-tipWidth-options.offset);coords.set("top",session.currentY-tipHeight-options.offset)}}tipElement.css(coords)}}function positionTipOnElement(element){var priorityList,finalPlacement;if(options.smartPlacement||options.followMouse&&element.data(DATA_FORCEDOPEN)){priorityList=$.fn.powerTip.smartPlacementLists[options.placement];$.each(priorityList,function(idx,pos){var collisions=getViewportCollisions(placeTooltip(element,pos),tipElement.outerWidth(),tipElement.outerHeight());finalPlacement=pos;return collisions!==Collision.none})}else{placeTooltip(element,options.placement);finalPlacement=options.placement}tipElement.removeClass("w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt");tipElement.addClass(finalPlacement)}function placeTooltip(element,placement){var iterationCount=0,tipWidth,tipHeight,coords=new CSSCoordinates;coords.set("top",0);coords.set("left",0);tipElement.css(coords);do{tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=placementCalculator.compute(element,placement,tipWidth,tipHeight,options.offset);tipElement.css(coords)}while(++iterationCount<=5&&(tipWidth!==tipElement.outerWidth()||tipHeight!==tipElement.outerHeight()));return coords}function closeDesyncedTip(){var isDesynced=false,hasDesyncableCloseEvent=$.grep(["mouseleave","mouseout","blur","focusout"],function(eventType){return $.inArray(eventType,options.closeEvents)!==-1}).length>0;if(session.isTipOpen&&!session.isClosing&&!session.delayInProgress&&hasDesyncableCloseEvent){if(session.activeHover.data(DATA_HASACTIVEHOVER)===false||session.activeHover.is(":disabled")){isDesynced=true}else if(!isMouseOver(session.activeHover)&&!session.activeHover.is(":focus")&&!session.activeHover.data(DATA_FORCEDOPEN)){if(tipElement.data(DATA_MOUSEONTOTIP)){if(!isMouseOver(tipElement)){isDesynced=true}}else{isDesynced=true}}if(isDesynced){hideTip(session.activeHover)}}}this.showTip=beginShowTip;this.hideTip=hideTip;this.resetPosition=positionTipOnElement}function isSvgElement(element){return Boolean(window.SVGElement&&element[0]instanceof SVGElement)}function isMouseEvent(event){return Boolean(event&&$.inArray(event.type,MOUSE_EVENTS)>-1&&typeof event.pageX==="number")}function initTracking(){if(!session.mouseTrackingActive){session.mouseTrackingActive=true;getViewportDimensions();$(getViewportDimensions);$document.on("mousemove"+EVENT_NAMESPACE,trackMouse);$window.on("resize"+EVENT_NAMESPACE,trackResize);$window.on("scroll"+EVENT_NAMESPACE,trackScroll)}}function getViewportDimensions(){session.scrollLeft=$window.scrollLeft();session.scrollTop=$window.scrollTop();session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackResize(){session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackScroll(){var x=$window.scrollLeft(),y=$window.scrollTop();if(x!==session.scrollLeft){session.currentX+=x-session.scrollLeft;session.scrollLeft=x}if(y!==session.scrollTop){session.currentY+=y-session.scrollTop;session.scrollTop=y}}function trackMouse(event){session.currentX=event.pageX;session.currentY=event.pageY}function isMouseOver(element){var elementPosition=element.offset(),elementBox=element[0].getBoundingClientRect(),elementWidth=elementBox.right-elementBox.left,elementHeight=elementBox.bottom-elementBox.top;return session.currentX>=elementPosition.left&&session.currentX<=elementPosition.left+elementWidth&&session.currentY>=elementPosition.top&&session.currentY<=elementPosition.top+elementHeight}function getTooltipContent(element){var tipText=element.data(DATA_POWERTIP),tipObject=element.data(DATA_POWERTIPJQ),tipTarget=element.data(DATA_POWERTIPTARGET),targetElement,content;if(tipText){if($.isFunction(tipText)){tipText=tipText.call(element[0])}content=tipText}else if(tipObject){if($.isFunction(tipObject)){tipObject=tipObject.call(element[0])}if(tipObject.length>0){content=tipObject.clone(true,true)}}else if(tipTarget){targetElement=$("#"+tipTarget);if(targetElement.length>0){content=targetElement.html()}}return content}function getViewportCollisions(coords,elementWidth,elementHeight){var viewportTop=session.scrollTop,viewportLeft=session.scrollLeft,viewportBottom=viewportTop+session.windowHeight,viewportRight=viewportLeft+session.windowWidth,collisions=Collision.none;if(coords.topviewportBottom||Math.abs(coords.bottom-session.windowHeight)>viewportBottom){collisions|=Collision.bottom}if(coords.leftviewportRight){collisions|=Collision.left}if(coords.left+elementWidth>viewportRight||coords.right1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);/*! SmartMenus jQuery Plugin - v1.1.0 - September 17, 2017 + * http://www.smartmenus.org/ + * Copyright Vasil Dinkov, Vadikom Web Ltd. http://vadikom.com; Licensed MIT */(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof module&&"object"==typeof module.exports?module.exports=t(require("jquery")):t(jQuery)})(function($){function initMouseDetection(t){var e=".smartmenus_mouse";if(mouseDetectionEnabled||t)mouseDetectionEnabled&&t&&($(document).off(e),mouseDetectionEnabled=!1);else{var i=!0,s=null,o={mousemove:function(t){var e={x:t.pageX,y:t.pageY,timeStamp:(new Date).getTime()};if(s){var o=Math.abs(s.x-e.x),a=Math.abs(s.y-e.y);if((o>0||a>0)&&2>=o&&2>=a&&300>=e.timeStamp-s.timeStamp&&(mouse=!0,i)){var n=$(t.target).closest("a");n.is("a")&&$.each(menuTrees,function(){return $.contains(this.$root[0],n[0])?(this.itemEnter({currentTarget:n[0]}),!1):void 0}),i=!1}}s=e}};o[touchEvents?"touchstart":"pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut"]=function(t){isTouchEvent(t.originalEvent)&&(mouse=!1)},$(document).on(getEventsNS(o,e)),mouseDetectionEnabled=!0}}function isTouchEvent(t){return!/^(4|mouse)$/.test(t.pointerType)}function getEventsNS(t,e){e||(e="");var i={};for(var s in t)i[s.split(" ").join(e+" ")+e]=t[s];return i}var menuTrees=[],mouse=!1,touchEvents="ontouchstart"in window,mouseDetectionEnabled=!1,requestAnimationFrame=window.requestAnimationFrame||function(t){return setTimeout(t,1e3/60)},cancelAnimationFrame=window.cancelAnimationFrame||function(t){clearTimeout(t)},canAnimate=!!$.fn.animate;return $.SmartMenus=function(t,e){this.$root=$(t),this.opts=e,this.rootId="",this.accessIdPrefix="",this.$subArrow=null,this.activatedItems=[],this.visibleSubMenus=[],this.showTimeout=0,this.hideTimeout=0,this.scrollTimeout=0,this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.idInc=0,this.$firstLink=null,this.$firstSub=null,this.disabled=!1,this.$disableOverlay=null,this.$touchScrollingSub=null,this.cssTransforms3d="perspective"in t.style||"webkitPerspective"in t.style,this.wasCollapsible=!1,this.init()},$.extend($.SmartMenus,{hideAll:function(){$.each(menuTrees,function(){this.menuHideAll()})},destroy:function(){for(;menuTrees.length;)menuTrees[0].destroy();initMouseDetection(!0)},prototype:{init:function(t){var e=this;if(!t){menuTrees.push(this),this.rootId=((new Date).getTime()+Math.random()+"").replace(/\D/g,""),this.accessIdPrefix="sm-"+this.rootId+"-",this.$root.hasClass("sm-rtl")&&(this.opts.rightToLeftSubMenus=!0);var i=".smartmenus";this.$root.data("smartmenus",this).attr("data-smartmenus-id",this.rootId).dataSM("level",1).on(getEventsNS({"mouseover focusin":$.proxy(this.rootOver,this),"mouseout focusout":$.proxy(this.rootOut,this),keydown:$.proxy(this.rootKeyDown,this)},i)).on(getEventsNS({mouseenter:$.proxy(this.itemEnter,this),mouseleave:$.proxy(this.itemLeave,this),mousedown:$.proxy(this.itemDown,this),focus:$.proxy(this.itemFocus,this),blur:$.proxy(this.itemBlur,this),click:$.proxy(this.itemClick,this)},i),"a"),i+=this.rootId,this.opts.hideOnClick&&$(document).on(getEventsNS({touchstart:$.proxy(this.docTouchStart,this),touchmove:$.proxy(this.docTouchMove,this),touchend:$.proxy(this.docTouchEnd,this),click:$.proxy(this.docClick,this)},i)),$(window).on(getEventsNS({"resize orientationchange":$.proxy(this.winResize,this)},i)),this.opts.subIndicators&&(this.$subArrow=$("").addClass("sub-arrow"),this.opts.subIndicatorsText&&this.$subArrow.html(this.opts.subIndicatorsText)),initMouseDetection()}if(this.$firstSub=this.$root.find("ul").each(function(){e.menuInit($(this))}).eq(0),this.$firstLink=this.$root.find("a").eq(0),this.opts.markCurrentItem){var s=/(index|default)\.[^#\?\/]*/i,o=/#.*/,a=window.location.href.replace(s,""),n=a.replace(o,"");this.$root.find("a").each(function(){var t=this.href.replace(s,""),i=$(this);(t==a||t==n)&&(i.addClass("current"),e.opts.markCurrentTree&&i.parentsUntil("[data-smartmenus-id]","ul").each(function(){$(this).dataSM("parent-a").addClass("current")}))})}this.wasCollapsible=this.isCollapsible()},destroy:function(t){if(!t){var e=".smartmenus";this.$root.removeData("smartmenus").removeAttr("data-smartmenus-id").removeDataSM("level").off(e),e+=this.rootId,$(document).off(e),$(window).off(e),this.opts.subIndicators&&(this.$subArrow=null)}this.menuHideAll();var i=this;this.$root.find("ul").each(function(){var t=$(this);t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.dataSM("shown-before")&&((i.opts.subMenusMinWidth||i.opts.subMenusMaxWidth)&&t.css({width:"",minWidth:"",maxWidth:""}).removeClass("sm-nowrap"),t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.css({zIndex:"",top:"",left:"",marginLeft:"",marginTop:"",display:""})),0==(t.attr("id")||"").indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeDataSM("in-mega").removeDataSM("shown-before").removeDataSM("scroll-arrows").removeDataSM("parent-a").removeDataSM("level").removeDataSM("beforefirstshowfired").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeAttr("aria-expanded"),this.$root.find("a.has-submenu").each(function(){var t=$(this);0==t.attr("id").indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeClass("has-submenu").removeDataSM("sub").removeAttr("aria-haspopup").removeAttr("aria-controls").removeAttr("aria-expanded").closest("li").removeDataSM("sub"),this.opts.subIndicators&&this.$root.find("span.sub-arrow").remove(),this.opts.markCurrentItem&&this.$root.find("a.current").removeClass("current"),t||(this.$root=null,this.$firstLink=null,this.$firstSub=null,this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),menuTrees.splice($.inArray(this,menuTrees),1))},disable:function(t){if(!this.disabled){if(this.menuHideAll(),!t&&!this.opts.isPopup&&this.$root.is(":visible")){var e=this.$root.offset();this.$disableOverlay=$('
').css({position:"absolute",top:e.top,left:e.left,width:this.$root.outerWidth(),height:this.$root.outerHeight(),zIndex:this.getStartZIndex(!0),opacity:0}).appendTo(document.body)}this.disabled=!0}},docClick:function(t){return this.$touchScrollingSub?(this.$touchScrollingSub=null,void 0):((this.visibleSubMenus.length&&!$.contains(this.$root[0],t.target)||$(t.target).closest("a").length)&&this.menuHideAll(),void 0)},docTouchEnd:function(){if(this.lastTouch){if(!(!this.visibleSubMenus.length||void 0!==this.lastTouch.x2&&this.lastTouch.x1!=this.lastTouch.x2||void 0!==this.lastTouch.y2&&this.lastTouch.y1!=this.lastTouch.y2||this.lastTouch.target&&$.contains(this.$root[0],this.lastTouch.target))){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var t=this;this.hideTimeout=setTimeout(function(){t.menuHideAll()},350)}this.lastTouch=null}},docTouchMove:function(t){if(this.lastTouch){var e=t.originalEvent.touches[0];this.lastTouch.x2=e.pageX,this.lastTouch.y2=e.pageY}},docTouchStart:function(t){var e=t.originalEvent.touches[0];this.lastTouch={x1:e.pageX,y1:e.pageY,target:e.target}},enable:function(){this.disabled&&(this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),this.disabled=!1)},getClosestMenu:function(t){for(var e=$(t).closest("ul");e.dataSM("in-mega");)e=e.parent().closest("ul");return e[0]||null},getHeight:function(t){return this.getOffset(t,!0)},getOffset:function(t,e){var i;"none"==t.css("display")&&(i={position:t[0].style.position,visibility:t[0].style.visibility},t.css({position:"absolute",visibility:"hidden"}).show());var s=t[0].getBoundingClientRect&&t[0].getBoundingClientRect(),o=s&&(e?s.height||s.bottom-s.top:s.width||s.right-s.left);return o||0===o||(o=e?t[0].offsetHeight:t[0].offsetWidth),i&&t.hide().css(i),o},getStartZIndex:function(t){var e=parseInt(this[t?"$root":"$firstSub"].css("z-index"));return!t&&isNaN(e)&&(e=parseInt(this.$root.css("z-index"))),isNaN(e)?1:e},getTouchPoint:function(t){return t.touches&&t.touches[0]||t.changedTouches&&t.changedTouches[0]||t},getViewport:function(t){var e=t?"Height":"Width",i=document.documentElement["client"+e],s=window["inner"+e];return s&&(i=Math.min(i,s)),i},getViewportHeight:function(){return this.getViewport(!0)},getViewportWidth:function(){return this.getViewport()},getWidth:function(t){return this.getOffset(t)},handleEvents:function(){return!this.disabled&&this.isCSSOn()},handleItemEvents:function(t){return this.handleEvents()&&!this.isLinkInMegaMenu(t)},isCollapsible:function(){return"static"==this.$firstSub.css("position")},isCSSOn:function(){return"inline"!=this.$firstLink.css("display")},isFixed:function(){var t="fixed"==this.$root.css("position");return t||this.$root.parentsUntil("body").each(function(){return"fixed"==$(this).css("position")?(t=!0,!1):void 0}),t},isLinkInMegaMenu:function(t){return $(this.getClosestMenu(t[0])).hasClass("mega-menu")},isTouchMode:function(){return!mouse||this.opts.noMouseOver||this.isCollapsible()},itemActivate:function(t,e){var i=t.closest("ul"),s=i.dataSM("level");if(s>1&&(!this.activatedItems[s-2]||this.activatedItems[s-2][0]!=i.dataSM("parent-a")[0])){var o=this;$(i.parentsUntil("[data-smartmenus-id]","ul").get().reverse()).add(i).each(function(){o.itemActivate($(this).dataSM("parent-a"))})}if((!this.isCollapsible()||e)&&this.menuHideSubMenus(this.activatedItems[s-1]&&this.activatedItems[s-1][0]==t[0]?s:s-1),this.activatedItems[s-1]=t,this.$root.triggerHandler("activate.smapi",t[0])!==!1){var a=t.dataSM("sub");a&&(this.isTouchMode()||!this.opts.showOnClick||this.clickActivated)&&this.menuShow(a)}},itemBlur:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&this.$root.triggerHandler("blur.smapi",e[0])},itemClick:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(this.$touchScrollingSub&&this.$touchScrollingSub[0]==e.closest("ul")[0])return this.$touchScrollingSub=null,t.stopPropagation(),!1;if(this.$root.triggerHandler("click.smapi",e[0])===!1)return!1;var i=$(t.target).is(".sub-arrow"),s=e.dataSM("sub"),o=s?2==s.dataSM("level"):!1,a=this.isCollapsible(),n=/toggle$/.test(this.opts.collapsibleBehavior),r=/link$/.test(this.opts.collapsibleBehavior),h=/^accordion/.test(this.opts.collapsibleBehavior);if(s&&!s.is(":visible")){if((!r||!a||i)&&(this.opts.showOnClick&&o&&(this.clickActivated=!0),this.itemActivate(e,h),s.is(":visible")))return this.focusActivated=!0,!1}else if(a&&(n||i))return this.itemActivate(e,h),this.menuHide(s),n&&(this.focusActivated=!1),!1;return this.opts.showOnClick&&o||e.hasClass("disabled")||this.$root.triggerHandler("select.smapi",e[0])===!1?!1:void 0}},itemDown:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&e.dataSM("mousedown",!0)},itemEnter:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(!this.isTouchMode()){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);var i=this;this.showTimeout=setTimeout(function(){i.itemActivate(e)},this.opts.showOnClick&&1==e.closest("ul").dataSM("level")?1:this.opts.showTimeout)}this.$root.triggerHandler("mouseenter.smapi",e[0])}},itemFocus:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(!this.focusActivated||this.isTouchMode()&&e.dataSM("mousedown")||this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0]==e[0]||this.itemActivate(e,!0),this.$root.triggerHandler("focus.smapi",e[0]))},itemLeave:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(this.isTouchMode()||(e[0].blur(),this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0)),e.removeDataSM("mousedown"),this.$root.triggerHandler("mouseleave.smapi",e[0]))},menuHide:function(t){if(this.$root.triggerHandler("beforehide.smapi",t[0])!==!1&&(canAnimate&&t.stop(!0,!0),"none"!=t.css("display"))){var e=function(){t.css("z-index","")};this.isCollapsible()?canAnimate&&this.opts.collapsibleHideFunction?this.opts.collapsibleHideFunction.call(this,t,e):t.hide(this.opts.collapsibleHideDuration,e):canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,t,e):t.hide(this.opts.hideDuration,e),t.dataSM("scroll")&&(this.menuScrollStop(t),t.css({"touch-action":"","-ms-touch-action":"","-webkit-transform":"",transform:""}).off(".smartmenus_scroll").removeDataSM("scroll").dataSM("scroll-arrows").hide()),t.dataSM("parent-a").removeClass("highlighted").attr("aria-expanded","false"),t.attr({"aria-expanded":"false","aria-hidden":"true"});var i=t.dataSM("level");this.activatedItems.splice(i-1,1),this.visibleSubMenus.splice($.inArray(t,this.visibleSubMenus),1),this.$root.triggerHandler("hide.smapi",t[0])}},menuHideAll:function(){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);for(var t=this.opts.isPopup?1:0,e=this.visibleSubMenus.length-1;e>=t;e--)this.menuHide(this.visibleSubMenus[e]);this.opts.isPopup&&(canAnimate&&this.$root.stop(!0,!0),this.$root.is(":visible")&&(canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,this.$root):this.$root.hide(this.opts.hideDuration))),this.activatedItems=[],this.visibleSubMenus=[],this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.$root.triggerHandler("hideAll.smapi")},menuHideSubMenus:function(t){for(var e=this.activatedItems.length-1;e>=t;e--){var i=this.activatedItems[e].dataSM("sub");i&&this.menuHide(i)}},menuInit:function(t){if(!t.dataSM("in-mega")){t.hasClass("mega-menu")&&t.find("ul").dataSM("in-mega",!0);for(var e=2,i=t[0];(i=i.parentNode.parentNode)!=this.$root[0];)e++;var s=t.prevAll("a").eq(-1);s.length||(s=t.prevAll().find("a").eq(-1)),s.addClass("has-submenu").dataSM("sub",t),t.dataSM("parent-a",s).dataSM("level",e).parent().dataSM("sub",t);var o=s.attr("id")||this.accessIdPrefix+ ++this.idInc,a=t.attr("id")||this.accessIdPrefix+ ++this.idInc;s.attr({id:o,"aria-haspopup":"true","aria-controls":a,"aria-expanded":"false"}),t.attr({id:a,role:"group","aria-hidden":"true","aria-labelledby":o,"aria-expanded":"false"}),this.opts.subIndicators&&s[this.opts.subIndicatorsPos](this.$subArrow.clone())}},menuPosition:function(t){var e,i,s=t.dataSM("parent-a"),o=s.closest("li"),a=o.parent(),n=t.dataSM("level"),r=this.getWidth(t),h=this.getHeight(t),u=s.offset(),l=u.left,c=u.top,d=this.getWidth(s),m=this.getHeight(s),p=$(window),f=p.scrollLeft(),v=p.scrollTop(),b=this.getViewportWidth(),S=this.getViewportHeight(),g=a.parent().is("[data-sm-horizontal-sub]")||2==n&&!a.hasClass("sm-vertical"),M=this.opts.rightToLeftSubMenus&&!o.is("[data-sm-reverse]")||!this.opts.rightToLeftSubMenus&&o.is("[data-sm-reverse]"),w=2==n?this.opts.mainMenuSubOffsetX:this.opts.subMenusSubOffsetX,T=2==n?this.opts.mainMenuSubOffsetY:this.opts.subMenusSubOffsetY;if(g?(e=M?d-r-w:w,i=this.opts.bottomToTopSubMenus?-h-T:m+T):(e=M?w-r:d-w,i=this.opts.bottomToTopSubMenus?m-T-h:T),this.opts.keepInViewport){var y=l+e,I=c+i;if(M&&f>y?e=g?f-y+e:d-w:!M&&y+r>f+b&&(e=g?f+b-r-y+e:w-r),g||(S>h&&I+h>v+S?i+=v+S-h-I:(h>=S||v>I)&&(i+=v-I)),g&&(I+h>v+S+.49||v>I)||!g&&h>S+.49){var x=this;t.dataSM("scroll-arrows")||t.dataSM("scroll-arrows",$([$('')[0],$('')[0]]).on({mouseenter:function(){t.dataSM("scroll").up=$(this).hasClass("scroll-up"),x.menuScroll(t)},mouseleave:function(e){x.menuScrollStop(t),x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(t){t.preventDefault()}}).insertAfter(t));var A=".smartmenus_scroll";if(t.dataSM("scroll",{y:this.cssTransforms3d?0:i-m,step:1,itemH:m,subH:h,arrowDownH:this.getHeight(t.dataSM("scroll-arrows").eq(1))}).on(getEventsNS({mouseover:function(e){x.menuScrollOver(t,e)},mouseout:function(e){x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(e){x.menuScrollMousewheel(t,e)}},A)).dataSM("scroll-arrows").css({top:"auto",left:"0",marginLeft:e+(parseInt(t.css("border-left-width"))||0),width:r-(parseInt(t.css("border-left-width"))||0)-(parseInt(t.css("border-right-width"))||0),zIndex:t.css("z-index")}).eq(g&&this.opts.bottomToTopSubMenus?0:1).show(),this.isFixed()){var C={};C[touchEvents?"touchstart touchmove touchend":"pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp"]=function(e){x.menuScrollTouch(t,e)},t.css({"touch-action":"none","-ms-touch-action":"none"}).on(getEventsNS(C,A))}}}t.css({top:"auto",left:"0",marginLeft:e,marginTop:i-m})},menuScroll:function(t,e,i){var s,o=t.dataSM("scroll"),a=t.dataSM("scroll-arrows"),n=o.up?o.upEnd:o.downEnd;if(!e&&o.momentum){if(o.momentum*=.92,s=o.momentum,.5>s)return this.menuScrollStop(t),void 0}else s=i||(e||!this.opts.scrollAccelerate?this.opts.scrollStep:Math.floor(o.step));var r=t.dataSM("level");if(this.activatedItems[r-1]&&this.activatedItems[r-1].dataSM("sub")&&this.activatedItems[r-1].dataSM("sub").is(":visible")&&this.menuHideSubMenus(r-1),o.y=o.up&&o.y>=n||!o.up&&n>=o.y?o.y:Math.abs(n-o.y)>s?o.y+(o.up?s:-s):n,t.css(this.cssTransforms3d?{"-webkit-transform":"translate3d(0, "+o.y+"px, 0)",transform:"translate3d(0, "+o.y+"px, 0)"}:{marginTop:o.y}),mouse&&(o.up&&o.y>o.downEnd||!o.up&&o.y0;t.dataSM("scroll-arrows").eq(i?0:1).is(":visible")&&(t.dataSM("scroll").up=i,this.menuScroll(t,!0))}e.preventDefault()},menuScrollOut:function(t,e){mouse&&(/^scroll-(up|down)/.test((e.relatedTarget||"").className)||(t[0]==e.relatedTarget||$.contains(t[0],e.relatedTarget))&&this.getClosestMenu(e.relatedTarget)==t[0]||t.dataSM("scroll-arrows").css("visibility","hidden"))},menuScrollOver:function(t,e){if(mouse&&!/^scroll-(up|down)/.test(e.target.className)&&this.getClosestMenu(e.target)==t[0]){this.menuScrollRefreshData(t);var i=t.dataSM("scroll"),s=$(window).scrollTop()-t.dataSM("parent-a").offset().top-i.itemH;t.dataSM("scroll-arrows").eq(0).css("margin-top",s).end().eq(1).css("margin-top",s+this.getViewportHeight()-i.arrowDownH).end().css("visibility","visible")}},menuScrollRefreshData:function(t){var e=t.dataSM("scroll"),i=$(window).scrollTop()-t.dataSM("parent-a").offset().top-e.itemH;this.cssTransforms3d&&(i=-(parseFloat(t.css("margin-top"))-i)),$.extend(e,{upEnd:i,downEnd:i+this.getViewportHeight()-e.subH})},menuScrollStop:function(t){return this.scrollTimeout?(cancelAnimationFrame(this.scrollTimeout),this.scrollTimeout=0,t.dataSM("scroll").step=1,!0):void 0},menuScrollTouch:function(t,e){if(e=e.originalEvent,isTouchEvent(e)){var i=this.getTouchPoint(e);if(this.getClosestMenu(i.target)==t[0]){var s=t.dataSM("scroll");if(/(start|down)$/i.test(e.type))this.menuScrollStop(t)?(e.preventDefault(),this.$touchScrollingSub=t):this.$touchScrollingSub=null,this.menuScrollRefreshData(t),$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp});else if(/move$/i.test(e.type)){var o=void 0!==s.touchY?s.touchY:s.touchStartY;if(void 0!==o&&o!=i.pageY){this.$touchScrollingSub=t;var a=i.pageY>o;void 0!==s.up&&s.up!=a&&$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp}),$.extend(s,{up:a,touchY:i.pageY}),this.menuScroll(t,!0,Math.abs(i.pageY-o))}e.preventDefault()}else void 0!==s.touchY&&((s.momentum=15*Math.pow(Math.abs(i.pageY-s.touchStartY)/(e.timeStamp-s.touchStartTime),2))&&(this.menuScrollStop(t),this.menuScroll(t),e.preventDefault()),delete s.touchY)}}},menuShow:function(t){if((t.dataSM("beforefirstshowfired")||(t.dataSM("beforefirstshowfired",!0),this.$root.triggerHandler("beforefirstshow.smapi",t[0])!==!1))&&this.$root.triggerHandler("beforeshow.smapi",t[0])!==!1&&(t.dataSM("shown-before",!0),canAnimate&&t.stop(!0,!0),!t.is(":visible"))){var e=t.dataSM("parent-a"),i=this.isCollapsible();if((this.opts.keepHighlighted||i)&&e.addClass("highlighted"),i)t.removeClass("sm-nowrap").css({zIndex:"",width:"auto",minWidth:"",maxWidth:"",top:"",left:"",marginLeft:"",marginTop:""});else{if(t.css("z-index",this.zIndexInc=(this.zIndexInc||this.getStartZIndex())+1),(this.opts.subMenusMinWidth||this.opts.subMenusMaxWidth)&&(t.css({width:"auto",minWidth:"",maxWidth:""}).addClass("sm-nowrap"),this.opts.subMenusMinWidth&&t.css("min-width",this.opts.subMenusMinWidth),this.opts.subMenusMaxWidth)){var s=this.getWidth(t);t.css("max-width",this.opts.subMenusMaxWidth),s>this.getWidth(t)&&t.removeClass("sm-nowrap").css("width",this.opts.subMenusMaxWidth)}this.menuPosition(t)}var o=function(){t.css("overflow","")};i?canAnimate&&this.opts.collapsibleShowFunction?this.opts.collapsibleShowFunction.call(this,t,o):t.show(this.opts.collapsibleShowDuration,o):canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,t,o):t.show(this.opts.showDuration,o),e.attr("aria-expanded","true"),t.attr({"aria-expanded":"true","aria-hidden":"false"}),this.visibleSubMenus.push(t),this.$root.triggerHandler("show.smapi",t[0])}},popupHide:function(t){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},t?1:this.opts.hideTimeout)},popupShow:function(t,e){if(!this.opts.isPopup)return alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.'),void 0;if(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),this.$root.dataSM("shown-before",!0),canAnimate&&this.$root.stop(!0,!0),!this.$root.is(":visible")){this.$root.css({left:t,top:e});var i=this,s=function(){i.$root.css("overflow","")};canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,this.$root,s):this.$root.show(this.opts.showDuration,s),this.visibleSubMenus[0]=this.$root}},refresh:function(){this.destroy(!0),this.init(!0)},rootKeyDown:function(t){if(this.handleEvents())switch(t.keyCode){case 27:var e=this.activatedItems[0];if(e){this.menuHideAll(),e[0].focus();var i=e.dataSM("sub");i&&this.menuHide(i)}break;case 32:var s=$(t.target);if(s.is("a")&&this.handleItemEvents(s)){var i=s.dataSM("sub");i&&!i.is(":visible")&&(this.itemClick({currentTarget:t.target}),t.preventDefault())}}},rootOut:function(t){if(this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),!this.opts.showOnClick||!this.opts.hideOnClick)){var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},this.opts.hideTimeout)}},rootOver:function(t){this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0)},winResize:function(t){if(this.handleEvents()){if(!("onorientationchange"in window)||"orientationchange"==t.type){var e=this.isCollapsible();this.wasCollapsible&&e||(this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0].blur(),this.menuHideAll()),this.wasCollapsible=e}}else if(this.$disableOverlay){var i=this.$root.offset();this.$disableOverlay.css({top:i.top,left:i.left,width:this.$root.outerWidth(),height:this.$root.outerHeight()})}}}}),$.fn.dataSM=function(t,e){return e?this.data(t+"_smartmenus",e):this.data(t+"_smartmenus")},$.fn.removeDataSM=function(t){return this.removeData(t+"_smartmenus")},$.fn.smartmenus=function(options){if("string"==typeof options){var args=arguments,method=options;return Array.prototype.shift.call(args),this.each(function(){var t=$(this).data("smartmenus");t&&t[method]&&t[method].apply(t,args)})}return this.each(function(){var dataOpts=$(this).data("sm-options")||null;if(dataOpts)try{dataOpts=eval("("+dataOpts+")")}catch(e){dataOpts=null,alert('ERROR\n\nSmartMenus jQuery init:\nInvalid "data-sm-options" attribute value syntax.')}new $.SmartMenus(this,$.extend({},$.fn.smartmenus.defaults,options,dataOpts))})},$.fn.smartmenus.defaults={isPopup:!1,mainMenuSubOffsetX:0,mainMenuSubOffsetY:0,subMenusSubOffsetX:0,subMenusSubOffsetY:0,subMenusMinWidth:"10em",subMenusMaxWidth:"20em",subIndicators:!0,subIndicatorsPos:"append",subIndicatorsText:"",scrollStep:30,scrollAccelerate:!0,showTimeout:250,hideTimeout:500,showDuration:0,showFunction:null,hideDuration:0,hideFunction:function(t,e){t.fadeOut(200,e)},collapsibleShowDuration:0,collapsibleShowFunction:function(t,e){t.slideDown(200,e)},collapsibleHideDuration:0,collapsibleHideFunction:function(t,e){t.slideUp(200,e)},showOnClick:!1,hideOnClick:!0,noMouseOver:!1,keepInViewport:!0,keepHighlighted:!0,markCurrentItem:!1,markCurrentTree:!0,rightToLeftSubMenus:!1,bottomToTopSubMenus:!1,collapsibleBehavior:"default"},$}); \ No newline at end of file diff --git a/doc/html/lib_2buffer_8c_source.html b/doc/html/lib_2buffer_8c_source.html new file mode 100644 index 0000000..28c1063 --- /dev/null +++ b/doc/html/lib_2buffer_8c_source.html @@ -0,0 +1,406 @@ + + + + + + + +libfuse: lib/buffer.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+ +
enum fuse_buf_flags flags
+ +
off_t pos
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + +
+ + + + diff --git a/doc/html/lib_2compat_8c_source.html b/doc/html/lib_2compat_8c_source.html new file mode 100644 index 0000000..b08bfd2 --- /dev/null +++ b/doc/html/lib_2compat_8c_source.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: lib/compat.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + + + diff --git a/doc/html/lib_2cuse__lowlevel_8c_source.html b/doc/html/lib_2cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..46b399c --- /dev/null +++ b/doc/html/lib_2cuse__lowlevel_8c_source.html @@ -0,0 +1,451 @@ + + + + + + + +libfuse: lib/cuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file COPYING.LIB.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
196{
+
197 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
198 struct cuse_init_out outarg;
+
199 struct fuse_session *se = req->se;
+
200 struct cuse_data *cd = se->cuse_data;
+
201 size_t bufsize = se->bufsize;
+
202 struct cuse_lowlevel_ops *clop = req_clop(req);
+
203
+
204 (void) nodeid;
+
205 if (se->debug) {
+
206 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
207 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
208 }
+
209 se->conn.proto_major = arg->major;
+
210 se->conn.proto_minor = arg->minor;
+
211
+
212 /* XXX This is not right.*/
+
213 se->conn.capable_ext = 0;
+
214 se->conn.want_ext = 0;
+
215
+
216 if (arg->major < 7) {
+
217 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
218 arg->major, arg->minor);
+
219 fuse_reply_err(req, EPROTO);
+
220 return;
+
221 }
+
222
+
223 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
224 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
225 bufsize);
+
226 bufsize = FUSE_MIN_READ_BUFFER;
+
227 }
+
228
+
229 bufsize -= 4096;
+
230 if (bufsize < se->conn.max_write)
+
231 se->conn.max_write = bufsize;
+
232
+
233 se->got_init = 1;
+
234 if (se->op.init)
+
235 se->op.init(se->userdata, &se->conn);
+
236
+
237 memset(&outarg, 0, sizeof(outarg));
+
238 outarg.major = FUSE_KERNEL_VERSION;
+
239 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
240 outarg.flags = cd->flags;
+
241 outarg.max_read = cd->max_read;
+
242 outarg.max_write = se->conn.max_write;
+
243 outarg.dev_major = cd->dev_major;
+
244 outarg.dev_minor = cd->dev_minor;
+
245
+
246 if (se->debug) {
+
247 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
248 outarg.major, outarg.minor);
+
249 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
250 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
251 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
252 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
253 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
255 cd->dev_info);
+
256 }
+
257
+
258 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
259
+
260 if (clop->init_done)
+
261 clop->init_done(se->userdata);
+
262
+
263 fuse_free_req(req);
+
264}
+
265
+
266struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
267 const struct cuse_info *ci,
+
268 const struct cuse_lowlevel_ops *clop,
+
269 int *multithreaded, void *userdata)
+
270{
+
271 const char *devname = "/dev/cuse";
+
272 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
275 };
+
276 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
277 struct fuse_session *se;
+
278 struct fuse_cmdline_opts opts;
+
279 int fd;
+
280 int res;
+
281
+
282 if (fuse_parse_cmdline(&args, &opts) == -1)
+
283 return NULL;
+
284 *multithreaded = !opts.singlethread;
+
285
+
286 /* Remove subtype= option */
+
287 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
288 if (res == -1)
+
289 goto out1;
+
290
+
291 /*
+
292 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
293 * would ensue.
+
294 */
+
295 do {
+
296 fd = open("/dev/null", O_RDWR);
+
297 if (fd > 2)
+
298 close(fd);
+
299 } while (fd >= 0 && fd <= 2);
+
300
+
301 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
302 if (se == NULL)
+
303 goto out1;
+
304
+
305 fd = open(devname, O_RDWR);
+
306 if (fd == -1) {
+
307 if (errno == ENODEV || errno == ENOENT)
+
308 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
309 else
+
310 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
311 devname, strerror(errno));
+
312 goto err_se;
+
313 }
+
314 se->fd = fd;
+
315
+
316 res = fuse_set_signal_handlers(se);
+
317 if (res == -1)
+
318 goto err_se;
+
319
+
320 res = fuse_daemonize(opts.foreground);
+
321 if (res == -1)
+
322 goto err_sig;
+
323
+
324 fuse_opt_free_args(&args);
+
325 return se;
+
326
+
327err_sig:
+ +
329err_se:
+ +
331out1:
+
332 free(opts.mountpoint);
+
333 fuse_opt_free_args(&args);
+
334 return NULL;
+
335}
+
336
+
337void cuse_lowlevel_teardown(struct fuse_session *se)
+
338{
+ + +
341}
+
342
+
343int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
344 const struct cuse_lowlevel_ops *clop, void *userdata)
+
345{
+
346 struct fuse_session *se;
+
347 int multithreaded;
+
348 int res;
+
349
+
350 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
351 userdata);
+
352 if (se == NULL)
+
353 return 1;
+
354
+
355 if (multithreaded) {
+
356 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
357 res = fuse_session_loop_mt(se, config);
+
358 fuse_loop_cfg_destroy(config);
+
359 }
+
360 else
+
361 res = fuse_session_loop(se);
+
362
+
363 cuse_lowlevel_teardown(se);
+
364 if (res == -1)
+
365 return 1;
+
366
+
367 return 0;
+
368}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + + + diff --git a/doc/html/lib_2fuse_8c_source.html b/doc/html/lib_2fuse_8c_source.html new file mode 100644 index 0000000..34b22d8 --- /dev/null +++ b/doc/html/lib_2fuse_8c_source.html @@ -0,0 +1,5424 @@ + + + + + + + +libfuse: lib/fuse.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define container_of(ptr, type, member) ({ \
+
96 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
97 (type *)( (char *)__mptr - offsetof(type,member) );})
+
98
+
99#define list_entry(ptr, type, member) \
+
100 container_of(ptr, type, member)
+
101
+
102struct list_head {
+
103 struct list_head *next;
+
104 struct list_head *prev;
+
105};
+
106
+
107struct node_slab {
+
108 struct list_head list; /* must be the first member */
+
109 struct list_head freelist;
+
110 int used;
+
111};
+
112
+
113struct fuse {
+
114 struct fuse_session *se;
+
115 struct node_table name_table;
+
116 struct node_table id_table;
+
117 struct list_head lru_table;
+
118 fuse_ino_t ctr;
+
119 unsigned int generation;
+
120 unsigned int hidectr;
+
121 pthread_mutex_t lock;
+
122 struct fuse_config conf;
+
123 int intr_installed;
+
124 struct fuse_fs *fs;
+
125 struct lock_queue_element *lockq;
+
126 int pagesize;
+
127 struct list_head partial_slabs;
+
128 struct list_head full_slabs;
+
129 pthread_t prune_thread;
+
130};
+
131
+
132struct lock {
+
133 int type;
+
134 off_t start;
+
135 off_t end;
+
136 pid_t pid;
+
137 uint64_t owner;
+
138 struct lock *next;
+
139};
+
140
+
141struct node {
+
142 struct node *name_next;
+
143 struct node *id_next;
+
144 fuse_ino_t nodeid;
+
145 unsigned int generation;
+
146 int refctr;
+
147 struct node *parent;
+
148 char *name;
+
149 uint64_t nlookup;
+
150 int open_count;
+
151 struct timespec stat_updated;
+
152 struct timespec mtime;
+
153 off_t size;
+
154 struct lock *locks;
+
155 unsigned int is_hidden : 1;
+
156 unsigned int cache_valid : 1;
+
157 int treelock;
+
158 char inline_name[32];
+
159};
+
160
+
161#define TREELOCK_WRITE -1
+
162#define TREELOCK_WAIT_OFFSET INT_MIN
+
163
+
164struct node_lru {
+
165 struct node node;
+
166 struct list_head lru;
+
167 struct timespec forget_time;
+
168};
+
169
+
170struct fuse_direntry {
+
171 struct stat stat;
+
172 enum fuse_fill_dir_flags flags;
+
173 char *name;
+
174 struct fuse_direntry *next;
+
175};
+
176
+
177struct fuse_dh {
+
178 pthread_mutex_t lock;
+
179 struct fuse *fuse;
+
180 fuse_req_t req;
+
181 char *contents;
+
182 struct fuse_direntry *first;
+
183 struct fuse_direntry **last;
+
184 unsigned len;
+
185 unsigned size;
+
186 unsigned needlen;
+
187 int filled;
+
188 uint64_t fh;
+
189 int error;
+
190 fuse_ino_t nodeid;
+
191};
+
192
+
193struct fuse_context_i {
+
194 struct fuse_context ctx;
+
195 fuse_req_t req;
+
196};
+
197
+
198/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
199extern fuse_module_factory_t fuse_module_subdir_factory;
+
200#ifdef HAVE_ICONV
+
201extern fuse_module_factory_t fuse_module_iconv_factory;
+
202#endif
+
203
+
204static pthread_key_t fuse_context_key;
+
205static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
206static int fuse_context_ref;
+
207static struct fuse_module *fuse_modules = NULL;
+
208
+
209static int fuse_register_module(const char *name,
+
210 fuse_module_factory_t factory,
+
211 struct fusemod_so *so)
+
212{
+
213 struct fuse_module *mod;
+
214
+
215 mod = calloc(1, sizeof(struct fuse_module));
+
216 if (!mod) {
+
217 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
218 return -1;
+
219 }
+
220 mod->name = strdup(name);
+
221 if (!mod->name) {
+
222 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
223 free(mod);
+
224 return -1;
+
225 }
+
226 mod->factory = factory;
+
227 mod->ctr = 0;
+
228 mod->so = so;
+
229 if (mod->so)
+
230 mod->so->ctr++;
+
231 mod->next = fuse_modules;
+
232 fuse_modules = mod;
+
233
+
234 return 0;
+
235}
+
236
+
237static void fuse_unregister_module(struct fuse_module *m)
+
238{
+
239 struct fuse_module **mp;
+
240 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
241 if (*mp == m) {
+
242 *mp = (*mp)->next;
+
243 break;
+
244 }
+
245 }
+
246 free(m->name);
+
247 free(m);
+
248}
+
249
+
250static int fuse_load_so_module(const char *module)
+
251{
+
252 int ret = -1;
+
253 char *tmp;
+
254 struct fusemod_so *so;
+
255 fuse_module_factory_t *factory;
+
256
+
257 tmp = malloc(strlen(module) + 64);
+
258 if (!tmp) {
+
259 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
260 return -1;
+
261 }
+
262 sprintf(tmp, "libfusemod_%s.so", module);
+
263 so = calloc(1, sizeof(struct fusemod_so));
+
264 if (!so) {
+
265 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
266 goto out;
+
267 }
+
268
+
269 so->handle = dlopen(tmp, RTLD_NOW);
+
270 if (so->handle == NULL) {
+
271 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
272 tmp, dlerror());
+
273 goto out_free_so;
+
274 }
+
275
+
276 sprintf(tmp, "fuse_module_%s_factory", module);
+
277 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
278 if (factory == NULL) {
+
279 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
280 tmp, dlerror());
+
281 goto out_dlclose;
+
282 }
+
283 ret = fuse_register_module(module, *factory, so);
+
284 if (ret)
+
285 goto out_dlclose;
+
286
+
287out:
+
288 free(tmp);
+
289 return ret;
+
290
+
291out_dlclose:
+
292 dlclose(so->handle);
+
293out_free_so:
+
294 free(so);
+
295 goto out;
+
296}
+
297
+
298static struct fuse_module *fuse_find_module(const char *module)
+
299{
+
300 struct fuse_module *m;
+
301 for (m = fuse_modules; m; m = m->next) {
+
302 if (strcmp(module, m->name) == 0) {
+
303 m->ctr++;
+
304 break;
+
305 }
+
306 }
+
307 return m;
+
308}
+
309
+
310static struct fuse_module *fuse_get_module(const char *module)
+
311{
+
312 struct fuse_module *m;
+
313
+
314 pthread_mutex_lock(&fuse_context_lock);
+
315 m = fuse_find_module(module);
+
316 if (!m) {
+
317 int err = fuse_load_so_module(module);
+
318 if (!err)
+
319 m = fuse_find_module(module);
+
320 }
+
321 pthread_mutex_unlock(&fuse_context_lock);
+
322 return m;
+
323}
+
324
+
325static void fuse_put_module(struct fuse_module *m)
+
326{
+
327 pthread_mutex_lock(&fuse_context_lock);
+
328 if (m->so)
+
329 assert(m->ctr > 0);
+
330 /* Builtin modules may already have m->ctr == 0 */
+
331 if (m->ctr > 0)
+
332 m->ctr--;
+
333 if (!m->ctr && m->so) {
+
334 struct fusemod_so *so = m->so;
+
335 assert(so->ctr > 0);
+
336 so->ctr--;
+
337 if (!so->ctr) {
+
338 struct fuse_module **mp;
+
339 for (mp = &fuse_modules; *mp;) {
+
340 if ((*mp)->so == so)
+
341 fuse_unregister_module(*mp);
+
342 else
+
343 mp = &(*mp)->next;
+
344 }
+
345 dlclose(so->handle);
+
346 free(so);
+
347 }
+
348 } else if (!m->ctr) {
+
349 fuse_unregister_module(m);
+
350 }
+
351 pthread_mutex_unlock(&fuse_context_lock);
+
352}
+
353
+
354static void init_list_head(struct list_head *list)
+
355{
+
356 list->next = list;
+
357 list->prev = list;
+
358}
+
359
+
360static int list_empty(const struct list_head *head)
+
361{
+
362 return head->next == head;
+
363}
+
364
+
365static void list_add(struct list_head *new, struct list_head *prev,
+
366 struct list_head *next)
+
367{
+
368 next->prev = new;
+
369 new->next = next;
+
370 new->prev = prev;
+
371 prev->next = new;
+
372}
+
373
+
374static inline void list_add_head(struct list_head *new, struct list_head *head)
+
375{
+
376 list_add(new, head, head->next);
+
377}
+
378
+
379static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
380{
+
381 list_add(new, head->prev, head);
+
382}
+
383
+
384static inline void list_del(struct list_head *entry)
+
385{
+
386 struct list_head *prev = entry->prev;
+
387 struct list_head *next = entry->next;
+
388
+
389 next->prev = prev;
+
390 prev->next = next;
+
391}
+
392
+
393static inline int lru_enabled(struct fuse *f)
+
394{
+
395 return f->conf.remember > 0;
+
396}
+
397
+
398static struct node_lru *node_lru(struct node *node)
+
399{
+
400 return (struct node_lru *) node;
+
401}
+
402
+
403static size_t get_node_size(struct fuse *f)
+
404{
+
405 if (lru_enabled(f))
+
406 return sizeof(struct node_lru);
+
407 else
+
408 return sizeof(struct node);
+
409}
+
410
+
411#ifdef FUSE_NODE_SLAB
+
412static struct node_slab *list_to_slab(struct list_head *head)
+
413{
+
414 return (struct node_slab *) head;
+
415}
+
416
+
417static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
418{
+
419 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
420}
+
421
+
422static int alloc_slab(struct fuse *f)
+
423{
+
424 void *mem;
+
425 struct node_slab *slab;
+
426 char *start;
+
427 size_t num;
+
428 size_t i;
+
429 size_t node_size = get_node_size(f);
+
430
+
431 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
432 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
433
+
434 if (mem == MAP_FAILED)
+
435 return -1;
+
436
+
437 slab = mem;
+
438 init_list_head(&slab->freelist);
+
439 slab->used = 0;
+
440 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
441
+
442 start = (char *) mem + f->pagesize - num * node_size;
+
443 for (i = 0; i < num; i++) {
+
444 struct list_head *n;
+
445
+
446 n = (struct list_head *) (start + i * node_size);
+
447 list_add_tail(n, &slab->freelist);
+
448 }
+
449 list_add_tail(&slab->list, &f->partial_slabs);
+
450
+
451 return 0;
+
452}
+
453
+
454static struct node *alloc_node(struct fuse *f)
+
455{
+
456 struct node_slab *slab;
+
457 struct list_head *node;
+
458
+
459 if (list_empty(&f->partial_slabs)) {
+
460 int res = alloc_slab(f);
+
461 if (res != 0)
+
462 return NULL;
+
463 }
+
464 slab = list_to_slab(f->partial_slabs.next);
+
465 slab->used++;
+
466 node = slab->freelist.next;
+
467 list_del(node);
+
468 if (list_empty(&slab->freelist)) {
+
469 list_del(&slab->list);
+
470 list_add_tail(&slab->list, &f->full_slabs);
+
471 }
+
472 memset(node, 0, sizeof(struct node));
+
473
+
474 return (struct node *) node;
+
475}
+
476
+
477static void free_slab(struct fuse *f, struct node_slab *slab)
+
478{
+
479 int res;
+
480
+
481 list_del(&slab->list);
+
482 res = munmap(slab, f->pagesize);
+
483 if (res == -1)
+
484 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
485 slab);
+
486}
+
487
+
488static void free_node_mem(struct fuse *f, struct node *node)
+
489{
+
490 struct node_slab *slab = node_to_slab(f, node);
+
491 struct list_head *n = (struct list_head *) node;
+
492
+
493 slab->used--;
+
494 if (slab->used) {
+
495 if (list_empty(&slab->freelist)) {
+
496 list_del(&slab->list);
+
497 list_add_tail(&slab->list, &f->partial_slabs);
+
498 }
+
499 list_add_head(n, &slab->freelist);
+
500 } else {
+
501 free_slab(f, slab);
+
502 }
+
503}
+
504#else
+
505static struct node *alloc_node(struct fuse *f)
+
506{
+
507 return (struct node *) calloc(1, get_node_size(f));
+
508}
+
509
+
510static void free_node_mem(struct fuse *f, struct node *node)
+
511{
+
512 (void) f;
+
513 free(node);
+
514}
+
515#endif
+
516
+
517static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
518{
+
519 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
520 uint64_t oldhash = hash % (f->id_table.size / 2);
+
521
+
522 if (oldhash >= f->id_table.split)
+
523 return oldhash;
+
524 else
+
525 return hash;
+
526}
+
527
+
528static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
529{
+
530 size_t hash = id_hash(f, nodeid);
+
531 struct node *node;
+
532
+
533 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
534 if (node->nodeid == nodeid)
+
535 return node;
+
536
+
537 return NULL;
+
538}
+
539
+
540static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
541{
+
542 struct node *node = get_node_nocheck(f, nodeid);
+
543 if (!node) {
+
544 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
545 (unsigned long long) nodeid);
+
546 abort();
+
547 }
+
548 return node;
+
549}
+
550
+
551static void curr_time(struct timespec *now);
+
552static double diff_timespec(const struct timespec *t1,
+
553 const struct timespec *t2);
+
554
+
555static void remove_node_lru(struct node *node)
+
556{
+
557 struct node_lru *lnode = node_lru(node);
+
558 list_del(&lnode->lru);
+
559 init_list_head(&lnode->lru);
+
560}
+
561
+
562static void set_forget_time(struct fuse *f, struct node *node)
+
563{
+
564 struct node_lru *lnode = node_lru(node);
+
565
+
566 list_del(&lnode->lru);
+
567 list_add_tail(&lnode->lru, &f->lru_table);
+
568 curr_time(&lnode->forget_time);
+
569}
+
570
+
571static void free_node(struct fuse *f, struct node *node)
+
572{
+
573 if (node->name != node->inline_name)
+
574 free(node->name);
+
575 free_node_mem(f, node);
+
576}
+
577
+
578static void node_table_reduce(struct node_table *t)
+
579{
+
580 size_t newsize = t->size / 2;
+
581 void *newarray;
+
582
+
583 if (newsize < NODE_TABLE_MIN_SIZE)
+
584 return;
+
585
+
586 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
587 if (newarray != NULL)
+
588 t->array = newarray;
+
589
+
590 t->size = newsize;
+
591 t->split = t->size / 2;
+
592}
+
593
+
594static void remerge_id(struct fuse *f)
+
595{
+
596 struct node_table *t = &f->id_table;
+
597 int iter;
+
598
+
599 if (t->split == 0)
+
600 node_table_reduce(t);
+
601
+
602 for (iter = 8; t->split > 0 && iter; iter--) {
+
603 struct node **upper;
+
604
+
605 t->split--;
+
606 upper = &t->array[t->split + t->size / 2];
+
607 if (*upper) {
+
608 struct node **nodep;
+
609
+
610 for (nodep = &t->array[t->split]; *nodep;
+
611 nodep = &(*nodep)->id_next);
+
612
+
613 *nodep = *upper;
+
614 *upper = NULL;
+
615 break;
+
616 }
+
617 }
+
618}
+
619
+
620static void unhash_id(struct fuse *f, struct node *node)
+
621{
+
622 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
623
+
624 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
625 if (*nodep == node) {
+
626 *nodep = node->id_next;
+
627 f->id_table.use--;
+
628
+
629 if(f->id_table.use < f->id_table.size / 4)
+
630 remerge_id(f);
+
631 return;
+
632 }
+
633}
+
634
+
635static int node_table_resize(struct node_table *t)
+
636{
+
637 size_t newsize = t->size * 2;
+
638 void *newarray;
+
639
+
640 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
641 if (newarray == NULL)
+
642 return -1;
+
643
+
644 t->array = newarray;
+
645 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
646 t->size = newsize;
+
647 t->split = 0;
+
648
+
649 return 0;
+
650}
+
651
+
652static void rehash_id(struct fuse *f)
+
653{
+
654 struct node_table *t = &f->id_table;
+
655 struct node **nodep;
+
656 struct node **next;
+
657 size_t hash;
+
658
+
659 if (t->split == t->size / 2)
+
660 return;
+
661
+
662 hash = t->split;
+
663 t->split++;
+
664 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
665 struct node *node = *nodep;
+
666 size_t newhash = id_hash(f, node->nodeid);
+
667
+
668 if (newhash != hash) {
+
669 next = nodep;
+
670 *nodep = node->id_next;
+
671 node->id_next = t->array[newhash];
+
672 t->array[newhash] = node;
+
673 } else {
+
674 next = &node->id_next;
+
675 }
+
676 }
+
677 if (t->split == t->size / 2)
+
678 node_table_resize(t);
+
679}
+
680
+
681static void hash_id(struct fuse *f, struct node *node)
+
682{
+
683 size_t hash = id_hash(f, node->nodeid);
+
684 node->id_next = f->id_table.array[hash];
+
685 f->id_table.array[hash] = node;
+
686 f->id_table.use++;
+
687
+
688 if (f->id_table.use >= f->id_table.size / 2)
+
689 rehash_id(f);
+
690}
+
691
+
692static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
693 const char *name)
+
694{
+
695 uint64_t hash = parent;
+
696 uint64_t oldhash;
+
697
+
698 for (; *name; name++)
+
699 hash = hash * 31 + (unsigned char) *name;
+
700
+
701 hash %= f->name_table.size;
+
702 oldhash = hash % (f->name_table.size / 2);
+
703 if (oldhash >= f->name_table.split)
+
704 return oldhash;
+
705 else
+
706 return hash;
+
707}
+
708
+
709static void unref_node(struct fuse *f, struct node *node);
+
710
+
711static void remerge_name(struct fuse *f)
+
712{
+
713 struct node_table *t = &f->name_table;
+
714 int iter;
+
715
+
716 if (t->split == 0)
+
717 node_table_reduce(t);
+
718
+
719 for (iter = 8; t->split > 0 && iter; iter--) {
+
720 struct node **upper;
+
721
+
722 t->split--;
+
723 upper = &t->array[t->split + t->size / 2];
+
724 if (*upper) {
+
725 struct node **nodep;
+
726
+
727 for (nodep = &t->array[t->split]; *nodep;
+
728 nodep = &(*nodep)->name_next);
+
729
+
730 *nodep = *upper;
+
731 *upper = NULL;
+
732 break;
+
733 }
+
734 }
+
735}
+
736
+
737static void unhash_name(struct fuse *f, struct node *node)
+
738{
+
739 if (node->name) {
+
740 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
741 struct node **nodep = &f->name_table.array[hash];
+
742
+
743 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
744 if (*nodep == node) {
+
745 *nodep = node->name_next;
+
746 node->name_next = NULL;
+
747 unref_node(f, node->parent);
+
748 if (node->name != node->inline_name)
+
749 free(node->name);
+
750 node->name = NULL;
+
751 node->parent = NULL;
+
752 f->name_table.use--;
+
753
+
754 if (f->name_table.use < f->name_table.size / 4)
+
755 remerge_name(f);
+
756 return;
+
757 }
+
758 fuse_log(FUSE_LOG_ERR,
+
759 "fuse internal error: unable to unhash node: %llu\n",
+
760 (unsigned long long) node->nodeid);
+
761 abort();
+
762 }
+
763}
+
764
+
765static void rehash_name(struct fuse *f)
+
766{
+
767 struct node_table *t = &f->name_table;
+
768 struct node **nodep;
+
769 struct node **next;
+
770 size_t hash;
+
771
+
772 if (t->split == t->size / 2)
+
773 return;
+
774
+
775 hash = t->split;
+
776 t->split++;
+
777 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
778 struct node *node = *nodep;
+
779 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
780
+
781 if (newhash != hash) {
+
782 next = nodep;
+
783 *nodep = node->name_next;
+
784 node->name_next = t->array[newhash];
+
785 t->array[newhash] = node;
+
786 } else {
+
787 next = &node->name_next;
+
788 }
+
789 }
+
790 if (t->split == t->size / 2)
+
791 node_table_resize(t);
+
792}
+
793
+
794static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
795 const char *name)
+
796{
+
797 size_t hash = name_hash(f, parentid, name);
+
798 struct node *parent = get_node(f, parentid);
+
799 if (strlen(name) < sizeof(node->inline_name)) {
+
800 strcpy(node->inline_name, name);
+
801 node->name = node->inline_name;
+
802 } else {
+
803 node->name = strdup(name);
+
804 if (node->name == NULL)
+
805 return -1;
+
806 }
+
807
+
808 parent->refctr ++;
+
809 node->parent = parent;
+
810 node->name_next = f->name_table.array[hash];
+
811 f->name_table.array[hash] = node;
+
812 f->name_table.use++;
+
813
+
814 if (f->name_table.use >= f->name_table.size / 2)
+
815 rehash_name(f);
+
816
+
817 return 0;
+
818}
+
819
+
820static void delete_node(struct fuse *f, struct node *node)
+
821{
+
822 if (f->conf.debug)
+
823 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
824 (unsigned long long) node->nodeid);
+
825
+
826 assert(node->treelock == 0);
+
827 unhash_name(f, node);
+
828 if (lru_enabled(f))
+
829 remove_node_lru(node);
+
830 unhash_id(f, node);
+
831 free_node(f, node);
+
832}
+
833
+
834static void unref_node(struct fuse *f, struct node *node)
+
835{
+
836 assert(node->refctr > 0);
+
837 node->refctr --;
+
838 if (!node->refctr)
+
839 delete_node(f, node);
+
840}
+
841
+
842static fuse_ino_t next_id(struct fuse *f)
+
843{
+
844 do {
+
845 f->ctr = (f->ctr + 1) & 0xffffffff;
+
846 if (!f->ctr)
+
847 f->generation ++;
+
848 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
849 get_node_nocheck(f, f->ctr) != NULL);
+
850 return f->ctr;
+
851}
+
852
+
853static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
854 const char *name)
+
855{
+
856 size_t hash = name_hash(f, parent, name);
+
857 struct node *node;
+
858
+
859 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
860 if (node->parent->nodeid == parent &&
+
861 strcmp(node->name, name) == 0)
+
862 return node;
+
863
+
864 return NULL;
+
865}
+
866
+
867static void inc_nlookup(struct node *node)
+
868{
+
869 if (!node->nlookup)
+
870 node->refctr++;
+
871 node->nlookup++;
+
872}
+
873
+
874static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
875 const char *name)
+
876{
+
877 struct node *node;
+
878
+
879 pthread_mutex_lock(&f->lock);
+
880 if (!name)
+
881 node = get_node(f, parent);
+
882 else
+
883 node = lookup_node(f, parent, name);
+
884 if (node == NULL) {
+
885 node = alloc_node(f);
+
886 if (node == NULL)
+
887 goto out_err;
+
888
+
889 node->nodeid = next_id(f);
+
890 node->generation = f->generation;
+
891 if (f->conf.remember)
+
892 inc_nlookup(node);
+
893
+
894 if (hash_name(f, node, parent, name) == -1) {
+
895 free_node(f, node);
+
896 node = NULL;
+
897 goto out_err;
+
898 }
+
899 hash_id(f, node);
+
900 if (lru_enabled(f)) {
+
901 struct node_lru *lnode = node_lru(node);
+
902 init_list_head(&lnode->lru);
+
903 }
+
904 } else if (lru_enabled(f) && node->nlookup == 1) {
+
905 remove_node_lru(node);
+
906 }
+
907 inc_nlookup(node);
+
908out_err:
+
909 pthread_mutex_unlock(&f->lock);
+
910 return node;
+
911}
+
912
+
913static int lookup_path_in_cache(struct fuse *f,
+
914 const char *path, fuse_ino_t *inop)
+
915{
+
916 char *tmp = strdup(path);
+
917 if (!tmp)
+
918 return -ENOMEM;
+
919
+
920 pthread_mutex_lock(&f->lock);
+
921 fuse_ino_t ino = FUSE_ROOT_ID;
+
922
+
923 int err = 0;
+
924 char *save_ptr;
+
925 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
926 while (path_element != NULL) {
+
927 struct node *node = lookup_node(f, ino, path_element);
+
928 if (node == NULL) {
+
929 err = -ENOENT;
+
930 break;
+
931 }
+
932 ino = node->nodeid;
+
933 path_element = strtok_r(NULL, "/", &save_ptr);
+
934 }
+
935 pthread_mutex_unlock(&f->lock);
+
936 free(tmp);
+
937
+
938 if (!err)
+
939 *inop = ino;
+
940 return err;
+
941}
+
942
+
943static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
944{
+
945 size_t len = strlen(name);
+
946
+
947 if (s - len <= *buf) {
+
948 unsigned pathlen = *bufsize - (s - *buf);
+
949 unsigned newbufsize = *bufsize;
+
950 char *newbuf;
+
951
+
952 while (newbufsize < pathlen + len + 1) {
+
953 if (newbufsize >= 0x80000000)
+
954 newbufsize = 0xffffffff;
+
955 else
+
956 newbufsize *= 2;
+
957 }
+
958
+
959 newbuf = realloc(*buf, newbufsize);
+
960 if (newbuf == NULL)
+
961 return NULL;
+
962
+
963 *buf = newbuf;
+
964 s = newbuf + newbufsize - pathlen;
+
965 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
966 *bufsize = newbufsize;
+
967 }
+
968 s -= len;
+
969 memcpy(s, name, len);
+
970 s--;
+
971 *s = '/';
+
972
+
973 return s;
+
974}
+
975
+
976static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
977 struct node *end)
+
978{
+
979 struct node *node;
+
980
+
981 if (wnode) {
+
982 assert(wnode->treelock == TREELOCK_WRITE);
+
983 wnode->treelock = 0;
+
984 }
+
985
+
986 for (node = get_node(f, nodeid);
+
987 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
988 assert(node->treelock != 0);
+
989 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
990 assert(node->treelock != TREELOCK_WRITE);
+
991 node->treelock--;
+
992 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
993 node->treelock = 0;
+
994 }
+
995}
+
996
+
997static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
998 char **path, struct node **wnodep, bool need_lock)
+
999{
+
1000 unsigned bufsize = 256;
+
1001 char *buf;
+
1002 char *s;
+
1003 struct node *node;
+
1004 struct node *wnode = NULL;
+
1005 int err;
+
1006
+
1007 *path = NULL;
+
1008
+
1009 err = -ENOMEM;
+
1010 buf = malloc(bufsize);
+
1011 if (buf == NULL)
+
1012 goto out_err;
+
1013
+
1014 s = buf + bufsize - 1;
+
1015 *s = '\0';
+
1016
+
1017 if (name != NULL) {
+
1018 s = add_name(&buf, &bufsize, s, name);
+
1019 err = -ENOMEM;
+
1020 if (s == NULL)
+
1021 goto out_free;
+
1022 }
+
1023
+
1024 if (wnodep) {
+
1025 assert(need_lock);
+
1026 wnode = lookup_node(f, nodeid, name);
+
1027 if (wnode) {
+
1028 if (wnode->treelock != 0) {
+
1029 if (wnode->treelock > 0)
+
1030 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1031 err = -EAGAIN;
+
1032 goto out_free;
+
1033 }
+
1034 wnode->treelock = TREELOCK_WRITE;
+
1035 }
+
1036 }
+
1037
+
1038 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1039 node = node->parent) {
+
1040 err = -ESTALE;
+
1041 if (node->name == NULL || node->parent == NULL)
+
1042 goto out_unlock;
+
1043
+
1044 err = -ENOMEM;
+
1045 s = add_name(&buf, &bufsize, s, node->name);
+
1046 if (s == NULL)
+
1047 goto out_unlock;
+
1048
+
1049 if (need_lock) {
+
1050 err = -EAGAIN;
+
1051 if (node->treelock < 0)
+
1052 goto out_unlock;
+
1053
+
1054 node->treelock++;
+
1055 }
+
1056 }
+
1057
+
1058 if (s[0])
+
1059 memmove(buf, s, bufsize - (s - buf));
+
1060 else
+
1061 strcpy(buf, "/");
+
1062
+
1063 *path = buf;
+
1064 if (wnodep)
+
1065 *wnodep = wnode;
+
1066
+
1067 return 0;
+
1068
+
1069 out_unlock:
+
1070 if (need_lock)
+
1071 unlock_path(f, nodeid, wnode, node);
+
1072 out_free:
+
1073 free(buf);
+
1074
+
1075 out_err:
+
1076 return err;
+
1077}
+
1078
+
1079static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1080 fuse_ino_t nodeid2, const char *name2,
+
1081 char **path1, char **path2,
+
1082 struct node **wnode1, struct node **wnode2)
+
1083{
+
1084 int err;
+
1085
+
1086 /* FIXME: locking two paths needs deadlock checking */
+
1087 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1088 if (!err) {
+
1089 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1090 if (err) {
+
1091 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1092
+
1093 unlock_path(f, nodeid1, wn1, NULL);
+
1094 free(*path1);
+
1095 }
+
1096 }
+
1097 return err;
+
1098}
+
1099
+
1100static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1101{
+
1102 int err;
+
1103
+
1104 if (!qe->path1) {
+
1105 /* Just waiting for it to be unlocked */
+
1106 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1107 pthread_cond_signal(&qe->cond);
+
1108
+
1109 return;
+
1110 }
+
1111
+
1112 if (qe->done)
+
1113 return; // Don't try to double-lock the element
+
1114
+
1115 if (!qe->path2) {
+
1116 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1117 qe->wnode1, true);
+
1118 } else {
+
1119 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1120 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1121 qe->wnode2);
+
1122 }
+
1123
+
1124 if (err == -EAGAIN)
+
1125 return; /* keep trying */
+
1126
+
1127 qe->err = err;
+
1128 qe->done = true;
+
1129 pthread_cond_signal(&qe->cond);
+
1130}
+
1131
+
1132static void wake_up_queued(struct fuse *f)
+
1133{
+
1134 struct lock_queue_element *qe;
+
1135
+
1136 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1137 queue_element_wakeup(f, qe);
+
1138}
+
1139
+
1140static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1141 const char *name, bool wr)
+
1142{
+
1143 if (f->conf.debug) {
+
1144 struct node *wnode = NULL;
+
1145
+
1146 if (wr)
+
1147 wnode = lookup_node(f, nodeid, name);
+
1148
+
1149 if (wnode) {
+
1150 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1151 msg, (unsigned long long) wnode->nodeid);
+
1152 } else {
+
1153 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1154 msg, (unsigned long long) nodeid);
+
1155 }
+
1156 }
+
1157}
+
1158
+
1159static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1160{
+
1161 struct lock_queue_element **qp;
+
1162
+
1163 qe->done = false;
+
1164 pthread_cond_init(&qe->cond, NULL);
+
1165 qe->next = NULL;
+
1166 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1167 *qp = qe;
+
1168}
+
1169
+
1170static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1171{
+
1172 struct lock_queue_element **qp;
+
1173
+
1174 pthread_cond_destroy(&qe->cond);
+
1175 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1176 *qp = qe->next;
+
1177}
+
1178
+
1179static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1180{
+
1181 queue_path(f, qe);
+
1182
+
1183 do {
+
1184 pthread_cond_wait(&qe->cond, &f->lock);
+
1185 } while (!qe->done);
+
1186
+
1187 dequeue_path(f, qe);
+
1188
+
1189 return qe->err;
+
1190}
+
1191
+
1192static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1193 char **path, struct node **wnode)
+
1194{
+
1195 int err;
+
1196
+
1197 pthread_mutex_lock(&f->lock);
+
1198 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1199 if (err == -EAGAIN) {
+
1200 struct lock_queue_element qe = {
+
1201 .nodeid1 = nodeid,
+
1202 .name1 = name,
+
1203 .path1 = path,
+
1204 .wnode1 = wnode,
+
1205 };
+
1206 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1207 err = wait_path(f, &qe);
+
1208 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1209 }
+
1210 pthread_mutex_unlock(&f->lock);
+
1211
+
1212 return err;
+
1213}
+
1214
+
1215static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1216{
+
1217 return get_path_common(f, nodeid, NULL, path, NULL);
+
1218}
+
1219
+
1220static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1221{
+
1222 int err = 0;
+
1223
+
1224 if (f->conf.nullpath_ok) {
+
1225 *path = NULL;
+
1226 } else {
+
1227 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1228 if (err == -ESTALE)
+
1229 err = 0;
+
1230 }
+
1231
+
1232 return err;
+
1233}
+
1234
+
1235static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1236 char **path)
+
1237{
+
1238 return get_path_common(f, nodeid, name, path, NULL);
+
1239}
+
1240
+
1241static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1242 char **path, struct node **wnode)
+
1243{
+
1244 return get_path_common(f, nodeid, name, path, wnode);
+
1245}
+
1246
+
1247#if defined(__FreeBSD__)
+
1248#define CHECK_DIR_LOOP
+
1249#endif
+
1250
+
1251#if defined(CHECK_DIR_LOOP)
+
1252static int check_dir_loop(struct fuse *f,
+
1253 fuse_ino_t nodeid1, const char *name1,
+
1254 fuse_ino_t nodeid2, const char *name2)
+
1255{
+
1256 struct node *node, *node1, *node2;
+
1257 fuse_ino_t id1, id2;
+
1258
+
1259 node1 = lookup_node(f, nodeid1, name1);
+
1260 id1 = node1 ? node1->nodeid : nodeid1;
+
1261
+
1262 node2 = lookup_node(f, nodeid2, name2);
+
1263 id2 = node2 ? node2->nodeid : nodeid2;
+
1264
+
1265 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1266 node = node->parent) {
+
1267 if (node->name == NULL || node->parent == NULL)
+
1268 break;
+
1269
+
1270 if (node->nodeid != id2 && node->nodeid == id1)
+
1271 return -EINVAL;
+
1272 }
+
1273
+
1274 if (node2)
+
1275 {
+
1276 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1277 node = node->parent) {
+
1278 if (node->name == NULL || node->parent == NULL)
+
1279 break;
+
1280
+
1281 if (node->nodeid != id1 && node->nodeid == id2)
+
1282 return -ENOTEMPTY;
+
1283 }
+
1284 }
+
1285
+
1286 return 0;
+
1287}
+
1288#endif
+
1289
+
1290static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1291 fuse_ino_t nodeid2, const char *name2,
+
1292 char **path1, char **path2,
+
1293 struct node **wnode1, struct node **wnode2)
+
1294{
+
1295 int err;
+
1296
+
1297 pthread_mutex_lock(&f->lock);
+
1298
+
1299#if defined(CHECK_DIR_LOOP)
+
1300 if (name1)
+
1301 {
+
1302 // called during rename; perform dir loop check
+
1303 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1304 if (err)
+
1305 goto out_unlock;
+
1306 }
+
1307#endif
+
1308
+
1309 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1310 path1, path2, wnode1, wnode2);
+
1311 if (err == -EAGAIN) {
+
1312 struct lock_queue_element qe = {
+
1313 .nodeid1 = nodeid1,
+
1314 .name1 = name1,
+
1315 .path1 = path1,
+
1316 .wnode1 = wnode1,
+
1317 .nodeid2 = nodeid2,
+
1318 .name2 = name2,
+
1319 .path2 = path2,
+
1320 .wnode2 = wnode2,
+
1321 };
+
1322
+
1323 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1324 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1325 err = wait_path(f, &qe);
+
1326 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1327 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1328 }
+
1329
+
1330#if defined(CHECK_DIR_LOOP)
+
1331out_unlock:
+
1332#endif
+
1333 pthread_mutex_unlock(&f->lock);
+
1334
+
1335 return err;
+
1336}
+
1337
+
1338static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1339 struct node *wnode, char *path)
+
1340{
+
1341 pthread_mutex_lock(&f->lock);
+
1342 unlock_path(f, nodeid, wnode, NULL);
+
1343 if (f->lockq)
+
1344 wake_up_queued(f);
+
1345 pthread_mutex_unlock(&f->lock);
+
1346 free(path);
+
1347}
+
1348
+
1349static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1350{
+
1351 if (path)
+
1352 free_path_wrlock(f, nodeid, NULL, path);
+
1353}
+
1354
+
1355static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1356 struct node *wnode1, struct node *wnode2,
+
1357 char *path1, char *path2)
+
1358{
+
1359 pthread_mutex_lock(&f->lock);
+
1360 unlock_path(f, nodeid1, wnode1, NULL);
+
1361 unlock_path(f, nodeid2, wnode2, NULL);
+
1362 wake_up_queued(f);
+
1363 pthread_mutex_unlock(&f->lock);
+
1364 free(path1);
+
1365 free(path2);
+
1366}
+
1367
+
1368static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1369{
+
1370 struct node *node;
+
1371 if (nodeid == FUSE_ROOT_ID)
+
1372 return;
+
1373 pthread_mutex_lock(&f->lock);
+
1374 node = get_node(f, nodeid);
+
1375
+
1376 /*
+
1377 * Node may still be locked due to interrupt idiocy in open,
+
1378 * create and opendir
+
1379 */
+
1380 while (node->nlookup == nlookup && node->treelock) {
+
1381 struct lock_queue_element qe = {
+
1382 .nodeid1 = nodeid,
+
1383 };
+
1384
+
1385 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1386 queue_path(f, &qe);
+
1387
+
1388 do {
+
1389 pthread_cond_wait(&qe.cond, &f->lock);
+
1390 } while (node->nlookup == nlookup && node->treelock);
+
1391
+
1392 dequeue_path(f, &qe);
+
1393 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1394 }
+
1395
+
1396 assert(node->nlookup >= nlookup);
+
1397 node->nlookup -= nlookup;
+
1398 if (!node->nlookup) {
+
1399 unref_node(f, node);
+
1400 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1401 set_forget_time(f, node);
+
1402 }
+
1403 pthread_mutex_unlock(&f->lock);
+
1404}
+
1405
+
1406static void unlink_node(struct fuse *f, struct node *node)
+
1407{
+
1408 if (f->conf.remember) {
+
1409 assert(node->nlookup > 1);
+
1410 node->nlookup--;
+
1411 }
+
1412 unhash_name(f, node);
+
1413}
+
1414
+
1415static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1416{
+
1417 struct node *node;
+
1418
+
1419 pthread_mutex_lock(&f->lock);
+
1420 node = lookup_node(f, dir, name);
+
1421 if (node != NULL)
+
1422 unlink_node(f, node);
+
1423 pthread_mutex_unlock(&f->lock);
+
1424}
+
1425
+
1426static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1427 fuse_ino_t newdir, const char *newname, int hide)
+
1428{
+
1429 struct node *node;
+
1430 struct node *newnode;
+
1431 int err = 0;
+
1432
+
1433 pthread_mutex_lock(&f->lock);
+
1434 node = lookup_node(f, olddir, oldname);
+
1435 newnode = lookup_node(f, newdir, newname);
+
1436 if (node == NULL)
+
1437 goto out;
+
1438
+
1439 if (newnode != NULL) {
+
1440 if (hide) {
+
1441 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1442 err = -EBUSY;
+
1443 goto out;
+
1444 }
+
1445 unlink_node(f, newnode);
+
1446 }
+
1447
+
1448 unhash_name(f, node);
+
1449 if (hash_name(f, node, newdir, newname) == -1) {
+
1450 err = -ENOMEM;
+
1451 goto out;
+
1452 }
+
1453
+
1454 if (hide)
+
1455 node->is_hidden = 1;
+
1456
+
1457out:
+
1458 pthread_mutex_unlock(&f->lock);
+
1459 return err;
+
1460}
+
1461
+
1462static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1463 fuse_ino_t newdir, const char *newname)
+
1464{
+
1465 struct node *oldnode;
+
1466 struct node *newnode;
+
1467 int err;
+
1468
+
1469 pthread_mutex_lock(&f->lock);
+
1470 oldnode = lookup_node(f, olddir, oldname);
+
1471 newnode = lookup_node(f, newdir, newname);
+
1472
+
1473 if (oldnode)
+
1474 unhash_name(f, oldnode);
+
1475 if (newnode)
+
1476 unhash_name(f, newnode);
+
1477
+
1478 err = -ENOMEM;
+
1479 if (oldnode) {
+
1480 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1481 goto out;
+
1482 }
+
1483 if (newnode) {
+
1484 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1485 goto out;
+
1486 }
+
1487 err = 0;
+
1488out:
+
1489 pthread_mutex_unlock(&f->lock);
+
1490 return err;
+
1491}
+
1492
+
1493static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1494{
+
1495 if (!f->conf.use_ino)
+
1496 stbuf->st_ino = nodeid;
+
1497 if (f->conf.set_mode) {
+
1498 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1499 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1500 (0777 & ~f->conf.dmask);
+
1501 else if (f->conf.fmask)
+
1502 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1503 (0777 & ~f->conf.fmask);
+
1504 else
+
1505 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1506 (0777 & ~f->conf.umask);
+
1507 }
+
1508 if (f->conf.set_uid)
+
1509 stbuf->st_uid = f->conf.uid;
+
1510 if (f->conf.set_gid)
+
1511 stbuf->st_gid = f->conf.gid;
+
1512}
+
1513
+
1514static struct fuse *req_fuse(fuse_req_t req)
+
1515{
+
1516 return (struct fuse *) fuse_req_userdata(req);
+
1517}
+
1518
+
1519static void fuse_intr_sighandler(int sig)
+
1520{
+
1521 (void) sig;
+
1522 /* Nothing to do */
+
1523}
+
1524
+
1525struct fuse_intr_data {
+
1526 pthread_t id;
+
1527 pthread_cond_t cond;
+
1528 int finished;
+
1529};
+
1530
+
1531static void fuse_interrupt(fuse_req_t req, void *d_)
+
1532{
+
1533 struct fuse_intr_data *d = d_;
+
1534 struct fuse *f = req_fuse(req);
+
1535
+
1536 if (d->id == pthread_self())
+
1537 return;
+
1538
+
1539 pthread_mutex_lock(&f->lock);
+
1540 while (!d->finished) {
+
1541 struct timeval now;
+
1542 struct timespec timeout;
+
1543
+
1544 pthread_kill(d->id, f->conf.intr_signal);
+
1545 gettimeofday(&now, NULL);
+
1546 timeout.tv_sec = now.tv_sec + 1;
+
1547 timeout.tv_nsec = now.tv_usec * 1000;
+
1548 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1549 }
+
1550 pthread_mutex_unlock(&f->lock);
+
1551}
+
1552
+
1553static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1554 struct fuse_intr_data *d)
+
1555{
+
1556 pthread_mutex_lock(&f->lock);
+
1557 d->finished = 1;
+
1558 pthread_cond_broadcast(&d->cond);
+
1559 pthread_mutex_unlock(&f->lock);
+
1560 fuse_req_interrupt_func(req, NULL, NULL);
+
1561 pthread_cond_destroy(&d->cond);
+
1562}
+
1563
+
1564static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1565{
+
1566 d->id = pthread_self();
+
1567 pthread_cond_init(&d->cond, NULL);
+
1568 d->finished = 0;
+
1569 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1570}
+
1571
+
1572static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1573 struct fuse_intr_data *d)
+
1574{
+
1575 if (f->conf.intr)
+
1576 fuse_do_finish_interrupt(f, req, d);
+
1577}
+
1578
+
1579static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1580 struct fuse_intr_data *d)
+
1581{
+
1582 if (f->conf.intr)
+
1583 fuse_do_prepare_interrupt(req, d);
+
1584}
+
1585
+
1586static const char* file_info_string(struct fuse_file_info *fi,
+
1587 char* buf, size_t len)
+
1588{
+
1589 if(fi == NULL)
+
1590 return "NULL";
+
1591 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1592 return buf;
+
1593}
+
1594
+
1595int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1596 struct fuse_file_info *fi)
+
1597{
+
1598 fuse_get_context()->private_data = fs->user_data;
+
1599 if (fs->op.getattr) {
+
1600 if (fs->debug) {
+
1601 char buf[10];
+
1602 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1603 file_info_string(fi, buf, sizeof(buf)),
+
1604 path);
+
1605 }
+
1606 return fs->op.getattr(path, buf, fi);
+
1607 } else {
+
1608 return -ENOSYS;
+
1609 }
+
1610}
+
1611
+
1612int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1613 const char *newpath, unsigned int flags)
+
1614{
+
1615 fuse_get_context()->private_data = fs->user_data;
+
1616 if (fs->op.rename) {
+
1617 if (fs->debug)
+
1618 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1619 flags);
+
1620
+
1621 return fs->op.rename(oldpath, newpath, flags);
+
1622 } else {
+
1623 return -ENOSYS;
+
1624 }
+
1625}
+
1626
+
1627int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1628{
+
1629 fuse_get_context()->private_data = fs->user_data;
+
1630 if (fs->op.unlink) {
+
1631 if (fs->debug)
+
1632 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1633
+
1634 return fs->op.unlink(path);
+
1635 } else {
+
1636 return -ENOSYS;
+
1637 }
+
1638}
+
1639
+
1640int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1641{
+
1642 fuse_get_context()->private_data = fs->user_data;
+
1643 if (fs->op.rmdir) {
+
1644 if (fs->debug)
+
1645 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1646
+
1647 return fs->op.rmdir(path);
+
1648 } else {
+
1649 return -ENOSYS;
+
1650 }
+
1651}
+
1652
+
1653int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1654{
+
1655 fuse_get_context()->private_data = fs->user_data;
+
1656 if (fs->op.symlink) {
+
1657 if (fs->debug)
+
1658 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1659
+
1660 return fs->op.symlink(linkname, path);
+
1661 } else {
+
1662 return -ENOSYS;
+
1663 }
+
1664}
+
1665
+
1666int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1667{
+
1668 fuse_get_context()->private_data = fs->user_data;
+
1669 if (fs->op.link) {
+
1670 if (fs->debug)
+
1671 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1672
+
1673 return fs->op.link(oldpath, newpath);
+
1674 } else {
+
1675 return -ENOSYS;
+
1676 }
+
1677}
+
1678
+
1679int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1680 struct fuse_file_info *fi)
+
1681{
+
1682 fuse_get_context()->private_data = fs->user_data;
+
1683 if (fs->op.release) {
+
1684 if (fs->debug)
+
1685 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1686 fi->flush ? "+flush" : "",
+
1687 (unsigned long long) fi->fh, fi->flags);
+
1688
+
1689 return fs->op.release(path, fi);
+
1690 } else {
+
1691 return 0;
+
1692 }
+
1693}
+
1694
+
1695int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1696 struct fuse_file_info *fi)
+
1697{
+
1698 fuse_get_context()->private_data = fs->user_data;
+
1699 if (fs->op.opendir) {
+
1700 int err;
+
1701
+
1702 if (fs->debug)
+
1703 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1704 path);
+
1705
+
1706 err = fs->op.opendir(path, fi);
+
1707
+
1708 if (fs->debug && !err)
+
1709 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1710 (unsigned long long) fi->fh, fi->flags, path);
+
1711
+
1712 return err;
+
1713 } else {
+
1714 return 0;
+
1715 }
+
1716}
+
1717
+
1718int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1719 struct fuse_file_info *fi)
+
1720{
+
1721 fuse_get_context()->private_data = fs->user_data;
+
1722 if (fs->op.open) {
+
1723 int err;
+
1724
+
1725 if (fs->debug)
+
1726 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1727 path);
+
1728
+
1729 err = fs->op.open(path, fi);
+
1730
+
1731 if (fs->debug && !err)
+
1732 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1733 (unsigned long long) fi->fh, fi->flags, path);
+
1734
+
1735 return err;
+
1736 } else {
+
1737 return 0;
+
1738 }
+
1739}
+
1740
+
1741static void fuse_free_buf(struct fuse_bufvec *buf)
+
1742{
+
1743 if (buf != NULL) {
+
1744 size_t i;
+
1745
+
1746 for (i = 0; i < buf->count; i++)
+
1747 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1748 free(buf->buf[i].mem);
+
1749 free(buf);
+
1750 }
+
1751}
+
1752
+
1753int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1754 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1755 struct fuse_file_info *fi)
+
1756{
+
1757 fuse_get_context()->private_data = fs->user_data;
+
1758 if (fs->op.read || fs->op.read_buf) {
+
1759 int res;
+
1760
+
1761 if (fs->debug)
+
1762 fuse_log(FUSE_LOG_DEBUG,
+
1763 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1764 (unsigned long long) fi->fh,
+
1765 size, (unsigned long long) off, fi->flags);
+
1766
+
1767 if (fs->op.read_buf) {
+
1768 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1769 } else {
+
1770 struct fuse_bufvec *buf;
+
1771 void *mem;
+
1772
+
1773 buf = malloc(sizeof(struct fuse_bufvec));
+
1774 if (buf == NULL)
+
1775 return -ENOMEM;
+
1776
+
1777 mem = malloc(size);
+
1778 if (mem == NULL) {
+
1779 free(buf);
+
1780 return -ENOMEM;
+
1781 }
+
1782 *buf = FUSE_BUFVEC_INIT(size);
+
1783 buf->buf[0].mem = mem;
+
1784 *bufp = buf;
+
1785
+
1786 res = fs->op.read(path, mem, size, off, fi);
+
1787 if (res >= 0)
+
1788 buf->buf[0].size = res;
+
1789 }
+
1790
+
1791 if (fs->debug && res >= 0)
+
1792 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1793 (unsigned long long) fi->fh,
+
1794 fuse_buf_size(*bufp),
+
1795 (unsigned long long) off);
+
1796 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1797 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1798
+
1799 if (res < 0)
+
1800 return res;
+
1801
+
1802 return 0;
+
1803 } else {
+
1804 return -ENOSYS;
+
1805 }
+
1806}
+
1807
+
1808int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1809 off_t off, struct fuse_file_info *fi)
+
1810{
+
1811 fuse_get_context()->private_data = fs->user_data;
+
1812 if (fs->op.read || fs->op.read_buf) {
+
1813 int res;
+
1814
+
1815 if (fs->debug)
+
1816 fuse_log(FUSE_LOG_DEBUG,
+
1817 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1818 (unsigned long long) fi->fh,
+
1819 size, (unsigned long long) off, fi->flags);
+
1820
+
1821 if (fs->op.read_buf) {
+
1822 struct fuse_bufvec *buf = NULL;
+
1823
+
1824 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1825 if (res == 0) {
+
1826 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1827
+
1828 dst.buf[0].mem = mem;
+
1829 res = fuse_buf_copy(&dst, buf, 0);
+
1830 }
+
1831 fuse_free_buf(buf);
+
1832 } else {
+
1833 res = fs->op.read(path, mem, size, off, fi);
+
1834 }
+
1835
+
1836 if (fs->debug && res >= 0)
+
1837 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1838 (unsigned long long) fi->fh,
+
1839 res,
+
1840 (unsigned long long) off);
+
1841 if (res >= 0 && res > (int) size)
+
1842 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1843
+
1844 return res;
+
1845 } else {
+
1846 return -ENOSYS;
+
1847 }
+
1848}
+
1849
+
1850int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1851 struct fuse_bufvec *buf, off_t off,
+
1852 struct fuse_file_info *fi)
+
1853{
+
1854 fuse_get_context()->private_data = fs->user_data;
+
1855 if (fs->op.write_buf || fs->op.write) {
+
1856 int res;
+
1857 size_t size = fuse_buf_size(buf);
+
1858
+
1859 assert(buf->idx == 0 && buf->off == 0);
+
1860 if (fs->debug)
+
1861 fuse_log(FUSE_LOG_DEBUG,
+
1862 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1863 fi->writepage ? "page" : "",
+
1864 (unsigned long long) fi->fh,
+
1865 size,
+
1866 (unsigned long long) off,
+
1867 fi->flags);
+
1868
+
1869 if (fs->op.write_buf) {
+
1870 res = fs->op.write_buf(path, buf, off, fi);
+
1871 } else {
+
1872 void *mem = NULL;
+
1873 struct fuse_buf *flatbuf;
+
1874 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1875
+
1876 if (buf->count == 1 &&
+
1877 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1878 flatbuf = &buf->buf[0];
+
1879 } else {
+
1880 res = -ENOMEM;
+
1881 mem = malloc(size);
+
1882 if (mem == NULL)
+
1883 goto out;
+
1884
+
1885 tmp.buf[0].mem = mem;
+
1886 res = fuse_buf_copy(&tmp, buf, 0);
+
1887 if (res <= 0)
+
1888 goto out_free;
+
1889
+
1890 tmp.buf[0].size = res;
+
1891 flatbuf = &tmp.buf[0];
+
1892 }
+
1893
+
1894 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1895 off, fi);
+
1896out_free:
+
1897 free(mem);
+
1898 }
+
1899out:
+
1900 if (fs->debug && res >= 0)
+
1901 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1902 fi->writepage ? "page" : "",
+
1903 (unsigned long long) fi->fh, res,
+
1904 (unsigned long long) off);
+
1905 if (res > (int) size)
+
1906 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1907
+
1908 return res;
+
1909 } else {
+
1910 return -ENOSYS;
+
1911 }
+
1912}
+
1913
+
1914int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1915 size_t size, off_t off, struct fuse_file_info *fi)
+
1916{
+
1917 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1918
+
1919 bufv.buf[0].mem = (void *) mem;
+
1920
+
1921 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1922}
+
1923
+
1924int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1925 struct fuse_file_info *fi)
+
1926{
+
1927 fuse_get_context()->private_data = fs->user_data;
+
1928 if (fs->op.fsync) {
+
1929 if (fs->debug)
+
1930 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1931 (unsigned long long) fi->fh, datasync);
+
1932
+
1933 return fs->op.fsync(path, datasync, fi);
+
1934 } else {
+
1935 return -ENOSYS;
+
1936 }
+
1937}
+
1938
+
1939int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1940 struct fuse_file_info *fi)
+
1941{
+
1942 fuse_get_context()->private_data = fs->user_data;
+
1943 if (fs->op.fsyncdir) {
+
1944 if (fs->debug)
+
1945 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1946 (unsigned long long) fi->fh, datasync);
+
1947
+
1948 return fs->op.fsyncdir(path, datasync, fi);
+
1949 } else {
+
1950 return -ENOSYS;
+
1951 }
+
1952}
+
1953
+
1954int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1955 struct fuse_file_info *fi)
+
1956{
+
1957 fuse_get_context()->private_data = fs->user_data;
+
1958 if (fs->op.flush) {
+
1959 if (fs->debug)
+
1960 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1961 (unsigned long long) fi->fh);
+
1962
+
1963 return fs->op.flush(path, fi);
+
1964 } else {
+
1965 return -ENOSYS;
+
1966 }
+
1967}
+
1968
+
1969int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1970{
+
1971 fuse_get_context()->private_data = fs->user_data;
+
1972 if (fs->op.statfs) {
+
1973 if (fs->debug)
+
1974 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1975
+
1976 return fs->op.statfs(path, buf);
+
1977 } else {
+
1978 buf->f_namemax = 255;
+
1979 buf->f_bsize = 512;
+
1980 return 0;
+
1981 }
+
1982}
+
1983
+
1984int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1985 struct fuse_file_info *fi)
+
1986{
+
1987 fuse_get_context()->private_data = fs->user_data;
+
1988 if (fs->op.releasedir) {
+
1989 if (fs->debug)
+
1990 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1991 (unsigned long long) fi->fh, fi->flags);
+
1992
+
1993 return fs->op.releasedir(path, fi);
+
1994 } else {
+
1995 return 0;
+
1996 }
+
1997}
+
1998
+
1999int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
2000 fuse_fill_dir_t filler, off_t off,
+
2001 struct fuse_file_info *fi,
+
2002 enum fuse_readdir_flags flags)
+
2003{
+
2004 fuse_get_context()->private_data = fs->user_data;
+
2005 if (fs->op.readdir) {
+
2006 if (fs->debug) {
+
2007 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2008 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2009 (unsigned long long) fi->fh,
+
2010 (unsigned long long) off);
+
2011 }
+
2012
+
2013 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2014 } else {
+
2015 return -ENOSYS;
+
2016 }
+
2017}
+
2018
+
2019int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2020 struct fuse_file_info *fi)
+
2021{
+
2022 fuse_get_context()->private_data = fs->user_data;
+
2023 if (fs->op.create) {
+
2024 int err;
+
2025
+
2026 if (fs->debug)
+
2027 fuse_log(FUSE_LOG_DEBUG,
+
2028 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2029 fi->flags, path, mode,
+
2030 fuse_get_context()->umask);
+
2031
+
2032 err = fs->op.create(path, mode, fi);
+
2033
+
2034 if (fs->debug && !err)
+
2035 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2036 (unsigned long long) fi->fh, fi->flags, path);
+
2037
+
2038 return err;
+
2039 } else {
+
2040 return -ENOSYS;
+
2041 }
+
2042}
+
2043
+
2044int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2045 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2046{
+
2047 fuse_get_context()->private_data = fs->user_data;
+
2048 if (fs->op.lock) {
+
2049 if (fs->debug)
+
2050 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2051 (unsigned long long) fi->fh,
+
2052 (cmd == F_GETLK ? "F_GETLK" :
+
2053 (cmd == F_SETLK ? "F_SETLK" :
+
2054 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2055 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2056 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2057 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2058 "???"))),
+
2059 (unsigned long long) lock->l_start,
+
2060 (unsigned long long) lock->l_len,
+
2061 (unsigned long long) lock->l_pid);
+
2062
+
2063 return fs->op.lock(path, fi, cmd, lock);
+
2064 } else {
+
2065 return -ENOSYS;
+
2066 }
+
2067}
+
2068
+
2069int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2070 struct fuse_file_info *fi, int op)
+
2071{
+
2072 fuse_get_context()->private_data = fs->user_data;
+
2073 if (fs->op.flock) {
+
2074 if (fs->debug) {
+
2075 int xop = op & ~LOCK_NB;
+
2076
+
2077 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2078 (unsigned long long) fi->fh,
+
2079 xop == LOCK_SH ? "LOCK_SH" :
+
2080 (xop == LOCK_EX ? "LOCK_EX" :
+
2081 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2082 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2083 }
+
2084 return fs->op.flock(path, fi, op);
+
2085 } else {
+
2086 return -ENOSYS;
+
2087 }
+
2088}
+
2089
+
2090int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2091 gid_t gid, struct fuse_file_info *fi)
+
2092{
+
2093 fuse_get_context()->private_data = fs->user_data;
+
2094 if (fs->op.chown) {
+
2095 if (fs->debug) {
+
2096 char buf[10];
+
2097 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2098 file_info_string(fi, buf, sizeof(buf)),
+
2099 path, (unsigned long) uid, (unsigned long) gid);
+
2100 }
+
2101 return fs->op.chown(path, uid, gid, fi);
+
2102 } else {
+
2103 return -ENOSYS;
+
2104 }
+
2105}
+
2106
+
2107int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2108 struct fuse_file_info *fi)
+
2109{
+
2110 fuse_get_context()->private_data = fs->user_data;
+
2111 if (fs->op.truncate) {
+
2112 if (fs->debug) {
+
2113 char buf[10];
+
2114 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2115 file_info_string(fi, buf, sizeof(buf)),
+
2116 (unsigned long long) size);
+
2117 }
+
2118 return fs->op.truncate(path, size, fi);
+
2119 } else {
+
2120 return -ENOSYS;
+
2121 }
+
2122}
+
2123
+
2124int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2125 const struct timespec tv[2], struct fuse_file_info *fi)
+
2126{
+
2127 fuse_get_context()->private_data = fs->user_data;
+
2128 if (fs->op.utimens) {
+
2129 if (fs->debug) {
+
2130 char buf[10];
+
2131 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n",
+
2132 file_info_string(fi, buf, sizeof(buf)),
+
2133 path, tv[0].tv_sec, tv[0].tv_nsec,
+
2134 tv[1].tv_sec, tv[1].tv_nsec);
+
2135 }
+
2136 return fs->op.utimens(path, tv, fi);
+
2137 } else {
+
2138 return -ENOSYS;
+
2139 }
+
2140}
+
2141
+
2142int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2143{
+
2144 fuse_get_context()->private_data = fs->user_data;
+
2145 if (fs->op.access) {
+
2146 if (fs->debug)
+
2147 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2148
+
2149 return fs->op.access(path, mask);
+
2150 } else {
+
2151 return -ENOSYS;
+
2152 }
+
2153}
+
2154
+
2155int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2156 size_t len)
+
2157{
+
2158 fuse_get_context()->private_data = fs->user_data;
+
2159 if (fs->op.readlink) {
+
2160 if (fs->debug)
+
2161 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2162 (unsigned long) len);
+
2163
+
2164 return fs->op.readlink(path, buf, len);
+
2165 } else {
+
2166 return -ENOSYS;
+
2167 }
+
2168}
+
2169
+
2170int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2171 dev_t rdev)
+
2172{
+
2173 fuse_get_context()->private_data = fs->user_data;
+
2174 if (fs->op.mknod) {
+
2175 if (fs->debug)
+
2176 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2177 path, mode, (unsigned long long) rdev,
+
2178 fuse_get_context()->umask);
+
2179
+
2180 return fs->op.mknod(path, mode, rdev);
+
2181 } else {
+
2182 return -ENOSYS;
+
2183 }
+
2184}
+
2185
+
2186int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2187{
+
2188 fuse_get_context()->private_data = fs->user_data;
+
2189 if (fs->op.mkdir) {
+
2190 if (fs->debug)
+
2191 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2192 path, mode, fuse_get_context()->umask);
+
2193
+
2194 return fs->op.mkdir(path, mode);
+
2195 } else {
+
2196 return -ENOSYS;
+
2197 }
+
2198}
+
2199
+
2200int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2201 const char *value, size_t size, int flags)
+
2202{
+
2203 fuse_get_context()->private_data = fs->user_data;
+
2204 if (fs->op.setxattr) {
+
2205 if (fs->debug)
+
2206 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2207 path, name, (unsigned long) size, flags);
+
2208
+
2209 return fs->op.setxattr(path, name, value, size, flags);
+
2210 } else {
+
2211 return -ENOSYS;
+
2212 }
+
2213}
+
2214
+
2215int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2216 char *value, size_t size)
+
2217{
+
2218 fuse_get_context()->private_data = fs->user_data;
+
2219 if (fs->op.getxattr) {
+
2220 if (fs->debug)
+
2221 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2222 path, name, (unsigned long) size);
+
2223
+
2224 return fs->op.getxattr(path, name, value, size);
+
2225 } else {
+
2226 return -ENOSYS;
+
2227 }
+
2228}
+
2229
+
2230int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2231 size_t size)
+
2232{
+
2233 fuse_get_context()->private_data = fs->user_data;
+
2234 if (fs->op.listxattr) {
+
2235 if (fs->debug)
+
2236 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2237 path, (unsigned long) size);
+
2238
+
2239 return fs->op.listxattr(path, list, size);
+
2240 } else {
+
2241 return -ENOSYS;
+
2242 }
+
2243}
+
2244
+
2245int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2246 uint64_t *idx)
+
2247{
+
2248 fuse_get_context()->private_data = fs->user_data;
+
2249 if (fs->op.bmap) {
+
2250 if (fs->debug)
+
2251 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2252 path, (unsigned long) blocksize,
+
2253 (unsigned long long) *idx);
+
2254
+
2255 return fs->op.bmap(path, blocksize, idx);
+
2256 } else {
+
2257 return -ENOSYS;
+
2258 }
+
2259}
+
2260
+
2261int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2262{
+
2263 fuse_get_context()->private_data = fs->user_data;
+
2264 if (fs->op.removexattr) {
+
2265 if (fs->debug)
+
2266 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2267
+
2268 return fs->op.removexattr(path, name);
+
2269 } else {
+
2270 return -ENOSYS;
+
2271 }
+
2272}
+
2273
+
2274int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2275 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2276 void *data)
+
2277{
+
2278 fuse_get_context()->private_data = fs->user_data;
+
2279 if (fs->op.ioctl) {
+
2280 if (fs->debug)
+
2281 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2282 (unsigned long long) fi->fh, cmd, flags);
+
2283
+
2284 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2285 } else
+
2286 return -ENOSYS;
+
2287}
+
2288
+
2289int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2290 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2291 unsigned *reventsp)
+
2292{
+
2293 fuse_get_context()->private_data = fs->user_data;
+
2294 if (fs->op.poll) {
+
2295 int res;
+
2296
+
2297 if (fs->debug)
+
2298 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2299 (unsigned long long) fi->fh, ph,
+
2300 fi->poll_events);
+
2301
+
2302 res = fs->op.poll(path, fi, ph, reventsp);
+
2303
+
2304 if (fs->debug && !res)
+
2305 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2306 (unsigned long long) fi->fh, *reventsp);
+
2307
+
2308 return res;
+
2309 } else
+
2310 return -ENOSYS;
+
2311}
+
2312
+
2313int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2314 off_t offset, off_t length, struct fuse_file_info *fi)
+
2315{
+
2316 fuse_get_context()->private_data = fs->user_data;
+
2317 if (fs->op.fallocate) {
+
2318 if (fs->debug)
+
2319 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2320 path,
+
2321 mode,
+
2322 (unsigned long long) offset,
+
2323 (unsigned long long) length);
+
2324
+
2325 return fs->op.fallocate(path, mode, offset, length, fi);
+
2326 } else
+
2327 return -ENOSYS;
+
2328}
+
2329
+
2330ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2331 struct fuse_file_info *fi_in, off_t off_in,
+
2332 const char *path_out,
+
2333 struct fuse_file_info *fi_out, off_t off_out,
+
2334 size_t len, int flags)
+
2335{
+
2336 fuse_get_context()->private_data = fs->user_data;
+
2337 if (fs->op.copy_file_range) {
+
2338 if (fs->debug)
+
2339 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2340 "%s:%llu, length: %llu\n",
+
2341 path_in,
+
2342 (unsigned long long) off_in,
+
2343 path_out,
+
2344 (unsigned long long) off_out,
+
2345 (unsigned long long) len);
+
2346
+
2347 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2348 fi_out, off_out, len, flags);
+
2349 } else
+
2350 return -ENOSYS;
+
2351}
+
2352
+
2353off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2354 struct fuse_file_info *fi)
+
2355{
+
2356 fuse_get_context()->private_data = fs->user_data;
+
2357 if (fs->op.lseek) {
+
2358 if (fs->debug) {
+
2359 char buf[10];
+
2360 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2361 file_info_string(fi, buf, sizeof(buf)),
+
2362 (unsigned long long) off, whence);
+
2363 }
+
2364 return fs->op.lseek(path, off, whence, fi);
+
2365 } else {
+
2366 return -ENOSYS;
+
2367 }
+
2368}
+
2369
+
2370static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2371{
+
2372 struct node *node;
+
2373 int isopen = 0;
+
2374 pthread_mutex_lock(&f->lock);
+
2375 node = lookup_node(f, dir, name);
+
2376 if (node && node->open_count > 0)
+
2377 isopen = 1;
+
2378 pthread_mutex_unlock(&f->lock);
+
2379 return isopen;
+
2380}
+
2381
+
2382static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2383 char *newname, size_t bufsize)
+
2384{
+
2385 struct stat buf;
+
2386 struct node *node;
+
2387 struct node *newnode;
+
2388 char *newpath;
+
2389 int res;
+
2390 int failctr = 10;
+
2391
+
2392 do {
+
2393 pthread_mutex_lock(&f->lock);
+
2394 node = lookup_node(f, dir, oldname);
+
2395 if (node == NULL) {
+
2396 pthread_mutex_unlock(&f->lock);
+
2397 return NULL;
+
2398 }
+
2399 do {
+
2400 f->hidectr ++;
+
2401 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2402 (unsigned int) node->nodeid, f->hidectr);
+
2403 newnode = lookup_node(f, dir, newname);
+
2404 } while(newnode);
+
2405
+
2406 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2407 pthread_mutex_unlock(&f->lock);
+
2408 if (res)
+
2409 break;
+
2410
+
2411 memset(&buf, 0, sizeof(buf));
+
2412 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2413 if (res == -ENOENT)
+
2414 break;
+
2415 free(newpath);
+
2416 newpath = NULL;
+
2417 } while(res == 0 && --failctr);
+
2418
+
2419 return newpath;
+
2420}
+
2421
+
2422static int hide_node(struct fuse *f, const char *oldpath,
+
2423 fuse_ino_t dir, const char *oldname)
+
2424{
+
2425 char newname[64];
+
2426 char *newpath;
+
2427 int err = -EBUSY;
+
2428
+
2429 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2430 if (newpath) {
+
2431 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2432 if (!err)
+
2433 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2434 free(newpath);
+
2435 }
+
2436 return err;
+
2437}
+
2438
+
2439static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2440{
+
2441 return stbuf->st_mtime == ts->tv_sec &&
+
2442 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2443}
+
2444
+
2445#ifndef CLOCK_MONOTONIC
+
2446#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2447#endif
+
2448
+
2449static void curr_time(struct timespec *now)
+
2450{
+
2451 static clockid_t clockid = CLOCK_MONOTONIC;
+
2452 int res = clock_gettime(clockid, now);
+
2453 if (res == -1 && errno == EINVAL) {
+
2454 clockid = CLOCK_REALTIME;
+
2455 res = clock_gettime(clockid, now);
+
2456 }
+
2457 if (res == -1) {
+
2458 perror("fuse: clock_gettime");
+
2459 abort();
+
2460 }
+
2461}
+
2462
+
2463static void update_stat(struct node *node, const struct stat *stbuf)
+
2464{
+
2465 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2466 stbuf->st_size != node->size))
+
2467 node->cache_valid = 0;
+
2468 node->mtime.tv_sec = stbuf->st_mtime;
+
2469 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2470 node->size = stbuf->st_size;
+
2471 curr_time(&node->stat_updated);
+
2472}
+
2473
+
2474static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2475 struct fuse_entry_param *e)
+
2476{
+
2477 struct node *node;
+
2478
+
2479 node = find_node(f, nodeid, name);
+
2480 if (node == NULL)
+
2481 return -ENOMEM;
+
2482
+
2483 e->ino = node->nodeid;
+
2484 e->generation = node->generation;
+
2485 e->entry_timeout = f->conf.entry_timeout;
+
2486 e->attr_timeout = f->conf.attr_timeout;
+
2487 if (f->conf.auto_cache) {
+
2488 pthread_mutex_lock(&f->lock);
+
2489 update_stat(node, &e->attr);
+
2490 pthread_mutex_unlock(&f->lock);
+
2491 }
+
2492 set_stat(f, e->ino, &e->attr);
+
2493 return 0;
+
2494}
+
2495
+
2496static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2497 const char *name, const char *path,
+
2498 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2499{
+
2500 int res;
+
2501
+
2502 memset(e, 0, sizeof(struct fuse_entry_param));
+
2503 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2504 if (res == 0) {
+
2505 res = do_lookup(f, nodeid, name, e);
+
2506 if (res == 0 && f->conf.debug) {
+
2507 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2508 (unsigned long long) e->ino);
+
2509 }
+
2510 }
+
2511 return res;
+
2512}
+
2513
+
2514static struct fuse_context_i *fuse_get_context_internal(void)
+
2515{
+
2516 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2517}
+
2518
+
2519static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2520{
+
2521 struct fuse_context_i *c = fuse_get_context_internal();
+
2522 if (c == NULL) {
+
2523 c = (struct fuse_context_i *)
+
2524 calloc(1, sizeof(struct fuse_context_i));
+
2525 if (c == NULL) {
+
2526 /* This is hard to deal with properly, so just
+
2527 abort. If memory is so low that the
+
2528 context cannot be allocated, there's not
+
2529 much hope for the filesystem anyway */
+
2530 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2531 abort();
+
2532 }
+
2533 pthread_setspecific(fuse_context_key, c);
+
2534 } else {
+
2535 memset(c, 0, sizeof(*c));
+
2536 }
+
2537 c->ctx.fuse = f;
+
2538
+
2539 return c;
+
2540}
+
2541
+
2542static void fuse_freecontext(void *data)
+
2543{
+
2544 free(data);
+
2545}
+
2546
+
2547static int fuse_create_context_key(void)
+
2548{
+
2549 int err = 0;
+
2550 pthread_mutex_lock(&fuse_context_lock);
+
2551 if (!fuse_context_ref) {
+
2552 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2553 if (err) {
+
2554 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2555 strerror(err));
+
2556 pthread_mutex_unlock(&fuse_context_lock);
+
2557 return -1;
+
2558 }
+
2559 }
+
2560 fuse_context_ref++;
+
2561 pthread_mutex_unlock(&fuse_context_lock);
+
2562 return 0;
+
2563}
+
2564
+
2565static void fuse_delete_context_key(void)
+
2566{
+
2567 pthread_mutex_lock(&fuse_context_lock);
+
2568 fuse_context_ref--;
+
2569 if (!fuse_context_ref) {
+
2570 free(pthread_getspecific(fuse_context_key));
+
2571 pthread_key_delete(fuse_context_key);
+
2572 }
+
2573 pthread_mutex_unlock(&fuse_context_lock);
+
2574}
+
2575
+
2576static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2577{
+
2578 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2579 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2580 c->req = req;
+
2581 c->ctx.uid = ctx->uid;
+
2582 c->ctx.gid = ctx->gid;
+
2583 c->ctx.pid = ctx->pid;
+
2584 c->ctx.umask = ctx->umask;
+
2585 return c->ctx.fuse;
+
2586}
+
2587
+
2588static inline void reply_err(fuse_req_t req, int err)
+
2589{
+
2590 /* fuse_reply_err() uses non-negated errno values */
+
2591 fuse_reply_err(req, -err);
+
2592}
+
2593
+
2594static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2595 int err)
+
2596{
+
2597 if (!err) {
+
2598 struct fuse *f = req_fuse(req);
+
2599 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2600 /* Skip forget for negative result */
+
2601 if (e->ino != 0)
+
2602 forget_node(f, e->ino, 1);
+
2603 }
+
2604 } else
+
2605 reply_err(req, err);
+
2606}
+
2607
+
2608void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2609 struct fuse_config *cfg)
+
2610{
+
2611 fuse_get_context()->private_data = fs->user_data;
+
2612 if (!fs->op.write_buf)
+
2613 fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ);
+
2614 if (!fs->op.lock)
+
2615 fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS);
+
2616 if (!fs->op.flock)
+
2617 fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
+
2618 if (fs->op.init) {
+
2619 uint64_t want_ext_default = conn->want_ext;
+
2620 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
+
2621 int rc;
+
2622
+
2623 conn->want = want_default;
+
2624 fs->user_data = fs->op.init(conn, cfg);
+
2625
+
2626 rc = convert_to_conn_want_ext(conn, want_ext_default,
+
2627 want_default);
+
2628
+
2629 if (rc != 0) {
+
2630 /*
+
2631 * This is a grave developer error, but
+
2632 * we cannot return an error here, as the function
+
2633 * signature does not allow it.
+
2634 */
+
2635 fuse_log(
+
2636 FUSE_LOG_ERR,
+
2637 "fuse: Aborting due to invalid conn want flags.\n");
+
2638 _exit(EXIT_FAILURE);
+
2639 }
+
2640 }
+
2641}
+
2642
+
2643static int fuse_init_intr_signal(int signum, int *installed);
+
2644
+
2645static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2646{
+
2647 struct fuse *f = (struct fuse *) data;
+
2648
+
2649 fuse_create_context(f);
+
2650 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
+
2651 fuse_fs_init(f->fs, conn, &f->conf);
+
2652
+
2653 if (f->conf.intr) {
+
2654 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2655 &f->intr_installed) == -1)
+
2656 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2657 } else {
+
2658 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2659 conn->no_interrupt = 1;
+
2660 }
+
2661}
+
2662
+
2663void fuse_fs_destroy(struct fuse_fs *fs)
+
2664{
+
2665 fuse_get_context()->private_data = fs->user_data;
+
2666 if (fs->op.destroy)
+
2667 fs->op.destroy(fs->user_data);
+
2668}
+
2669
+
2670static void fuse_lib_destroy(void *data)
+
2671{
+
2672 struct fuse *f = (struct fuse *) data;
+
2673
+
2674 fuse_create_context(f);
+
2675 fuse_fs_destroy(f->fs);
+
2676}
+
2677
+
2678static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2679 const char *name)
+
2680{
+
2681 struct fuse *f = req_fuse_prepare(req);
+
2682 struct fuse_entry_param e;
+
2683 char *path;
+
2684 int err;
+
2685 struct node *dot = NULL;
+
2686
+
2687 if (name[0] == '.') {
+
2688 int len = strlen(name);
+
2689
+
2690 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2691 pthread_mutex_lock(&f->lock);
+
2692 if (len == 1) {
+
2693 if (f->conf.debug)
+
2694 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2695 dot = get_node_nocheck(f, parent);
+
2696 if (dot == NULL) {
+
2697 pthread_mutex_unlock(&f->lock);
+
2698 reply_entry(req, &e, -ESTALE);
+
2699 return;
+
2700 }
+
2701 dot->refctr++;
+
2702 } else {
+
2703 if (f->conf.debug)
+
2704 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2705 parent = get_node(f, parent)->parent->nodeid;
+
2706 }
+
2707 pthread_mutex_unlock(&f->lock);
+
2708 name = NULL;
+
2709 }
+
2710 }
+
2711
+
2712 err = get_path_name(f, parent, name, &path);
+
2713 if (!err) {
+
2714 struct fuse_intr_data d;
+
2715 if (f->conf.debug)
+
2716 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2717 fuse_prepare_interrupt(f, req, &d);
+
2718 err = lookup_path(f, parent, name, path, &e, NULL);
+
2719 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2720 e.ino = 0;
+
2721 e.entry_timeout = f->conf.negative_timeout;
+
2722 err = 0;
+
2723 }
+
2724 fuse_finish_interrupt(f, req, &d);
+
2725 free_path(f, parent, path);
+
2726 }
+
2727 if (dot) {
+
2728 pthread_mutex_lock(&f->lock);
+
2729 unref_node(f, dot);
+
2730 pthread_mutex_unlock(&f->lock);
+
2731 }
+
2732 reply_entry(req, &e, err);
+
2733}
+
2734
+
2735static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2736{
+
2737 if (f->conf.debug)
+
2738 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2739 (unsigned long long) nlookup);
+
2740 forget_node(f, ino, nlookup);
+
2741}
+
2742
+
2743static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2744{
+
2745 do_forget(req_fuse(req), ino, nlookup);
+
2746 fuse_reply_none(req);
+
2747}
+
2748
+
2749static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2750 struct fuse_forget_data *forgets)
+
2751{
+
2752 struct fuse *f = req_fuse(req);
+
2753 size_t i;
+
2754
+
2755 for (i = 0; i < count; i++)
+
2756 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2757
+
2758 fuse_reply_none(req);
+
2759}
+
2760
+
2761
+
2762static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2763 struct fuse_file_info *fi)
+
2764{
+
2765 struct fuse *f = req_fuse_prepare(req);
+
2766 struct stat buf;
+
2767 char *path;
+
2768 int err;
+
2769
+
2770 memset(&buf, 0, sizeof(buf));
+
2771
+
2772 if (fi != NULL)
+
2773 err = get_path_nullok(f, ino, &path);
+
2774 else
+
2775 err = get_path(f, ino, &path);
+
2776 if (!err) {
+
2777 struct fuse_intr_data d;
+
2778 fuse_prepare_interrupt(f, req, &d);
+
2779 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2780 fuse_finish_interrupt(f, req, &d);
+
2781 free_path(f, ino, path);
+
2782 }
+
2783 if (!err) {
+
2784 struct node *node;
+
2785
+
2786 pthread_mutex_lock(&f->lock);
+
2787 node = get_node(f, ino);
+
2788 if (node->is_hidden && buf.st_nlink > 0)
+
2789 buf.st_nlink--;
+
2790 if (f->conf.auto_cache)
+
2791 update_stat(node, &buf);
+
2792 pthread_mutex_unlock(&f->lock);
+
2793 set_stat(f, ino, &buf);
+
2794 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2795 } else
+
2796 reply_err(req, err);
+
2797}
+
2798
+
2799int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2800 struct fuse_file_info *fi)
+
2801{
+
2802 fuse_get_context()->private_data = fs->user_data;
+
2803 if (fs->op.chmod) {
+
2804 if (fs->debug) {
+
2805 char buf[10];
+
2806 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2807 file_info_string(fi, buf, sizeof(buf)),
+
2808 path, (unsigned long long) mode);
+
2809 }
+
2810 return fs->op.chmod(path, mode, fi);
+
2811 }
+
2812 else
+
2813 return -ENOSYS;
+
2814}
+
2815
+
2816static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2817 int valid, struct fuse_file_info *fi)
+
2818{
+
2819 struct fuse *f = req_fuse_prepare(req);
+
2820 struct stat buf;
+
2821 char *path;
+
2822 int err;
+
2823
+
2824 memset(&buf, 0, sizeof(buf));
+
2825 if (fi != NULL)
+
2826 err = get_path_nullok(f, ino, &path);
+
2827 else
+
2828 err = get_path(f, ino, &path);
+
2829 if (!err) {
+
2830 struct fuse_intr_data d;
+
2831 fuse_prepare_interrupt(f, req, &d);
+
2832 err = 0;
+
2833 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2834 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2835 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2836 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2837 attr->st_uid : (uid_t) -1;
+
2838 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2839 attr->st_gid : (gid_t) -1;
+
2840 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2841 }
+
2842 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2843 err = fuse_fs_truncate(f->fs, path,
+
2844 attr->st_size, fi);
+
2845 }
+
2846#ifdef HAVE_UTIMENSAT
+
2847 if (!err &&
+
2848 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2849 struct timespec tv[2];
+
2850
+
2851 tv[0].tv_sec = 0;
+
2852 tv[1].tv_sec = 0;
+
2853 tv[0].tv_nsec = UTIME_OMIT;
+
2854 tv[1].tv_nsec = UTIME_OMIT;
+
2855
+
2856 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2857 tv[0].tv_nsec = UTIME_NOW;
+
2858 else if (valid & FUSE_SET_ATTR_ATIME)
+
2859 tv[0] = attr->st_atim;
+
2860
+
2861 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2862 tv[1].tv_nsec = UTIME_NOW;
+
2863 else if (valid & FUSE_SET_ATTR_MTIME)
+
2864 tv[1] = attr->st_mtim;
+
2865
+
2866 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2867 } else
+
2868#endif
+
2869 if (!err &&
+
2870 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2871 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2872 struct timespec tv[2];
+
2873 tv[0].tv_sec = attr->st_atime;
+
2874 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2875 tv[1].tv_sec = attr->st_mtime;
+
2876 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2877 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2878 }
+
2879 if (!err) {
+
2880 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2881 }
+
2882 fuse_finish_interrupt(f, req, &d);
+
2883 free_path(f, ino, path);
+
2884 }
+
2885 if (!err) {
+
2886 if (f->conf.auto_cache) {
+
2887 pthread_mutex_lock(&f->lock);
+
2888 update_stat(get_node(f, ino), &buf);
+
2889 pthread_mutex_unlock(&f->lock);
+
2890 }
+
2891 set_stat(f, ino, &buf);
+
2892 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2893 } else
+
2894 reply_err(req, err);
+
2895}
+
2896
+
2897static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2898{
+
2899 struct fuse *f = req_fuse_prepare(req);
+
2900 char *path;
+
2901 int err;
+
2902
+
2903 err = get_path(f, ino, &path);
+
2904 if (!err) {
+
2905 struct fuse_intr_data d;
+
2906
+
2907 fuse_prepare_interrupt(f, req, &d);
+
2908 err = fuse_fs_access(f->fs, path, mask);
+
2909 fuse_finish_interrupt(f, req, &d);
+
2910 free_path(f, ino, path);
+
2911 }
+
2912 reply_err(req, err);
+
2913}
+
2914
+
2915static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2916{
+
2917 struct fuse *f = req_fuse_prepare(req);
+
2918 char linkname[PATH_MAX + 1];
+
2919 char *path;
+
2920 int err;
+
2921
+
2922 err = get_path(f, ino, &path);
+
2923 if (!err) {
+
2924 struct fuse_intr_data d;
+
2925 fuse_prepare_interrupt(f, req, &d);
+
2926 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2927 fuse_finish_interrupt(f, req, &d);
+
2928 free_path(f, ino, path);
+
2929 }
+
2930 if (!err) {
+
2931 linkname[PATH_MAX] = '\0';
+
2932 fuse_reply_readlink(req, linkname);
+
2933 } else
+
2934 reply_err(req, err);
+
2935}
+
2936
+
2937static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2938 mode_t mode, dev_t rdev)
+
2939{
+
2940 struct fuse *f = req_fuse_prepare(req);
+
2941 struct fuse_entry_param e;
+
2942 char *path;
+
2943 int err;
+
2944
+
2945 err = get_path_name(f, parent, name, &path);
+
2946 if (!err) {
+
2947 struct fuse_intr_data d;
+
2948
+
2949 fuse_prepare_interrupt(f, req, &d);
+
2950 err = -ENOSYS;
+
2951 if (S_ISREG(mode)) {
+
2952 struct fuse_file_info fi;
+
2953
+
2954 memset(&fi, 0, sizeof(fi));
+
2955 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2956 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2957 if (!err) {
+
2958 err = lookup_path(f, parent, name, path, &e,
+
2959 &fi);
+
2960 fuse_fs_release(f->fs, path, &fi);
+
2961 }
+
2962 }
+
2963 if (err == -ENOSYS) {
+
2964 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2965 if (!err)
+
2966 err = lookup_path(f, parent, name, path, &e,
+
2967 NULL);
+
2968 }
+
2969 fuse_finish_interrupt(f, req, &d);
+
2970 free_path(f, parent, path);
+
2971 }
+
2972 reply_entry(req, &e, err);
+
2973}
+
2974
+
2975static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2976 mode_t mode)
+
2977{
+
2978 struct fuse *f = req_fuse_prepare(req);
+
2979 struct fuse_entry_param e;
+
2980 char *path;
+
2981 int err;
+
2982
+
2983 err = get_path_name(f, parent, name, &path);
+
2984 if (!err) {
+
2985 struct fuse_intr_data d;
+
2986
+
2987 fuse_prepare_interrupt(f, req, &d);
+
2988 err = fuse_fs_mkdir(f->fs, path, mode);
+
2989 if (!err)
+
2990 err = lookup_path(f, parent, name, path, &e, NULL);
+
2991 fuse_finish_interrupt(f, req, &d);
+
2992 free_path(f, parent, path);
+
2993 }
+
2994 reply_entry(req, &e, err);
+
2995}
+
2996
+
2997static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2998 const char *name)
+
2999{
+
3000 struct fuse *f = req_fuse_prepare(req);
+
3001 struct node *wnode;
+
3002 char *path;
+
3003 int err;
+
3004
+
3005 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3006 if (!err) {
+
3007 struct fuse_intr_data d;
+
3008
+
3009 fuse_prepare_interrupt(f, req, &d);
+
3010 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
3011 err = hide_node(f, path, parent, name);
+
3012 if (!err) {
+
3013 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
3014 if (!is_open(f, parent, wnode->name)) {
+
3015 char *unlinkpath;
+
3016
+
3017 /* get the hidden file path, to unlink it */
+
3018 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
3019 err = fuse_fs_unlink(f->fs, unlinkpath);
+
3020 if (!err)
+
3021 remove_node(f, parent, wnode->name);
+
3022 free(unlinkpath);
+
3023 }
+
3024 }
+
3025 }
+
3026 } else {
+
3027 err = fuse_fs_unlink(f->fs, path);
+
3028 if (!err)
+
3029 remove_node(f, parent, name);
+
3030 }
+
3031 fuse_finish_interrupt(f, req, &d);
+
3032 free_path_wrlock(f, parent, wnode, path);
+
3033 }
+
3034 reply_err(req, err);
+
3035}
+
3036
+
3037static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3038{
+
3039 struct fuse *f = req_fuse_prepare(req);
+
3040 struct node *wnode;
+
3041 char *path;
+
3042 int err;
+
3043
+
3044 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3045 if (!err) {
+
3046 struct fuse_intr_data d;
+
3047
+
3048 fuse_prepare_interrupt(f, req, &d);
+
3049 err = fuse_fs_rmdir(f->fs, path);
+
3050 fuse_finish_interrupt(f, req, &d);
+
3051 if (!err)
+
3052 remove_node(f, parent, name);
+
3053 free_path_wrlock(f, parent, wnode, path);
+
3054 }
+
3055 reply_err(req, err);
+
3056}
+
3057
+
3058static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3059 fuse_ino_t parent, const char *name)
+
3060{
+
3061 struct fuse *f = req_fuse_prepare(req);
+
3062 struct fuse_entry_param e;
+
3063 char *path;
+
3064 int err;
+
3065
+
3066 err = get_path_name(f, parent, name, &path);
+
3067 if (!err) {
+
3068 struct fuse_intr_data d;
+
3069
+
3070 fuse_prepare_interrupt(f, req, &d);
+
3071 err = fuse_fs_symlink(f->fs, linkname, path);
+
3072 if (!err)
+
3073 err = lookup_path(f, parent, name, path, &e, NULL);
+
3074 fuse_finish_interrupt(f, req, &d);
+
3075 free_path(f, parent, path);
+
3076 }
+
3077 reply_entry(req, &e, err);
+
3078}
+
3079
+
3080static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3081 const char *oldname, fuse_ino_t newdir,
+
3082 const char *newname, unsigned int flags)
+
3083{
+
3084 struct fuse *f = req_fuse_prepare(req);
+
3085 char *oldpath;
+
3086 char *newpath;
+
3087 struct node *wnode1;
+
3088 struct node *wnode2;
+
3089 int err;
+
3090
+
3091 err = get_path2(f, olddir, oldname, newdir, newname,
+
3092 &oldpath, &newpath, &wnode1, &wnode2);
+
3093 if (!err) {
+
3094 struct fuse_intr_data d;
+
3095 err = 0;
+
3096 fuse_prepare_interrupt(f, req, &d);
+
3097 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3098 is_open(f, newdir, newname))
+
3099 err = hide_node(f, newpath, newdir, newname);
+
3100 if (!err) {
+
3101 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3102 if (!err) {
+
3103 if (flags & RENAME_EXCHANGE) {
+
3104 err = exchange_node(f, olddir, oldname,
+
3105 newdir, newname);
+
3106 } else {
+
3107 err = rename_node(f, olddir, oldname,
+
3108 newdir, newname, 0);
+
3109 }
+
3110 }
+
3111 }
+
3112 fuse_finish_interrupt(f, req, &d);
+
3113 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3114 }
+
3115 reply_err(req, err);
+
3116}
+
3117
+
3118static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3119 const char *newname)
+
3120{
+
3121 struct fuse *f = req_fuse_prepare(req);
+
3122 struct fuse_entry_param e;
+
3123 char *oldpath;
+
3124 char *newpath;
+
3125 int err;
+
3126
+
3127 err = get_path2(f, ino, NULL, newparent, newname,
+
3128 &oldpath, &newpath, NULL, NULL);
+
3129 if (!err) {
+
3130 struct fuse_intr_data d;
+
3131
+
3132 fuse_prepare_interrupt(f, req, &d);
+
3133 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3134 if (!err)
+
3135 err = lookup_path(f, newparent, newname, newpath,
+
3136 &e, NULL);
+
3137 fuse_finish_interrupt(f, req, &d);
+
3138 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3139 }
+
3140 reply_entry(req, &e, err);
+
3141}
+
3142
+
3143static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3144 struct fuse_file_info *fi)
+
3145{
+
3146 struct node *node;
+
3147 int unlink_hidden = 0;
+
3148
+
3149 fuse_fs_release(f->fs, path, fi);
+
3150
+
3151 pthread_mutex_lock(&f->lock);
+
3152 node = get_node(f, ino);
+
3153 assert(node->open_count > 0);
+
3154 --node->open_count;
+
3155 if (node->is_hidden && !node->open_count) {
+
3156 unlink_hidden = 1;
+
3157 node->is_hidden = 0;
+
3158 }
+
3159 pthread_mutex_unlock(&f->lock);
+
3160
+
3161 if(unlink_hidden) {
+
3162 if (path) {
+
3163 fuse_fs_unlink(f->fs, path);
+
3164 } else if (f->conf.nullpath_ok) {
+
3165 char *unlinkpath;
+
3166
+
3167 if (get_path(f, ino, &unlinkpath) == 0)
+
3168 fuse_fs_unlink(f->fs, unlinkpath);
+
3169
+
3170 free_path(f, ino, unlinkpath);
+
3171 }
+
3172 }
+
3173}
+
3174
+
3175static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3176 const char *name, mode_t mode,
+
3177 struct fuse_file_info *fi)
+
3178{
+
3179 struct fuse *f = req_fuse_prepare(req);
+
3180 struct fuse_intr_data d;
+
3181 struct fuse_entry_param e;
+
3182 char *path;
+
3183 int err;
+
3184
+
3185 err = get_path_name(f, parent, name, &path);
+
3186 if (!err) {
+
3187 fuse_prepare_interrupt(f, req, &d);
+
3188 err = fuse_fs_create(f->fs, path, mode, fi);
+
3189 if (!err) {
+
3190 err = lookup_path(f, parent, name, path, &e, fi);
+
3191 if (err)
+
3192 fuse_fs_release(f->fs, path, fi);
+
3193 else if (!S_ISREG(e.attr.st_mode)) {
+
3194 err = -EIO;
+
3195 fuse_fs_release(f->fs, path, fi);
+
3196 forget_node(f, e.ino, 1);
+
3197 } else {
+
3198 if (f->conf.direct_io)
+
3199 fi->direct_io = 1;
+
3200 if (f->conf.kernel_cache)
+
3201 fi->keep_cache = 1;
+
3202 if (fi->direct_io &&
+
3203 f->conf.parallel_direct_writes)
+
3204 fi->parallel_direct_writes = 1;
+
3205 }
+
3206 }
+
3207 fuse_finish_interrupt(f, req, &d);
+
3208 }
+
3209 if (!err) {
+
3210 pthread_mutex_lock(&f->lock);
+
3211 get_node(f, e.ino)->open_count++;
+
3212 pthread_mutex_unlock(&f->lock);
+
3213 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3214 /* The open syscall was interrupted, so it
+
3215 must be cancelled */
+
3216 fuse_do_release(f, e.ino, path, fi);
+
3217 forget_node(f, e.ino, 1);
+
3218 }
+
3219 } else {
+
3220 reply_err(req, err);
+
3221 }
+
3222
+
3223 free_path(f, parent, path);
+
3224}
+
3225
+
3226static double diff_timespec(const struct timespec *t1,
+
3227 const struct timespec *t2)
+
3228{
+
3229 return (t1->tv_sec - t2->tv_sec) +
+
3230 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3231}
+
3232
+
3233static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3234 struct fuse_file_info *fi)
+
3235{
+
3236 struct node *node;
+
3237
+
3238 pthread_mutex_lock(&f->lock);
+
3239 node = get_node(f, ino);
+
3240 if (node->cache_valid) {
+
3241 struct timespec now;
+
3242
+
3243 curr_time(&now);
+
3244 if (diff_timespec(&now, &node->stat_updated) >
+
3245 f->conf.ac_attr_timeout) {
+
3246 struct stat stbuf;
+
3247 int err;
+
3248 pthread_mutex_unlock(&f->lock);
+
3249 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3250 pthread_mutex_lock(&f->lock);
+
3251 if (!err)
+
3252 update_stat(node, &stbuf);
+
3253 else
+
3254 node->cache_valid = 0;
+
3255 }
+
3256 }
+
3257 if (node->cache_valid)
+
3258 fi->keep_cache = 1;
+
3259
+
3260 node->cache_valid = 1;
+
3261 pthread_mutex_unlock(&f->lock);
+
3262}
+
3263
+
3264static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3265 struct fuse_file_info *fi)
+
3266{
+
3267 struct fuse *f = req_fuse_prepare(req);
+
3268 struct fuse_intr_data d;
+
3269 char *path;
+
3270 int err;
+
3271
+
3272 err = get_path(f, ino, &path);
+
3273 if (!err) {
+
3274 fuse_prepare_interrupt(f, req, &d);
+
3275 err = fuse_fs_open(f->fs, path, fi);
+
3276 if (!err) {
+
3277 if (f->conf.direct_io)
+
3278 fi->direct_io = 1;
+
3279 if (f->conf.kernel_cache)
+
3280 fi->keep_cache = 1;
+
3281
+
3282 if (f->conf.auto_cache)
+
3283 open_auto_cache(f, ino, path, fi);
+
3284
+
3285 if (f->conf.no_rofd_flush &&
+
3286 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3287 fi->noflush = 1;
+
3288
+
3289 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3290 fi->parallel_direct_writes = 1;
+
3291
+
3292 }
+
3293 fuse_finish_interrupt(f, req, &d);
+
3294 }
+
3295 if (!err) {
+
3296 pthread_mutex_lock(&f->lock);
+
3297 get_node(f, ino)->open_count++;
+
3298 pthread_mutex_unlock(&f->lock);
+
3299 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3300 /* The open syscall was interrupted, so it
+
3301 must be cancelled */
+
3302 fuse_do_release(f, ino, path, fi);
+
3303 }
+
3304 } else
+
3305 reply_err(req, err);
+
3306
+
3307 free_path(f, ino, path);
+
3308}
+
3309
+
3310static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3311 off_t off, struct fuse_file_info *fi)
+
3312{
+
3313 struct fuse *f = req_fuse_prepare(req);
+
3314 struct fuse_bufvec *buf = NULL;
+
3315 char *path;
+
3316 int res;
+
3317
+
3318 res = get_path_nullok(f, ino, &path);
+
3319 if (res == 0) {
+
3320 struct fuse_intr_data d;
+
3321
+
3322 fuse_prepare_interrupt(f, req, &d);
+
3323 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3324 fuse_finish_interrupt(f, req, &d);
+
3325 free_path(f, ino, path);
+
3326 }
+
3327
+
3328 if (res == 0)
+ +
3330 else
+
3331 reply_err(req, res);
+
3332
+
3333 fuse_free_buf(buf);
+
3334}
+
3335
+
3336static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3337 struct fuse_bufvec *buf, off_t off,
+
3338 struct fuse_file_info *fi)
+
3339{
+
3340 struct fuse *f = req_fuse_prepare(req);
+
3341 char *path;
+
3342 int res;
+
3343
+
3344 res = get_path_nullok(f, ino, &path);
+
3345 if (res == 0) {
+
3346 struct fuse_intr_data d;
+
3347
+
3348 fuse_prepare_interrupt(f, req, &d);
+
3349 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3350 fuse_finish_interrupt(f, req, &d);
+
3351 free_path(f, ino, path);
+
3352 }
+
3353
+
3354 if (res >= 0)
+
3355 fuse_reply_write(req, res);
+
3356 else
+
3357 reply_err(req, res);
+
3358}
+
3359
+
3360static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3361 struct fuse_file_info *fi)
+
3362{
+
3363 struct fuse *f = req_fuse_prepare(req);
+
3364 char *path;
+
3365 int err;
+
3366
+
3367 err = get_path_nullok(f, ino, &path);
+
3368 if (!err) {
+
3369 struct fuse_intr_data d;
+
3370
+
3371 fuse_prepare_interrupt(f, req, &d);
+
3372 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3373 fuse_finish_interrupt(f, req, &d);
+
3374 free_path(f, ino, path);
+
3375 }
+
3376 reply_err(req, err);
+
3377}
+
3378
+
3379static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3380 struct fuse_file_info *fi)
+
3381{
+
3382 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3383 memset(fi, 0, sizeof(struct fuse_file_info));
+
3384 fi->fh = dh->fh;
+
3385 return dh;
+
3386}
+
3387
+
3388static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3389 struct fuse_file_info *llfi)
+
3390{
+
3391 struct fuse *f = req_fuse_prepare(req);
+
3392 struct fuse_intr_data d;
+
3393 struct fuse_dh *dh;
+
3394 struct fuse_file_info fi;
+
3395 char *path;
+
3396 int err;
+
3397
+
3398 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3399 if (dh == NULL) {
+
3400 reply_err(req, -ENOMEM);
+
3401 return;
+
3402 }
+
3403 memset(dh, 0, sizeof(struct fuse_dh));
+
3404 dh->fuse = f;
+
3405 dh->contents = NULL;
+
3406 dh->first = NULL;
+
3407 dh->len = 0;
+
3408 dh->filled = 0;
+
3409 dh->nodeid = ino;
+
3410 pthread_mutex_init(&dh->lock, NULL);
+
3411
+
3412 llfi->fh = (uintptr_t) dh;
+
3413
+
3414 memset(&fi, 0, sizeof(fi));
+
3415 fi.flags = llfi->flags;
+
3416
+
3417 err = get_path(f, ino, &path);
+
3418 if (!err) {
+
3419 fuse_prepare_interrupt(f, req, &d);
+
3420 err = fuse_fs_opendir(f->fs, path, &fi);
+
3421 fuse_finish_interrupt(f, req, &d);
+
3422 dh->fh = fi.fh;
+
3423 llfi->cache_readdir = fi.cache_readdir;
+
3424 llfi->keep_cache = fi.keep_cache;
+
3425 }
+
3426 if (!err) {
+
3427 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3428 /* The opendir syscall was interrupted, so it
+
3429 must be cancelled */
+
3430 fuse_fs_releasedir(f->fs, path, &fi);
+
3431 pthread_mutex_destroy(&dh->lock);
+
3432 free(dh);
+
3433 }
+
3434 } else {
+
3435 reply_err(req, err);
+
3436 pthread_mutex_destroy(&dh->lock);
+
3437 free(dh);
+
3438 }
+
3439 free_path(f, ino, path);
+
3440}
+
3441
+
3442static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3443{
+
3444 if (minsize > dh->size) {
+
3445 char *newptr;
+
3446 unsigned newsize = dh->size;
+
3447 if (!newsize)
+
3448 newsize = 1024;
+
3449 while (newsize < minsize) {
+
3450 if (newsize >= 0x80000000)
+
3451 newsize = 0xffffffff;
+
3452 else
+
3453 newsize *= 2;
+
3454 }
+
3455
+
3456 newptr = (char *) realloc(dh->contents, newsize);
+
3457 if (!newptr) {
+
3458 dh->error = -ENOMEM;
+
3459 return -1;
+
3460 }
+
3461 dh->contents = newptr;
+
3462 dh->size = newsize;
+
3463 }
+
3464 return 0;
+
3465}
+
3466
+
3467static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3468 struct stat *st, enum fuse_fill_dir_flags flags)
+
3469{
+
3470 struct fuse_direntry *de;
+
3471
+
3472 de = malloc(sizeof(struct fuse_direntry));
+
3473 if (!de) {
+
3474 dh->error = -ENOMEM;
+
3475 return -1;
+
3476 }
+
3477 de->name = strdup(name);
+
3478 if (!de->name) {
+
3479 dh->error = -ENOMEM;
+
3480 free(de);
+
3481 return -1;
+
3482 }
+
3483 de->flags = flags;
+
3484 de->stat = *st;
+
3485 de->next = NULL;
+
3486
+
3487 *dh->last = de;
+
3488 dh->last = &de->next;
+
3489
+
3490 return 0;
+
3491}
+
3492
+
3493static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3494 const char *name)
+
3495{
+
3496 struct node *node;
+
3497 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3498
+
3499 pthread_mutex_lock(&f->lock);
+
3500 node = lookup_node(f, parent, name);
+
3501 if (node)
+
3502 res = node->nodeid;
+
3503 pthread_mutex_unlock(&f->lock);
+
3504
+
3505 return res;
+
3506}
+
3507
+
3508static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3509 off_t off, enum fuse_fill_dir_flags flags)
+
3510{
+
3511 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3512 struct stat stbuf;
+
3513
+
3514 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3515 dh->error = -EIO;
+
3516 return 1;
+
3517 }
+
3518
+
3519 if (statp)
+
3520 stbuf = *statp;
+
3521 else {
+
3522 memset(&stbuf, 0, sizeof(stbuf));
+
3523 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3524 }
+
3525
+
3526 if (!dh->fuse->conf.use_ino) {
+
3527 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3528 if (dh->fuse->conf.readdir_ino) {
+
3529 stbuf.st_ino = (ino_t)
+
3530 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3531 }
+
3532 }
+
3533
+
3534 if (off) {
+
3535 size_t newlen;
+
3536
+
3537 if (dh->filled) {
+
3538 dh->error = -EIO;
+
3539 return 1;
+
3540 }
+
3541
+
3542 if (dh->first) {
+
3543 dh->error = -EIO;
+
3544 return 1;
+
3545 }
+
3546
+
3547 if (extend_contents(dh, dh->needlen) == -1)
+
3548 return 1;
+
3549
+
3550 newlen = dh->len +
+
3551 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3552 dh->needlen - dh->len, name,
+
3553 &stbuf, off);
+
3554 if (newlen > dh->needlen)
+
3555 return 1;
+
3556
+
3557 dh->len = newlen;
+
3558 } else {
+
3559 dh->filled = 1;
+
3560
+
3561 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3562 return 1;
+
3563 }
+
3564 return 0;
+
3565}
+
3566
+
3567static int is_dot_or_dotdot(const char *name)
+
3568{
+
3569 return name[0] == '.' && (name[1] == '\0' ||
+
3570 (name[1] == '.' && name[2] == '\0'));
+
3571}
+
3572
+
3573static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3574 off_t off, enum fuse_fill_dir_flags flags)
+
3575{
+
3576 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3577 struct fuse_entry_param e = {
+
3578 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3579 .ino = 0,
+
3580 };
+
3581 struct fuse *f = dh->fuse;
+
3582 int res;
+
3583
+
3584 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3585 dh->error = -EIO;
+
3586 return 1;
+
3587 }
+
3588
+
3589 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3590 e.attr = *statp;
+
3591 } else {
+
3592 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3593 if (statp) {
+
3594 e.attr.st_mode = statp->st_mode;
+
3595 if (f->conf.use_ino)
+
3596 e.attr.st_ino = statp->st_ino;
+
3597 }
+
3598 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3599 e.attr.st_ino = (ino_t)
+
3600 lookup_nodeid(f, dh->nodeid, name);
+
3601 }
+
3602 }
+
3603
+
3604 if (off) {
+
3605 size_t newlen;
+
3606
+
3607 if (dh->filled) {
+
3608 dh->error = -EIO;
+
3609 return 1;
+
3610 }
+
3611
+
3612 if (dh->first) {
+
3613 dh->error = -EIO;
+
3614 return 1;
+
3615 }
+
3616 if (extend_contents(dh, dh->needlen) == -1)
+
3617 return 1;
+
3618
+
3619 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3620 if (!is_dot_or_dotdot(name)) {
+
3621 res = do_lookup(f, dh->nodeid, name, &e);
+
3622 if (res) {
+
3623 dh->error = res;
+
3624 return 1;
+
3625 }
+
3626 }
+
3627 }
+
3628
+
3629 newlen = dh->len +
+
3630 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3631 dh->needlen - dh->len, name,
+
3632 &e, off);
+
3633 if (newlen > dh->needlen)
+
3634 return 1;
+
3635 dh->len = newlen;
+
3636 } else {
+
3637 dh->filled = 1;
+
3638
+
3639 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3640 return 1;
+
3641 }
+
3642
+
3643 return 0;
+
3644}
+
3645
+
3646static void free_direntries(struct fuse_direntry *de)
+
3647{
+
3648 while (de) {
+
3649 struct fuse_direntry *next = de->next;
+
3650 free(de->name);
+
3651 free(de);
+
3652 de = next;
+
3653 }
+
3654}
+
3655
+
3656static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3657 size_t size, off_t off, struct fuse_dh *dh,
+
3658 struct fuse_file_info *fi,
+
3659 enum fuse_readdir_flags flags)
+
3660{
+
3661 char *path;
+
3662 int err;
+
3663
+
3664 if (f->fs->op.readdir)
+
3665 err = get_path_nullok(f, ino, &path);
+
3666 else
+
3667 err = get_path(f, ino, &path);
+
3668 if (!err) {
+
3669 struct fuse_intr_data d;
+
3670 fuse_fill_dir_t filler = fill_dir;
+
3671
+
3672 if (flags & FUSE_READDIR_PLUS)
+
3673 filler = fill_dir_plus;
+
3674
+
3675 free_direntries(dh->first);
+
3676 dh->first = NULL;
+
3677 dh->last = &dh->first;
+
3678 dh->len = 0;
+
3679 dh->error = 0;
+
3680 dh->needlen = size;
+
3681 dh->filled = 0;
+
3682 dh->req = req;
+
3683 fuse_prepare_interrupt(f, req, &d);
+
3684 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3685 fuse_finish_interrupt(f, req, &d);
+
3686 dh->req = NULL;
+
3687 if (!err)
+
3688 err = dh->error;
+
3689 if (err)
+
3690 dh->filled = 0;
+
3691 free_path(f, ino, path);
+
3692 }
+
3693 return err;
+
3694}
+
3695
+
3696static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3697 off_t off, enum fuse_readdir_flags flags)
+
3698{
+
3699 off_t pos;
+
3700 struct fuse_direntry *de = dh->first;
+
3701 int res;
+
3702
+
3703 dh->len = 0;
+
3704
+
3705 if (extend_contents(dh, dh->needlen) == -1)
+
3706 return dh->error;
+
3707
+
3708 for (pos = 0; pos < off; pos++) {
+
3709 if (!de)
+
3710 break;
+
3711
+
3712 de = de->next;
+
3713 }
+
3714 while (de) {
+
3715 char *p = dh->contents + dh->len;
+
3716 unsigned rem = dh->needlen - dh->len;
+
3717 unsigned thislen;
+
3718 unsigned newlen;
+
3719 pos++;
+
3720
+
3721 if (flags & FUSE_READDIR_PLUS) {
+
3722 struct fuse_entry_param e = {
+
3723 .ino = 0,
+
3724 .attr = de->stat,
+
3725 };
+
3726
+
3727 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3728 !is_dot_or_dotdot(de->name)) {
+
3729 res = do_lookup(dh->fuse, dh->nodeid,
+
3730 de->name, &e);
+
3731 if (res) {
+
3732 dh->error = res;
+
3733 return 1;
+
3734 }
+
3735 }
+
3736
+
3737 thislen = fuse_add_direntry_plus(req, p, rem,
+
3738 de->name, &e, pos);
+
3739 } else {
+
3740 thislen = fuse_add_direntry(req, p, rem,
+
3741 de->name, &de->stat, pos);
+
3742 }
+
3743 newlen = dh->len + thislen;
+
3744 if (newlen > dh->needlen)
+
3745 break;
+
3746 dh->len = newlen;
+
3747 de = de->next;
+
3748 }
+
3749 return 0;
+
3750}
+
3751
+
3752static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3753 off_t off, struct fuse_file_info *llfi,
+
3754 enum fuse_readdir_flags flags)
+
3755{
+
3756 struct fuse *f = req_fuse_prepare(req);
+
3757 struct fuse_file_info fi;
+
3758 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3759 int err;
+
3760
+
3761 pthread_mutex_lock(&dh->lock);
+
3762 /* According to SUS, directory contents need to be refreshed on
+
3763 rewinddir() */
+
3764 if (!off)
+
3765 dh->filled = 0;
+
3766
+
3767 if (!dh->filled) {
+
3768 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3769 if (err) {
+
3770 reply_err(req, err);
+
3771 goto out;
+
3772 }
+
3773 }
+
3774 if (dh->filled) {
+
3775 dh->needlen = size;
+
3776 err = readdir_fill_from_list(req, dh, off, flags);
+
3777 if (err) {
+
3778 reply_err(req, err);
+
3779 goto out;
+
3780 }
+
3781 }
+
3782 fuse_reply_buf(req, dh->contents, dh->len);
+
3783out:
+
3784 pthread_mutex_unlock(&dh->lock);
+
3785}
+
3786
+
3787static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3788 off_t off, struct fuse_file_info *llfi)
+
3789{
+
3790 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3791}
+
3792
+
3793static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3794 off_t off, struct fuse_file_info *llfi)
+
3795{
+
3796 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3797}
+
3798
+
3799static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3800 struct fuse_file_info *llfi)
+
3801{
+
3802 struct fuse *f = req_fuse_prepare(req);
+
3803 struct fuse_intr_data d;
+
3804 struct fuse_file_info fi;
+
3805 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3806 char *path;
+
3807
+
3808 get_path_nullok(f, ino, &path);
+
3809
+
3810 fuse_prepare_interrupt(f, req, &d);
+
3811 fuse_fs_releasedir(f->fs, path, &fi);
+
3812 fuse_finish_interrupt(f, req, &d);
+
3813 free_path(f, ino, path);
+
3814
+
3815 pthread_mutex_lock(&dh->lock);
+
3816 pthread_mutex_unlock(&dh->lock);
+
3817 pthread_mutex_destroy(&dh->lock);
+
3818 free_direntries(dh->first);
+
3819 free(dh->contents);
+
3820 free(dh);
+
3821 reply_err(req, 0);
+
3822}
+
3823
+
3824static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3825 struct fuse_file_info *llfi)
+
3826{
+
3827 struct fuse *f = req_fuse_prepare(req);
+
3828 struct fuse_file_info fi;
+
3829 char *path;
+
3830 int err;
+
3831
+
3832 get_dirhandle(llfi, &fi);
+
3833
+
3834 err = get_path_nullok(f, ino, &path);
+
3835 if (!err) {
+
3836 struct fuse_intr_data d;
+
3837 fuse_prepare_interrupt(f, req, &d);
+
3838 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3839 fuse_finish_interrupt(f, req, &d);
+
3840 free_path(f, ino, path);
+
3841 }
+
3842 reply_err(req, err);
+
3843}
+
3844
+
3845static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3846{
+
3847 struct fuse *f = req_fuse_prepare(req);
+
3848 struct statvfs buf;
+
3849 char *path = NULL;
+
3850 int err = 0;
+
3851
+
3852 memset(&buf, 0, sizeof(buf));
+
3853 if (ino)
+
3854 err = get_path(f, ino, &path);
+
3855
+
3856 if (!err) {
+
3857 struct fuse_intr_data d;
+
3858 fuse_prepare_interrupt(f, req, &d);
+
3859 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3860 fuse_finish_interrupt(f, req, &d);
+
3861 free_path(f, ino, path);
+
3862 }
+
3863
+
3864 if (!err)
+
3865 fuse_reply_statfs(req, &buf);
+
3866 else
+
3867 reply_err(req, err);
+
3868}
+
3869
+
3870static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3871 const char *value, size_t size, int flags)
+
3872{
+
3873 struct fuse *f = req_fuse_prepare(req);
+
3874 char *path;
+
3875 int err;
+
3876
+
3877 err = get_path(f, ino, &path);
+
3878 if (!err) {
+
3879 struct fuse_intr_data d;
+
3880 fuse_prepare_interrupt(f, req, &d);
+
3881 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3882 fuse_finish_interrupt(f, req, &d);
+
3883 free_path(f, ino, path);
+
3884 }
+
3885 reply_err(req, err);
+
3886}
+
3887
+
3888static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3889 const char *name, char *value, size_t size)
+
3890{
+
3891 int err;
+
3892 char *path;
+
3893
+
3894 err = get_path(f, ino, &path);
+
3895 if (!err) {
+
3896 struct fuse_intr_data d;
+
3897 fuse_prepare_interrupt(f, req, &d);
+
3898 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3899 fuse_finish_interrupt(f, req, &d);
+
3900 free_path(f, ino, path);
+
3901 }
+
3902 return err;
+
3903}
+
3904
+
3905static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3906 size_t size)
+
3907{
+
3908 struct fuse *f = req_fuse_prepare(req);
+
3909 int res;
+
3910
+
3911 if (size) {
+
3912 char *value = (char *) malloc(size);
+
3913 if (value == NULL) {
+
3914 reply_err(req, -ENOMEM);
+
3915 return;
+
3916 }
+
3917 res = common_getxattr(f, req, ino, name, value, size);
+
3918 if (res > 0)
+
3919 fuse_reply_buf(req, value, res);
+
3920 else
+
3921 reply_err(req, res);
+
3922 free(value);
+
3923 } else {
+
3924 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3925 if (res >= 0)
+
3926 fuse_reply_xattr(req, res);
+
3927 else
+
3928 reply_err(req, res);
+
3929 }
+
3930}
+
3931
+
3932static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3933 char *list, size_t size)
+
3934{
+
3935 char *path;
+
3936 int err;
+
3937
+
3938 err = get_path(f, ino, &path);
+
3939 if (!err) {
+
3940 struct fuse_intr_data d;
+
3941 fuse_prepare_interrupt(f, req, &d);
+
3942 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3943 fuse_finish_interrupt(f, req, &d);
+
3944 free_path(f, ino, path);
+
3945 }
+
3946 return err;
+
3947}
+
3948
+
3949static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3950{
+
3951 struct fuse *f = req_fuse_prepare(req);
+
3952 int res;
+
3953
+
3954 if (size) {
+
3955 char *list = (char *) malloc(size);
+
3956 if (list == NULL) {
+
3957 reply_err(req, -ENOMEM);
+
3958 return;
+
3959 }
+
3960 res = common_listxattr(f, req, ino, list, size);
+
3961 if (res > 0)
+
3962 fuse_reply_buf(req, list, res);
+
3963 else
+
3964 reply_err(req, res);
+
3965 free(list);
+
3966 } else {
+
3967 res = common_listxattr(f, req, ino, NULL, 0);
+
3968 if (res >= 0)
+
3969 fuse_reply_xattr(req, res);
+
3970 else
+
3971 reply_err(req, res);
+
3972 }
+
3973}
+
3974
+
3975static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3976 const char *name)
+
3977{
+
3978 struct fuse *f = req_fuse_prepare(req);
+
3979 char *path;
+
3980 int err;
+
3981
+
3982 err = get_path(f, ino, &path);
+
3983 if (!err) {
+
3984 struct fuse_intr_data d;
+
3985 fuse_prepare_interrupt(f, req, &d);
+
3986 err = fuse_fs_removexattr(f->fs, path, name);
+
3987 fuse_finish_interrupt(f, req, &d);
+
3988 free_path(f, ino, path);
+
3989 }
+
3990 reply_err(req, err);
+
3991}
+
3992
+
3993static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3994{
+
3995 struct lock *l;
+
3996
+
3997 for (l = node->locks; l; l = l->next)
+
3998 if (l->owner != lock->owner &&
+
3999 lock->start <= l->end && l->start <= lock->end &&
+
4000 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
4001 break;
+
4002
+
4003 return l;
+
4004}
+
4005
+
4006static void delete_lock(struct lock **lockp)
+
4007{
+
4008 struct lock *l = *lockp;
+
4009 *lockp = l->next;
+
4010 free(l);
+
4011}
+
4012
+
4013static void insert_lock(struct lock **pos, struct lock *lock)
+
4014{
+
4015 lock->next = *pos;
+
4016 *pos = lock;
+
4017}
+
4018
+
4019static int locks_insert(struct node *node, struct lock *lock)
+
4020{
+
4021 struct lock **lp;
+
4022 struct lock *newl1 = NULL;
+
4023 struct lock *newl2 = NULL;
+
4024
+
4025 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4026 lock->end != OFFSET_MAX) {
+
4027 newl1 = malloc(sizeof(struct lock));
+
4028 newl2 = malloc(sizeof(struct lock));
+
4029
+
4030 if (!newl1 || !newl2) {
+
4031 free(newl1);
+
4032 free(newl2);
+
4033 return -ENOLCK;
+
4034 }
+
4035 }
+
4036
+
4037 for (lp = &node->locks; *lp;) {
+
4038 struct lock *l = *lp;
+
4039 if (l->owner != lock->owner)
+
4040 goto skip;
+
4041
+
4042 if (lock->type == l->type) {
+
4043 if (l->end < lock->start - 1)
+
4044 goto skip;
+
4045 if (lock->end < l->start - 1)
+
4046 break;
+
4047 if (l->start <= lock->start && lock->end <= l->end)
+
4048 goto out;
+
4049 if (l->start < lock->start)
+
4050 lock->start = l->start;
+
4051 if (lock->end < l->end)
+
4052 lock->end = l->end;
+
4053 goto delete;
+
4054 } else {
+
4055 if (l->end < lock->start)
+
4056 goto skip;
+
4057 if (lock->end < l->start)
+
4058 break;
+
4059 if (lock->start <= l->start && l->end <= lock->end)
+
4060 goto delete;
+
4061 if (l->end <= lock->end) {
+
4062 l->end = lock->start - 1;
+
4063 goto skip;
+
4064 }
+
4065 if (lock->start <= l->start) {
+
4066 l->start = lock->end + 1;
+
4067 break;
+
4068 }
+
4069 *newl2 = *l;
+
4070 newl2->start = lock->end + 1;
+
4071 l->end = lock->start - 1;
+
4072 insert_lock(&l->next, newl2);
+
4073 newl2 = NULL;
+
4074 }
+
4075 skip:
+
4076 lp = &l->next;
+
4077 continue;
+
4078
+
4079 delete:
+
4080 delete_lock(lp);
+
4081 }
+
4082 if (lock->type != F_UNLCK) {
+
4083 *newl1 = *lock;
+
4084 insert_lock(lp, newl1);
+
4085 newl1 = NULL;
+
4086 }
+
4087out:
+
4088 free(newl1);
+
4089 free(newl2);
+
4090 return 0;
+
4091}
+
4092
+
4093static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4094{
+
4095 memset(lock, 0, sizeof(struct lock));
+
4096 lock->type = flock->l_type;
+
4097 lock->start = flock->l_start;
+
4098 lock->end =
+
4099 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4100 lock->pid = flock->l_pid;
+
4101}
+
4102
+
4103static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4104{
+
4105 flock->l_type = lock->type;
+
4106 flock->l_start = lock->start;
+
4107 flock->l_len =
+
4108 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4109 flock->l_pid = lock->pid;
+
4110}
+
4111
+
4112static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4113 const char *path, struct fuse_file_info *fi)
+
4114{
+
4115 struct fuse_intr_data d;
+
4116 struct flock lock;
+
4117 struct lock l;
+
4118 int err;
+
4119 int errlock;
+
4120
+
4121 fuse_prepare_interrupt(f, req, &d);
+
4122 memset(&lock, 0, sizeof(lock));
+
4123 lock.l_type = F_UNLCK;
+
4124 lock.l_whence = SEEK_SET;
+
4125 err = fuse_fs_flush(f->fs, path, fi);
+
4126 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4127 fuse_finish_interrupt(f, req, &d);
+
4128
+
4129 if (errlock != -ENOSYS) {
+
4130 flock_to_lock(&lock, &l);
+
4131 l.owner = fi->lock_owner;
+
4132 pthread_mutex_lock(&f->lock);
+
4133 locks_insert(get_node(f, ino), &l);
+
4134 pthread_mutex_unlock(&f->lock);
+
4135
+
4136 /* if op.lock() is defined FLUSH is needed regardless
+
4137 of op.flush() */
+
4138 if (err == -ENOSYS)
+
4139 err = 0;
+
4140 }
+
4141 return err;
+
4142}
+
4143
+
4144static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4145 struct fuse_file_info *fi)
+
4146{
+
4147 struct fuse *f = req_fuse_prepare(req);
+
4148 struct fuse_intr_data d;
+
4149 char *path;
+
4150 int err = 0;
+
4151
+
4152 get_path_nullok(f, ino, &path);
+
4153 if (fi->flush) {
+
4154 err = fuse_flush_common(f, req, ino, path, fi);
+
4155 if (err == -ENOSYS)
+
4156 err = 0;
+
4157 }
+
4158
+
4159 fuse_prepare_interrupt(f, req, &d);
+
4160 fuse_do_release(f, ino, path, fi);
+
4161 fuse_finish_interrupt(f, req, &d);
+
4162 free_path(f, ino, path);
+
4163
+
4164 reply_err(req, err);
+
4165}
+
4166
+
4167static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4168 struct fuse_file_info *fi)
+
4169{
+
4170 struct fuse *f = req_fuse_prepare(req);
+
4171 char *path;
+
4172 int err;
+
4173
+
4174 get_path_nullok(f, ino, &path);
+
4175 err = fuse_flush_common(f, req, ino, path, fi);
+
4176 free_path(f, ino, path);
+
4177
+
4178 reply_err(req, err);
+
4179}
+
4180
+
4181static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4182 struct fuse_file_info *fi, struct flock *lock,
+
4183 int cmd)
+
4184{
+
4185 struct fuse *f = req_fuse_prepare(req);
+
4186 char *path;
+
4187 int err;
+
4188
+
4189 err = get_path_nullok(f, ino, &path);
+
4190 if (!err) {
+
4191 struct fuse_intr_data d;
+
4192 fuse_prepare_interrupt(f, req, &d);
+
4193 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4194 fuse_finish_interrupt(f, req, &d);
+
4195 free_path(f, ino, path);
+
4196 }
+
4197 return err;
+
4198}
+
4199
+
4200static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4201 struct fuse_file_info *fi, struct flock *lock)
+
4202{
+
4203 int err;
+
4204 struct lock l;
+
4205 struct lock *conflict;
+
4206 struct fuse *f = req_fuse(req);
+
4207
+
4208 flock_to_lock(lock, &l);
+
4209 l.owner = fi->lock_owner;
+
4210 pthread_mutex_lock(&f->lock);
+
4211 conflict = locks_conflict(get_node(f, ino), &l);
+
4212 if (conflict)
+
4213 lock_to_flock(conflict, lock);
+
4214 pthread_mutex_unlock(&f->lock);
+
4215 if (!conflict)
+
4216 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4217 else
+
4218 err = 0;
+
4219
+
4220 if (!err)
+
4221 fuse_reply_lock(req, lock);
+
4222 else
+
4223 reply_err(req, err);
+
4224}
+
4225
+
4226static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4227 struct fuse_file_info *fi, struct flock *lock,
+
4228 int sleep)
+
4229{
+
4230 int err = fuse_lock_common(req, ino, fi, lock,
+
4231 sleep ? F_SETLKW : F_SETLK);
+
4232 if (!err) {
+
4233 struct fuse *f = req_fuse(req);
+
4234 struct lock l;
+
4235 flock_to_lock(lock, &l);
+
4236 l.owner = fi->lock_owner;
+
4237 pthread_mutex_lock(&f->lock);
+
4238 locks_insert(get_node(f, ino), &l);
+
4239 pthread_mutex_unlock(&f->lock);
+
4240 }
+
4241 reply_err(req, err);
+
4242}
+
4243
+
4244static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4245 struct fuse_file_info *fi, int op)
+
4246{
+
4247 struct fuse *f = req_fuse_prepare(req);
+
4248 char *path;
+
4249 int err;
+
4250
+
4251 err = get_path_nullok(f, ino, &path);
+
4252 if (err == 0) {
+
4253 struct fuse_intr_data d;
+
4254 fuse_prepare_interrupt(f, req, &d);
+
4255 err = fuse_fs_flock(f->fs, path, fi, op);
+
4256 fuse_finish_interrupt(f, req, &d);
+
4257 free_path(f, ino, path);
+
4258 }
+
4259 reply_err(req, err);
+
4260}
+
4261
+
4262static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4263 uint64_t idx)
+
4264{
+
4265 struct fuse *f = req_fuse_prepare(req);
+
4266 struct fuse_intr_data d;
+
4267 char *path;
+
4268 int err;
+
4269
+
4270 err = get_path(f, ino, &path);
+
4271 if (!err) {
+
4272 fuse_prepare_interrupt(f, req, &d);
+
4273 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4274 fuse_finish_interrupt(f, req, &d);
+
4275 free_path(f, ino, path);
+
4276 }
+
4277 if (!err)
+
4278 fuse_reply_bmap(req, idx);
+
4279 else
+
4280 reply_err(req, err);
+
4281}
+
4282
+
4283static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4284 void *arg, struct fuse_file_info *llfi,
+
4285 unsigned int flags, const void *in_buf,
+
4286 size_t in_bufsz, size_t out_bufsz)
+
4287{
+
4288 struct fuse *f = req_fuse_prepare(req);
+
4289 struct fuse_intr_data d;
+
4290 struct fuse_file_info fi;
+
4291 char *path, *out_buf = NULL;
+
4292 int err;
+
4293
+
4294 err = -EPERM;
+
4295 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4296 goto err;
+
4297
+
4298 if (flags & FUSE_IOCTL_DIR)
+
4299 get_dirhandle(llfi, &fi);
+
4300 else
+
4301 fi = *llfi;
+
4302
+
4303 if (out_bufsz) {
+
4304 err = -ENOMEM;
+
4305 out_buf = malloc(out_bufsz);
+
4306 if (!out_buf)
+
4307 goto err;
+
4308 }
+
4309
+
4310 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4311 if (out_buf && in_bufsz)
+
4312 memcpy(out_buf, in_buf, in_bufsz);
+
4313
+
4314 err = get_path_nullok(f, ino, &path);
+
4315 if (err)
+
4316 goto err;
+
4317
+
4318 fuse_prepare_interrupt(f, req, &d);
+
4319
+
4320 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4321 out_buf ? out_buf : (void *)in_buf);
+
4322
+
4323 fuse_finish_interrupt(f, req, &d);
+
4324 free_path(f, ino, path);
+
4325
+
4326 if (err < 0)
+
4327 goto err;
+
4328 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4329 goto out;
+
4330err:
+
4331 reply_err(req, err);
+
4332out:
+
4333 free(out_buf);
+
4334}
+
4335
+
4336static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4337 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4338{
+
4339 struct fuse *f = req_fuse_prepare(req);
+
4340 struct fuse_intr_data d;
+
4341 char *path;
+
4342 int err;
+
4343 unsigned revents = 0;
+
4344
+
4345 err = get_path_nullok(f, ino, &path);
+
4346 if (!err) {
+
4347 fuse_prepare_interrupt(f, req, &d);
+
4348 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4349 fuse_finish_interrupt(f, req, &d);
+
4350 free_path(f, ino, path);
+
4351 }
+
4352 if (!err)
+
4353 fuse_reply_poll(req, revents);
+
4354 else
+
4355 reply_err(req, err);
+
4356}
+
4357
+
4358static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4359 off_t offset, off_t length, struct fuse_file_info *fi)
+
4360{
+
4361 struct fuse *f = req_fuse_prepare(req);
+
4362 struct fuse_intr_data d;
+
4363 char *path;
+
4364 int err;
+
4365
+
4366 err = get_path_nullok(f, ino, &path);
+
4367 if (!err) {
+
4368 fuse_prepare_interrupt(f, req, &d);
+
4369 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4370 fuse_finish_interrupt(f, req, &d);
+
4371 free_path(f, ino, path);
+
4372 }
+
4373 reply_err(req, err);
+
4374}
+
4375
+
4376static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4377 off_t off_in, struct fuse_file_info *fi_in,
+
4378 fuse_ino_t nodeid_out, off_t off_out,
+
4379 struct fuse_file_info *fi_out, size_t len,
+
4380 int flags)
+
4381{
+
4382 struct fuse *f = req_fuse_prepare(req);
+
4383 struct fuse_intr_data d;
+
4384 char *path_in, *path_out;
+
4385 int err;
+
4386 ssize_t res;
+
4387
+
4388 err = get_path_nullok(f, nodeid_in, &path_in);
+
4389 if (err) {
+
4390 reply_err(req, err);
+
4391 return;
+
4392 }
+
4393
+
4394 err = get_path_nullok(f, nodeid_out, &path_out);
+
4395 if (err) {
+
4396 free_path(f, nodeid_in, path_in);
+
4397 reply_err(req, err);
+
4398 return;
+
4399 }
+
4400
+
4401 fuse_prepare_interrupt(f, req, &d);
+
4402 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4403 fi_out, off_out, len, flags);
+
4404 fuse_finish_interrupt(f, req, &d);
+
4405
+
4406 if (res >= 0)
+
4407 fuse_reply_write(req, res);
+
4408 else
+
4409 reply_err(req, res);
+
4410
+
4411 free_path(f, nodeid_in, path_in);
+
4412 free_path(f, nodeid_out, path_out);
+
4413}
+
4414
+
4415static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4416 struct fuse_file_info *fi)
+
4417{
+
4418 struct fuse *f = req_fuse_prepare(req);
+
4419 struct fuse_intr_data d;
+
4420 char *path;
+
4421 int err;
+
4422 off_t res;
+
4423
+
4424 err = get_path(f, ino, &path);
+
4425 if (err) {
+
4426 reply_err(req, err);
+
4427 return;
+
4428 }
+
4429
+
4430 fuse_prepare_interrupt(f, req, &d);
+
4431 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4432 fuse_finish_interrupt(f, req, &d);
+
4433 free_path(f, ino, path);
+
4434 if (res >= 0)
+
4435 fuse_reply_lseek(req, res);
+
4436 else
+
4437 reply_err(req, res);
+
4438}
+
4439
+
4440static int clean_delay(struct fuse *f)
+
4441{
+
4442 /*
+
4443 * This is calculating the delay between clean runs. To
+
4444 * reduce the number of cleans we are doing them 10 times
+
4445 * within the remember window.
+
4446 */
+
4447 int min_sleep = 60;
+
4448 int max_sleep = 3600;
+
4449 int sleep_time = f->conf.remember / 10;
+
4450
+
4451 if (sleep_time > max_sleep)
+
4452 return max_sleep;
+
4453 if (sleep_time < min_sleep)
+
4454 return min_sleep;
+
4455 return sleep_time;
+
4456}
+
4457
+
4458int fuse_clean_cache(struct fuse *f)
+
4459{
+
4460 struct node_lru *lnode;
+
4461 struct list_head *curr, *next;
+
4462 struct node *node;
+
4463 struct timespec now;
+
4464
+
4465 pthread_mutex_lock(&f->lock);
+
4466
+
4467 curr_time(&now);
+
4468
+
4469 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4470 double age;
+
4471
+
4472 next = curr->next;
+
4473 lnode = list_entry(curr, struct node_lru, lru);
+
4474 node = &lnode->node;
+
4475
+
4476 age = diff_timespec(&now, &lnode->forget_time);
+
4477 if (age <= f->conf.remember)
+
4478 break;
+
4479
+
4480 assert(node->nlookup == 1);
+
4481
+
4482 /* Don't forget active directories */
+
4483 if (node->refctr > 1)
+
4484 continue;
+
4485
+
4486 node->nlookup = 0;
+
4487 unhash_name(f, node);
+
4488 unref_node(f, node);
+
4489 }
+
4490 pthread_mutex_unlock(&f->lock);
+
4491
+
4492 return clean_delay(f);
+
4493}
+
4494
+
4495static struct fuse_lowlevel_ops fuse_path_ops = {
+
4496 .init = fuse_lib_init,
+
4497 .destroy = fuse_lib_destroy,
+
4498 .lookup = fuse_lib_lookup,
+
4499 .forget = fuse_lib_forget,
+
4500 .forget_multi = fuse_lib_forget_multi,
+
4501 .getattr = fuse_lib_getattr,
+
4502 .setattr = fuse_lib_setattr,
+
4503 .access = fuse_lib_access,
+
4504 .readlink = fuse_lib_readlink,
+
4505 .mknod = fuse_lib_mknod,
+
4506 .mkdir = fuse_lib_mkdir,
+
4507 .unlink = fuse_lib_unlink,
+
4508 .rmdir = fuse_lib_rmdir,
+
4509 .symlink = fuse_lib_symlink,
+
4510 .rename = fuse_lib_rename,
+
4511 .link = fuse_lib_link,
+
4512 .create = fuse_lib_create,
+
4513 .open = fuse_lib_open,
+
4514 .read = fuse_lib_read,
+
4515 .write_buf = fuse_lib_write_buf,
+
4516 .flush = fuse_lib_flush,
+
4517 .release = fuse_lib_release,
+
4518 .fsync = fuse_lib_fsync,
+
4519 .opendir = fuse_lib_opendir,
+
4520 .readdir = fuse_lib_readdir,
+
4521 .readdirplus = fuse_lib_readdirplus,
+
4522 .releasedir = fuse_lib_releasedir,
+
4523 .fsyncdir = fuse_lib_fsyncdir,
+
4524 .statfs = fuse_lib_statfs,
+
4525 .setxattr = fuse_lib_setxattr,
+
4526 .getxattr = fuse_lib_getxattr,
+
4527 .listxattr = fuse_lib_listxattr,
+
4528 .removexattr = fuse_lib_removexattr,
+
4529 .getlk = fuse_lib_getlk,
+
4530 .setlk = fuse_lib_setlk,
+
4531 .flock = fuse_lib_flock,
+
4532 .bmap = fuse_lib_bmap,
+
4533 .ioctl = fuse_lib_ioctl,
+
4534 .poll = fuse_lib_poll,
+
4535 .fallocate = fuse_lib_fallocate,
+
4536 .copy_file_range = fuse_lib_copy_file_range,
+
4537 .lseek = fuse_lib_lseek,
+
4538};
+
4539
+
4540int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4541{
+
4542 return fuse_lowlevel_notify_poll(ph);
+
4543}
+
4544
+
4545struct fuse_session *fuse_get_session(struct fuse *f)
+
4546{
+
4547 return f->se;
+
4548}
+
4549
+
4550static int fuse_session_loop_remember(struct fuse *f)
+
4551{
+
4552 struct fuse_session *se = f->se;
+
4553 int res = 0;
+
4554 struct timespec now;
+
4555 time_t next_clean;
+
4556 struct pollfd fds = {
+
4557 .fd = se->fd,
+
4558 .events = POLLIN
+
4559 };
+
4560 struct fuse_buf fbuf = {
+
4561 .mem = NULL,
+
4562 };
+
4563
+
4564 curr_time(&now);
+
4565 next_clean = now.tv_sec;
+
4566 while (!fuse_session_exited(se)) {
+
4567 unsigned timeout;
+
4568
+
4569 curr_time(&now);
+
4570 if (now.tv_sec < next_clean)
+
4571 timeout = next_clean - now.tv_sec;
+
4572 else
+
4573 timeout = 0;
+
4574
+
4575 res = poll(&fds, 1, timeout * 1000);
+
4576 if (res == -1) {
+
4577 if (errno == EINTR)
+
4578 continue;
+
4579 else
+
4580 break;
+
4581 } else if (res > 0) {
+
4582 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4583 NULL);
+
4584 if (res == -EINTR)
+
4585 continue;
+
4586 if (res <= 0)
+
4587 break;
+
4588
+
4589 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4590 } else {
+
4591 timeout = fuse_clean_cache(f);
+
4592 curr_time(&now);
+
4593 next_clean = now.tv_sec + timeout;
+
4594 }
+
4595 }
+
4596
+
4597 free(fbuf.mem);
+ +
4599 return res < 0 ? -1 : 0;
+
4600}
+
4601
+
4602int fuse_loop(struct fuse *f)
+
4603{
+
4604 if (!f)
+
4605 return -1;
+
4606
+
4607 if (lru_enabled(f))
+
4608 return fuse_session_loop_remember(f);
+
4609
+
4610 return fuse_session_loop(f->se);
+
4611}
+
4612
+
4613FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4614int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4615{
+
4616 if (f == NULL)
+
4617 return -1;
+
4618
+
4619 int res = fuse_start_cleanup_thread(f);
+
4620 if (res)
+
4621 return -1;
+
4622
+
4623 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4625 return res;
+
4626}
+
4627
+
4628int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4629FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4630int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4631{
+
4632 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4633 if (config == NULL)
+
4634 return ENOMEM;
+
4635
+
4636 fuse_loop_cfg_convert(config, config_v1);
+
4637
+
4638 int res = fuse_loop_mt_312(f, config);
+
4639
+
4640 fuse_loop_cfg_destroy(config);
+
4641
+
4642 return res;
+
4643}
+
4644
+
4645int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4646FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4647int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4648{
+
4649 int err;
+
4650 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4651
+
4652 if (config == NULL)
+
4653 return ENOMEM;
+
4654
+
4655 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4656
+
4657 err = fuse_loop_mt_312(f, config);
+
4658
+
4659 fuse_loop_cfg_destroy(config);
+
4660
+
4661 return err;
+
4662}
+
4663
+
4664void fuse_exit(struct fuse *f)
+
4665{
+
4666 fuse_session_exit(f->se);
+
4667}
+
4668
+
4669struct fuse_context *fuse_get_context(void)
+
4670{
+
4671 struct fuse_context_i *c = fuse_get_context_internal();
+
4672
+
4673 if (c)
+
4674 return &c->ctx;
+
4675 else
+
4676 return NULL;
+
4677}
+
4678
+
4679int fuse_getgroups(int size, gid_t list[])
+
4680{
+
4681 struct fuse_context_i *c = fuse_get_context_internal();
+
4682 if (!c)
+
4683 return -EINVAL;
+
4684
+
4685 return fuse_req_getgroups(c->req, size, list);
+
4686}
+
4687
+
4688int fuse_interrupted(void)
+
4689{
+
4690 struct fuse_context_i *c = fuse_get_context_internal();
+
4691
+
4692 if (c)
+
4693 return fuse_req_interrupted(c->req);
+
4694 else
+
4695 return 0;
+
4696}
+
4697
+
4698int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4699 fuse_ino_t ino;
+
4700 int err = lookup_path_in_cache(f, path, &ino);
+
4701 if (err) {
+
4702 return err;
+
4703 }
+
4704
+
4705 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4706}
+
4707
+
4708#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4709
+
4710static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4713 FUSE_LIB_OPT("debug", debug, 1),
+
4714 FUSE_LIB_OPT("-d", debug, 1),
+
4715 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4716 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4717 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4718 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4719 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4720 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4721 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4722 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4723 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4724 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4725 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4726 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4727 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4728 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4729 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4730 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4731 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4732 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4733 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4734 FUSE_LIB_OPT("noforget", remember, -1),
+
4735 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4736 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4737 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4739};
+
4740
+
4741static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4742 struct fuse_args *outargs)
+
4743{
+
4744 (void) arg; (void) outargs; (void) data; (void) key;
+
4745
+
4746 /* Pass through unknown options */
+
4747 return 1;
+
4748}
+
4749
+
4750
+
4751static const struct fuse_opt fuse_help_opts[] = {
+
4752 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4753 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4755};
+
4756
+
4757static void print_module_help(const char *name,
+ +
4759{
+
4760 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4761 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4762 fuse_opt_add_arg(&a, "-h") == -1)
+
4763 return;
+
4764 printf("\nOptions for %s module:\n", name);
+
4765 (*fac)(&a, NULL);
+ +
4767}
+
4768
+
4769void fuse_lib_help(struct fuse_args *args)
+
4770{
+
4771 /* These are not all options, but only the ones that
+
4772 may be of interest to an end-user */
+
4773 printf(
+
4774" -o kernel_cache cache files in kernel\n"
+
4775" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4776" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4777" -o umask=M set file permissions (octal)\n"
+
4778" -o fmask=M set file permissions (octal)\n"
+
4779" -o dmask=M set dir permissions (octal)\n"
+
4780" -o uid=N set file owner\n"
+
4781" -o gid=N set file group\n"
+
4782" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4783" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4784" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4785" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4786" -o noforget never forget cached inodes\n"
+
4787" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4788" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4789
+
4790
+
4791 /* Print low-level help */
+ +
4793
+
4794 /* Print help for builtin modules */
+
4795 print_module_help("subdir", &fuse_module_subdir_factory);
+
4796#ifdef HAVE_ICONV
+
4797 print_module_help("iconv", &fuse_module_iconv_factory);
+
4798#endif
+
4799
+
4800 /* Parse command line options in case we need to
+
4801 activate more modules */
+
4802 struct fuse_config conf = { .modules = NULL };
+
4803 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4804 fuse_lib_opt_proc) == -1
+
4805 || !conf.modules)
+
4806 return;
+
4807
+
4808 char *module;
+
4809 char *next;
+
4810 struct fuse_module *m;
+
4811
+
4812 // Iterate over all modules
+
4813 for (module = conf.modules; module; module = next) {
+
4814 char *p;
+
4815 for (p = module; *p && *p != ':'; p++);
+
4816 next = *p ? p + 1 : NULL;
+
4817 *p = '\0';
+
4818
+
4819 m = fuse_get_module(module);
+
4820 if (m)
+
4821 print_module_help(module, &m->factory);
+
4822 }
+
4823}
+
4824
+
4825static int fuse_init_intr_signal(int signum, int *installed)
+
4826{
+
4827 struct sigaction old_sa;
+
4828
+
4829 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4830 perror("fuse: cannot get old signal handler");
+
4831 return -1;
+
4832 }
+
4833
+
4834 if (old_sa.sa_handler == SIG_DFL) {
+
4835 struct sigaction sa;
+
4836
+
4837 memset(&sa, 0, sizeof(struct sigaction));
+
4838 sa.sa_handler = fuse_intr_sighandler;
+
4839 sigemptyset(&sa.sa_mask);
+
4840
+
4841 if (sigaction(signum, &sa, NULL) == -1) {
+
4842 perror("fuse: cannot set interrupt signal handler");
+
4843 return -1;
+
4844 }
+
4845 *installed = 1;
+
4846 }
+
4847 return 0;
+
4848}
+
4849
+
4850static void fuse_restore_intr_signal(int signum)
+
4851{
+
4852 struct sigaction sa;
+
4853
+
4854 memset(&sa, 0, sizeof(struct sigaction));
+
4855 sa.sa_handler = SIG_DFL;
+
4856 sigaction(signum, &sa, NULL);
+
4857}
+
4858
+
4859
+
4860static int fuse_push_module(struct fuse *f, const char *module,
+
4861 struct fuse_args *args)
+
4862{
+
4863 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4864 struct fuse_fs *newfs;
+
4865 struct fuse_module *m = fuse_get_module(module);
+
4866
+
4867 if (!m)
+
4868 return -1;
+
4869
+
4870 newfs = m->factory(args, fs);
+
4871 if (!newfs) {
+
4872 fuse_put_module(m);
+
4873 return -1;
+
4874 }
+
4875 f->fs = newfs;
+
4876 return 0;
+
4877}
+
4878
+
4879struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4880 void *user_data)
+
4881{
+
4882 struct fuse_fs *fs;
+
4883
+
4884 if (sizeof(struct fuse_operations) < op_size) {
+
4885 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4886 op_size = sizeof(struct fuse_operations);
+
4887 }
+
4888
+
4889 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4890 if (!fs) {
+
4891 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4892 return NULL;
+
4893 }
+
4894
+
4895 fs->user_data = user_data;
+
4896 if (op)
+
4897 memcpy(&fs->op, op, op_size);
+
4898 return fs;
+
4899}
+
4900
+
4901static int node_table_init(struct node_table *t)
+
4902{
+
4903 t->size = NODE_TABLE_MIN_SIZE;
+
4904 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4905 if (t->array == NULL) {
+
4906 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4907 return -1;
+
4908 }
+
4909 t->use = 0;
+
4910 t->split = 0;
+
4911
+
4912 return 0;
+
4913}
+
4914
+
4915static void *fuse_prune_nodes(void *fuse)
+
4916{
+
4917 struct fuse *f = fuse;
+
4918 int sleep_time;
+
4919
+
4920#ifdef HAVE_PTHREAD_SETNAME_NP
+
4921 pthread_setname_np(pthread_self(), "fuse_prune_nodes");
+
4922#endif
+
4923
+
4924 while(1) {
+
4925 sleep_time = fuse_clean_cache(f);
+
4926 sleep(sleep_time);
+
4927 }
+
4928 return NULL;
+
4929}
+
4930
+
4931int fuse_start_cleanup_thread(struct fuse *f)
+
4932{
+
4933 if (lru_enabled(f))
+
4934 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4935
+
4936 return 0;
+
4937}
+
4938
+
4939void fuse_stop_cleanup_thread(struct fuse *f)
+
4940{
+
4941 if (lru_enabled(f)) {
+
4942 pthread_mutex_lock(&f->lock);
+
4943 pthread_cancel(f->prune_thread);
+
4944 pthread_mutex_unlock(&f->lock);
+
4945 pthread_join(f->prune_thread, NULL);
+
4946 }
+
4947}
+
4948
+
4949/*
+
4950 * Not supposed to be called directly, but supposed to be called
+
4951 * through the fuse_new macro
+
4952 */
+
4953struct fuse *_fuse_new_31(struct fuse_args *args,
+
4954 const struct fuse_operations *op, size_t op_size,
+
4955 struct libfuse_version *version, void *user_data)
+
4956{
+
4957 struct fuse *f;
+
4958 struct node *root;
+
4959 struct fuse_fs *fs;
+
4960 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
4961
+
4962 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
4963 if (f == NULL) {
+
4964 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4965 goto out;
+
4966 }
+
4967
+
4968 f->conf.entry_timeout = 1.0;
+
4969 f->conf.attr_timeout = 1.0;
+
4970 f->conf.negative_timeout = 0.0;
+
4971 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
4972
+
4973 /* Parse options */
+
4974 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
4975 fuse_lib_opt_proc) == -1)
+
4976 goto out_free;
+
4977
+
4978 pthread_mutex_lock(&fuse_context_lock);
+
4979 static int builtin_modules_registered = 0;
+
4980 /* Have the builtin modules already been registered? */
+
4981 if (builtin_modules_registered == 0) {
+
4982 /* If not, register them. */
+
4983 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
4984#ifdef HAVE_ICONV
+
4985 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
4986#endif
+
4987 builtin_modules_registered= 1;
+
4988 }
+
4989 pthread_mutex_unlock(&fuse_context_lock);
+
4990
+
4991 if (fuse_create_context_key() == -1)
+
4992 goto out_free;
+
4993
+
4994 fs = fuse_fs_new(op, op_size, user_data);
+
4995 if (!fs)
+
4996 goto out_delete_context_key;
+
4997
+
4998 f->fs = fs;
+
4999
+
5000 /* Oh f**k, this is ugly! */
+
5001 if (!fs->op.lock) {
+
5002 llop.getlk = NULL;
+
5003 llop.setlk = NULL;
+
5004 }
+
5005
+
5006 f->pagesize = getpagesize();
+
5007 init_list_head(&f->partial_slabs);
+
5008 init_list_head(&f->full_slabs);
+
5009 init_list_head(&f->lru_table);
+
5010
+
5011 if (f->conf.modules) {
+
5012 char *module;
+
5013 char *next;
+
5014
+
5015 for (module = f->conf.modules; module; module = next) {
+
5016 char *p;
+
5017 for (p = module; *p && *p != ':'; p++);
+
5018 next = *p ? p + 1 : NULL;
+
5019 *p = '\0';
+
5020 if (module[0] &&
+
5021 fuse_push_module(f, module, args) == -1)
+
5022 goto out_free_fs;
+
5023 }
+
5024 }
+
5025
+
5026 if (!f->conf.ac_attr_timeout_set)
+
5027 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5028
+
5029#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5030 /*
+
5031 * In FreeBSD, we always use these settings as inode numbers
+
5032 * are needed to make getcwd(3) work.
+
5033 */
+
5034 f->conf.readdir_ino = 1;
+
5035#endif
+
5036
+
5037 /* not declared globally, to restrict usage of this function */
+
5038 struct fuse_session *fuse_session_new_versioned(
+
5039 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5040 size_t op_size, struct libfuse_version *version,
+
5041 void *userdata);
+
5042 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5043 f);
+
5044 if (f->se == NULL)
+
5045 goto out_free_fs;
+
5046
+
5047 if (f->conf.debug) {
+
5048 fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok);
+
5049 }
+
5050
+
5051 /* Trace topmost layer by default */
+
5052 f->fs->debug = f->conf.debug;
+
5053 f->ctr = 0;
+
5054 f->generation = 0;
+
5055 if (node_table_init(&f->name_table) == -1)
+
5056 goto out_free_session;
+
5057
+
5058 if (node_table_init(&f->id_table) == -1)
+
5059 goto out_free_name_table;
+
5060
+
5061 pthread_mutex_init(&f->lock, NULL);
+
5062
+
5063 root = alloc_node(f);
+
5064 if (root == NULL) {
+
5065 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5066 goto out_free_id_table;
+
5067 }
+
5068 if (lru_enabled(f)) {
+
5069 struct node_lru *lnode = node_lru(root);
+
5070 init_list_head(&lnode->lru);
+
5071 }
+
5072
+
5073 strcpy(root->inline_name, "/");
+
5074 root->name = root->inline_name;
+
5075 root->parent = NULL;
+
5076 root->nodeid = FUSE_ROOT_ID;
+
5077 inc_nlookup(root);
+
5078 hash_id(f, root);
+
5079
+
5080 return f;
+
5081
+
5082out_free_id_table:
+
5083 free(f->id_table.array);
+
5084out_free_name_table:
+
5085 free(f->name_table.array);
+
5086out_free_session:
+
5087 fuse_session_destroy(f->se);
+
5088out_free_fs:
+
5089 free(f->fs);
+
5090 free(f->conf.modules);
+
5091out_delete_context_key:
+
5092 fuse_delete_context_key();
+
5093out_free:
+
5094 free(f);
+
5095out:
+
5096 return NULL;
+
5097}
+
5098
+
5099/* Emulates 3.0-style fuse_new(), which processes --help */
+
5100FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5101struct fuse *_fuse_new_30(struct fuse_args *args,
+
5102 const struct fuse_operations *op,
+
5103 size_t op_size,
+
5104 struct libfuse_version *version,
+
5105 void *user_data)
+
5106{
+
5107 struct fuse_config conf = {0};
+
5108
+
5109 const struct fuse_opt opts[] = {
+
5110 FUSE_LIB_OPT("-h", show_help, 1),
+
5111 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5113 };
+
5114
+
5115 if (fuse_opt_parse(args, &conf, opts,
+
5116 fuse_lib_opt_proc) == -1)
+
5117 return NULL;
+
5118
+
5119 if (conf.show_help) {
+
5120 fuse_lib_help(args);
+
5121 return NULL;
+
5122 } else
+
5123 return _fuse_new_31(args, op, op_size, version, user_data);
+
5124}
+
5125
+
5126/* ABI compat version */
+
5127struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5128 size_t op_size, void *user_data);
+
5129FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5130struct fuse *fuse_new_31(struct fuse_args *args,
+
5131 const struct fuse_operations *op,
+
5132 size_t op_size, void *user_data)
+
5133{
+
5134 /* unknown version */
+
5135 struct libfuse_version version = { 0 };
+
5136
+
5137 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5138}
+
5139
+
5140/*
+
5141 * ABI compat version
+
5142 * Emulates 3.0-style fuse_new(), which processes --help
+
5143 */
+
5144struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5145 size_t op_size, void *user_data);
+
5146FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5147struct fuse *fuse_new_30(struct fuse_args *args,
+
5148 const struct fuse_operations *op,
+
5149 size_t op_size, void *user_data)
+
5150{
+
5151 struct fuse_config conf = {0};
+
5152
+
5153 const struct fuse_opt opts[] = {
+
5154 FUSE_LIB_OPT("-h", show_help, 1),
+
5155 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5157 };
+
5158
+
5159 if (fuse_opt_parse(args, &conf, opts,
+
5160 fuse_lib_opt_proc) == -1)
+
5161 return NULL;
+
5162
+
5163 if (conf.show_help) {
+
5164 fuse_lib_help(args);
+
5165 return NULL;
+
5166 } else
+
5167 return fuse_new_31(args, op, op_size, user_data);
+
5168}
+
5169
+
5170
+
5171void fuse_destroy(struct fuse *f)
+
5172{
+
5173 size_t i;
+
5174
+
5175 if (f->conf.intr && f->intr_installed)
+
5176 fuse_restore_intr_signal(f->conf.intr_signal);
+
5177
+
5178 if (f->fs) {
+
5179 fuse_create_context(f);
+
5180
+
5181 for (i = 0; i < f->id_table.size; i++) {
+
5182 struct node *node;
+
5183
+
5184 for (node = f->id_table.array[i]; node != NULL;
+
5185 node = node->id_next) {
+
5186 if (node->is_hidden) {
+
5187 char *path;
+
5188 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5189 fuse_fs_unlink(f->fs, path);
+
5190 free(path);
+
5191 }
+
5192 }
+
5193 }
+
5194 }
+
5195 }
+
5196 for (i = 0; i < f->id_table.size; i++) {
+
5197 struct node *node;
+
5198 struct node *next;
+
5199
+
5200 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5201 next = node->id_next;
+
5202 free_node(f, node);
+
5203 f->id_table.use--;
+
5204 }
+
5205 }
+
5206 assert(list_empty(&f->partial_slabs));
+
5207 assert(list_empty(&f->full_slabs));
+
5208
+
5209 while (fuse_modules) {
+
5210 fuse_put_module(fuse_modules);
+
5211 }
+
5212 free(f->id_table.array);
+
5213 free(f->name_table.array);
+
5214 pthread_mutex_destroy(&f->lock);
+
5215 fuse_session_destroy(f->se);
+
5216 free(f->fs);
+
5217 free(f->conf.modules);
+
5218 free(f);
+
5219 fuse_delete_context_key();
+
5220}
+
5221
+
5222int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5223 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5224}
+
5225
+
5226
+
5227void fuse_unmount(struct fuse *f) {
+ +
5229}
+
5230
+
5231int fuse_version(void)
+
5232{
+
5233 return FUSE_VERSION;
+
5234}
+
5235
+
5236const char *fuse_pkgversion(void)
+
5237{
+
5238 return PACKAGE_VERSION;
+
5239}
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4679
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
int fuse_interrupted(void)
Definition fuse.c:4688
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4929
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4698
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1386
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4664
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4458
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4937
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5234
+
int fuse_version(void)
Definition fuse.c:5229
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
int32_t show_help
Definition fuse.h:279
+ + +
uint32_t no_interrupt
+
uint64_t want_ext
+ +
void * private_data
Definition fuse.h:874
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+ + + + +
+ + + + diff --git a/doc/html/lib_2fuse__i_8h_source.html b/doc/html/lib_2fuse__i_8h_source.html new file mode 100644 index 0000000..8f2436c --- /dev/null +++ b/doc/html/lib_2fuse__i_8h_source.html @@ -0,0 +1,288 @@ + + + + + + + +libfuse: lib/fuse_i.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include "fuse.h"
+
10#include "fuse_lowlevel.h"
+
11#include "util.h"
+
12
+
13#include <stdint.h>
+
14#include <stdbool.h>
+
15#include <errno.h>
+
16
+
17#define MIN(a, b) \
+
18({ \
+
19 typeof(a) _a = (a); \
+
20 typeof(b) _b = (b); \
+
21 _a < _b ? _a : _b; \
+
22})
+
23
+
24struct mount_opts;
+
25
+
26struct fuse_req {
+
27 struct fuse_session *se;
+
28 uint64_t unique;
+
29 _Atomic int ref_cnt;
+
30 pthread_mutex_t lock;
+
31 struct fuse_ctx ctx;
+
32 struct fuse_chan *ch;
+
33 int interrupted;
+
34 unsigned int ioctl_64bit : 1;
+
35 union {
+
36 struct {
+
37 uint64_t unique;
+
38 } i;
+
39 struct {
+ +
41 void *data;
+
42 } ni;
+
43 } u;
+
44 struct fuse_req *next;
+
45 struct fuse_req *prev;
+
46};
+
47
+
48struct fuse_notify_req {
+
49 uint64_t unique;
+
50 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
51 const void *, const struct fuse_buf *);
+
52 struct fuse_notify_req *next;
+
53 struct fuse_notify_req *prev;
+
54};
+
55
+
56struct fuse_session {
+
57 char *mountpoint;
+
58 volatile int exited;
+
59 int fd;
+
60 struct fuse_custom_io *io;
+
61 struct mount_opts *mo;
+
62 int debug;
+
63 int deny_others;
+
64 struct fuse_lowlevel_ops op;
+
65 int got_init;
+
66 struct cuse_data *cuse_data;
+
67 void *userdata;
+
68 uid_t owner;
+
69 struct fuse_conn_info conn;
+
70 struct fuse_req list;
+
71 struct fuse_req interrupts;
+
72 pthread_mutex_t lock;
+
73 int got_destroy;
+
74 pthread_key_t pipe_key;
+
75 int broken_splice_nonblock;
+
76 uint64_t notify_ctr;
+
77 struct fuse_notify_req notify_list;
+
78 _Atomic size_t bufsize;
+
79 int error;
+
80
+
81 /* This is useful if any kind of ABI incompatibility is found at
+
82 * a later version, to 'fix' it at run time.
+
83 */
+
84 struct libfuse_version version;
+
85
+
86 /* true if reading requests from /dev/fuse are handled internally */
+
87 bool buf_reallocable;
+
88};
+
89
+
90struct fuse_chan {
+
91 pthread_mutex_t lock;
+
92 int ctr;
+
93 int fd;
+
94};
+
95
+
103struct fuse_module {
+
104 char *name;
+
105 fuse_module_factory_t factory;
+
106 struct fuse_module *next;
+
107 struct fusemod_so *so;
+
108 int ctr;
+
109};
+
110
+
119#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
120struct fuse_loop_config
+
121{
+
122 /* verififier that a correct struct was was passed. This is especially
+
123 * needed, as versions below (3, 12) were using a public struct
+
124 * (now called fuse_loop_config_v1), which was hard to extend with
+
125 * additional parameters, without risking that file system implementations
+
126 * would not have noticed and might either pass uninitialized members
+
127 * or even too small structs.
+
128 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
129 * or 1. v2 or even higher version just need to set a value here
+
130 * which not conflicting and very unlikely as having been set by
+
131 * file system implementation.
+
132 */
+
133 int version_id;
+
134
+
139 int clone_fd;
+ +
152
+
158 unsigned int max_threads;
+
159};
+
160#endif
+
161
+
162/* ----------------------------------------------------------- *
+
163 * Channel interface (when using -o clone_fd) *
+
164 * ----------------------------------------------------------- */
+
165
+
172struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
173
+
179void fuse_chan_put(struct fuse_chan *ch);
+
180
+
181struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
182void destroy_mount_opts(struct mount_opts *mo);
+
183void fuse_mount_version(void);
+
184unsigned get_max_read(struct mount_opts *o);
+
185void fuse_kern_unmount(const char *mountpoint, int fd);
+
186int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
187
+
188int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
189 int count);
+
190void fuse_free_req(fuse_req_t req);
+
191
+
192void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
193
+
194int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
195
+
196void fuse_buf_free(struct fuse_buf *buf);
+
197
+
198int fuse_session_receive_buf_internal(struct fuse_session *se,
+
199 struct fuse_buf *buf,
+
200 struct fuse_chan *ch);
+
201void fuse_session_process_buf_internal(struct fuse_session *se,
+
202 const struct fuse_buf *buf,
+
203 struct fuse_chan *ch);
+
204
+
205struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
206 size_t op_size, void *private_data);
+
207int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
208int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
209
+
215int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
216
+
217
+
218/*
+
219 * This can be changed dynamically on recent kernels through the
+
220 * /proc/sys/fs/fuse/max_pages_limit interface.
+
221 *
+
222 * Older kernels will always use the default value.
+
223 */
+
224#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
225#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
226
+
227/* room needed in buffer to accommodate header */
+
228#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
229
+
233static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn,
+
234 uint64_t want_ext_default,
+
235 uint32_t want_default)
+
236{
+
237 /*
+
238 * Convert want to want_ext if necessary.
+
239 * For the high level interface this function might be called
+
240 * twice, once from the high level interface and once from the
+
241 * low level interface. Both, with different want_ext_default and
+
242 * want_default values. In order to suppress a failure for the
+
243 * second call, we check if the lower 32 bits of want_ext are
+
244 * already set to the value of want.
+
245 */
+
246 if (conn->want != want_default &&
+
247 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
248 if (conn->want_ext != want_ext_default) {
+
249 fuse_log(FUSE_LOG_ERR,
+
250 "fuse: both 'want' and 'want_ext' are set\n");
+
251 return -EINVAL;
+
252 }
+
253
+
254 /* high bits from want_ext, low bits from want */
+
255 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
256 conn->want;
+
257 }
+
258
+
259 /* ensure there won't be a second conversion */
+
260 conn->want = fuse_lower_32_bits(conn->want_ext);
+
261
+
262 return 0;
+
263}
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1386
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + +
uint64_t want_ext
+ + + +
unsigned int max_threads
Definition fuse_i.h:156
+
unsigned int max_idle_threads
+ + + + +
+ + + + diff --git a/doc/html/lib_2fuse__log_8c_source.html b/doc/html/lib_2fuse__log_8c_source.html new file mode 100644 index 0000000..ab2aa76 --- /dev/null +++ b/doc/html/lib_2fuse__log_8c_source.html @@ -0,0 +1,162 @@ + + + + + + + +libfuse: lib/fuse_log.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdarg.h>
+
14#include <stdio.h>
+
15#include <stdbool.h>
+
16#include <syslog.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(__attribute__((unused)) enum fuse_log_level level,
+
23 const char *fmt, va_list ap)
+
24{
+
25 if (to_syslog) {
+
26 int sys_log_level = LOG_ERR;
+
27
+
28 /*
+
29 * with glibc fuse_log_level has identical values as
+
30 * syslog levels, but we also support BSD - better we convert to
+
31 * be sure.
+
32 */
+
33 switch (level) {
+
34 case FUSE_LOG_DEBUG:
+
35 sys_log_level = LOG_DEBUG;
+
36 break;
+
37 case FUSE_LOG_INFO:
+
38 sys_log_level = LOG_INFO;
+
39 break;
+
40 case FUSE_LOG_NOTICE:
+
41 sys_log_level = LOG_NOTICE;
+
42 break;
+
43 case FUSE_LOG_WARNING:
+
44 sys_log_level = LOG_WARNING;
+
45 break;
+
46 case FUSE_LOG_ERR:
+
47 sys_log_level = LOG_ERR;
+
48 break;
+
49 case FUSE_LOG_CRIT:
+
50 sys_log_level = LOG_CRIT;
+
51 break;
+
52 case FUSE_LOG_ALERT:
+
53 sys_log_level = LOG_ALERT;
+
54 break;
+
55 case FUSE_LOG_EMERG:
+
56 sys_log_level = LOG_EMERG;
+
57 }
+
58
+
59 char log[MAX_SYSLOG_LINE_LEN];
+
60 vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap);
+
61 syslog(sys_log_level, "%s", log);
+
62 } else {
+
63 vfprintf(stderr, fmt, ap);
+
64 }
+
65}
+
66
+
67static fuse_log_func_t log_func = default_log_func;
+
68
+ +
70{
+
71 if (!func)
+
72 func = default_log_func;
+
73
+
74 log_func = func;
+
75}
+
76
+
77void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
78{
+
79 va_list ap;
+
80
+
81 va_start(ap, fmt);
+
82 log_func(level, fmt, ap);
+
83 va_end(ap);
+
84}
+
85
+
86void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
87{
+
88 to_syslog = true;
+
89
+
90 openlog(ident, option, facility);
+
91}
+
92
+
93void fuse_log_close_syslog(void)
+
94{
+
95 closelog();
+
96}
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:93
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:86
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:69
+
+ + + + diff --git a/doc/html/lib_2fuse__loop_8c_source.html b/doc/html/lib_2fuse__loop_8c_source.html new file mode 100644 index 0000000..ed2db8c --- /dev/null +++ b/doc/html/lib_2fuse__loop_8c_source.html @@ -0,0 +1,112 @@ + + + + + + + +libfuse: lib/fuse_loop.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+ +
45 return res;
+
46}
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
void fuse_session_reset(struct fuse_session *se)
+ +
void * mem
+
+ + + + diff --git a/doc/html/lib_2fuse__loop__mt_8c_source.html b/doc/html/lib_2fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..c473fa5 --- /dev/null +++ b/doc/html/lib_2fuse__loop__mt_8c_source.html @@ -0,0 +1,601 @@ + + + + + + + +libfuse: lib/fuse_loop_mt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <string.h>
+
23#include <unistd.h>
+
24#include <signal.h>
+
25#include <semaphore.h>
+
26#include <errno.h>
+
27#include <sys/time.h>
+
28#include <sys/ioctl.h>
+
29#include <assert.h>
+
30#include <limits.h>
+
31
+
32/* Environment var controlling the thread stack size */
+
33#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
34
+
35#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
36#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
37#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
38#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
39 * by default */
+
40
+
41/* an arbitrary large value that cannot be valid */
+
42#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
43
+
44struct fuse_worker {
+
45 struct fuse_worker *prev;
+
46 struct fuse_worker *next;
+
47 pthread_t thread_id;
+
48
+
49 // We need to include fuse_buf so that we can properly free
+
50 // it when a thread is terminated by pthread_cancel().
+
51 struct fuse_buf fbuf;
+
52 struct fuse_chan *ch;
+
53 struct fuse_mt *mt;
+
54};
+
55
+
56struct fuse_mt {
+
57 pthread_mutex_t lock;
+
58 int numworker;
+
59 int numavail;
+
60 struct fuse_session *se;
+
61 struct fuse_worker main;
+
62 sem_t finish;
+
63 int exit;
+
64 int error;
+
65 int clone_fd;
+
66 int max_idle;
+
67 int max_threads;
+
68};
+
69
+
70static struct fuse_chan *fuse_chan_new(int fd)
+
71{
+
72 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
73 if (ch == NULL) {
+
74 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
75 return NULL;
+
76 }
+
77
+
78 memset(ch, 0, sizeof(*ch));
+
79 ch->fd = fd;
+
80 ch->ctr = 1;
+
81 pthread_mutex_init(&ch->lock, NULL);
+
82
+
83 return ch;
+
84}
+
85
+
86struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
87{
+
88 assert(ch->ctr > 0);
+
89 pthread_mutex_lock(&ch->lock);
+
90 ch->ctr++;
+
91 pthread_mutex_unlock(&ch->lock);
+
92
+
93 return ch;
+
94}
+
95
+
96void fuse_chan_put(struct fuse_chan *ch)
+
97{
+
98 if (ch == NULL)
+
99 return;
+
100 pthread_mutex_lock(&ch->lock);
+
101 ch->ctr--;
+
102 if (!ch->ctr) {
+
103 pthread_mutex_unlock(&ch->lock);
+
104 close(ch->fd);
+
105 pthread_mutex_destroy(&ch->lock);
+
106 free(ch);
+
107 } else
+
108 pthread_mutex_unlock(&ch->lock);
+
109}
+
110
+
111static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
112{
+
113 struct fuse_worker *prev = next->prev;
+
114 w->next = next;
+
115 w->prev = prev;
+
116 prev->next = w;
+
117 next->prev = w;
+
118}
+
119
+
120static void list_del_worker(struct fuse_worker *w)
+
121{
+
122 struct fuse_worker *prev = w->prev;
+
123 struct fuse_worker *next = w->next;
+
124 prev->next = next;
+
125 next->prev = prev;
+
126}
+
127
+
128static int fuse_loop_start_thread(struct fuse_mt *mt);
+
129
+
130static void *fuse_do_work(void *data)
+
131{
+
132 struct fuse_worker *w = (struct fuse_worker *) data;
+
133 struct fuse_mt *mt = w->mt;
+
134
+
135#ifdef HAVE_PTHREAD_SETNAME_NP
+
136 pthread_setname_np(pthread_self(), "fuse_worker");
+
137#endif
+
138
+
139 while (!fuse_session_exited(mt->se)) {
+
140 int isforget = 0;
+
141 int res;
+
142
+
143 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
144 res = fuse_session_receive_buf_internal(mt->se, &w->fbuf,
+
145 w->ch);
+
146 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
147 if (res == -EINTR)
+
148 continue;
+
149 if (res <= 0) {
+
150 if (res < 0) {
+
151 fuse_session_exit(mt->se);
+
152 mt->error = res;
+
153 }
+
154 break;
+
155 }
+
156
+
157 pthread_mutex_lock(&mt->lock);
+
158 if (mt->exit) {
+
159 pthread_mutex_unlock(&mt->lock);
+
160 return NULL;
+
161 }
+
162
+
163 /*
+
164 * This disgusting hack is needed so that zillions of threads
+
165 * are not created on a burst of FORGET messages
+
166 */
+
167 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
168 struct fuse_in_header *in = w->fbuf.mem;
+
169
+
170 if (in->opcode == FUSE_FORGET ||
+
171 in->opcode == FUSE_BATCH_FORGET)
+
172 isforget = 1;
+
173 }
+
174
+
175 if (!isforget)
+
176 mt->numavail--;
+
177 if (mt->numavail == 0 && mt->numworker < mt->max_threads)
+
178 fuse_loop_start_thread(mt);
+
179 pthread_mutex_unlock(&mt->lock);
+
180
+
181 fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch);
+
182
+
183 pthread_mutex_lock(&mt->lock);
+
184 if (!isforget)
+
185 mt->numavail++;
+
186
+
187 /* creating and destroying threads is rather expensive - and there is
+
188 * not much gain from destroying existing threads. It is therefore
+
189 * discouraged to set max_idle to anything else than -1. If there
+
190 * is indeed a good reason to destruct threads it should be done
+
191 * delayed, a moving average might be useful for that.
+
192 */
+
193 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
194 if (mt->exit) {
+
195 pthread_mutex_unlock(&mt->lock);
+
196 return NULL;
+
197 }
+
198 list_del_worker(w);
+
199 mt->numavail--;
+
200 mt->numworker--;
+
201 pthread_mutex_unlock(&mt->lock);
+
202
+
203 pthread_detach(w->thread_id);
+
204 fuse_buf_free(&w->fbuf);
+
205 fuse_chan_put(w->ch);
+
206 free(w);
+
207 return NULL;
+
208 }
+
209 pthread_mutex_unlock(&mt->lock);
+
210 }
+
211
+
212 sem_post(&mt->finish);
+
213
+
214 return NULL;
+
215}
+
216
+
217int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
218{
+
219 sigset_t oldset;
+
220 sigset_t newset;
+
221 int res;
+
222 pthread_attr_t attr;
+
223 char *stack_size;
+
224
+
225 /* Override default stack size
+
226 * XXX: This should ideally be a parameter option. It is rather
+
227 * well hidden here.
+
228 */
+
229 pthread_attr_init(&attr);
+
230 stack_size = getenv(ENVNAME_THREAD_STACK);
+
231 if (stack_size) {
+
232 long size;
+
233
+
234 res = libfuse_strtol(stack_size, &size);
+
235 if (res)
+
236 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
237 stack_size);
+
238 else if (pthread_attr_setstacksize(&attr, size))
+
239 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
240 size);
+
241 }
+
242
+
243 /* Disallow signal reception in worker threads */
+
244 sigemptyset(&newset);
+
245 sigaddset(&newset, SIGTERM);
+
246 sigaddset(&newset, SIGINT);
+
247 sigaddset(&newset, SIGHUP);
+
248 sigaddset(&newset, SIGQUIT);
+
249 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
250 res = pthread_create(thread_id, &attr, func, arg);
+
251 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
252 pthread_attr_destroy(&attr);
+
253 if (res != 0) {
+
254 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
255 strerror(res));
+
256 return -1;
+
257 }
+
258
+
259 return 0;
+
260}
+
261
+
262static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
263{
+
264 int res;
+
265 int clonefd;
+
266 uint32_t masterfd;
+
267 const char *devname = "/dev/fuse";
+
268
+
269#ifndef O_CLOEXEC
+
270#define O_CLOEXEC 0
+
271#endif
+
272 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
273 if (clonefd == -1) {
+
274 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
275 strerror(errno));
+
276 return -1;
+
277 }
+
278#ifndef O_CLOEXEC
+
279 fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
280#endif
+
281
+
282 masterfd = se->fd;
+
283 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
284 if (res == -1) {
+
285 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
286 strerror(errno));
+
287 close(clonefd);
+
288 return -1;
+
289 }
+
290 return clonefd;
+
291}
+
292
+
293static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
294{
+
295 int clonefd;
+
296 struct fuse_session *se = mt->se;
+
297 struct fuse_chan *newch;
+
298
+
299 if (se->io != NULL) {
+
300 if (se->io->clone_fd != NULL)
+
301 clonefd = se->io->clone_fd(se->fd);
+
302 else
+
303 return NULL;
+
304 } else {
+
305 clonefd = fuse_clone_chan_fd_default(se);
+
306 }
+
307 if (clonefd < 0)
+
308 return NULL;
+
309
+
310 newch = fuse_chan_new(clonefd);
+
311 if (newch == NULL)
+
312 close(clonefd);
+
313
+
314 return newch;
+
315}
+
316
+
317static int fuse_loop_start_thread(struct fuse_mt *mt)
+
318{
+
319 int res;
+
320
+
321 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
322 if (!w) {
+
323 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
324 return -1;
+
325 }
+
326 memset(w, 0, sizeof(struct fuse_worker));
+
327 w->fbuf.mem = NULL;
+
328 w->mt = mt;
+
329
+
330 w->ch = NULL;
+
331 if (mt->clone_fd) {
+
332 w->ch = fuse_clone_chan(mt);
+
333 if(!w->ch) {
+
334 /* Don't attempt this again */
+
335 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
336 "without -o clone_fd.\n");
+
337 mt->clone_fd = 0;
+
338 }
+
339 }
+
340
+
341 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
342 if (res == -1) {
+
343 fuse_chan_put(w->ch);
+
344 free(w);
+
345 return -1;
+
346 }
+
347 list_add_worker(w, &mt->main);
+
348 mt->numavail ++;
+
349 mt->numworker ++;
+
350
+
351 return 0;
+
352}
+
353
+
354static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
355{
+
356 pthread_join(w->thread_id, NULL);
+
357 pthread_mutex_lock(&mt->lock);
+
358 list_del_worker(w);
+
359 pthread_mutex_unlock(&mt->lock);
+
360 fuse_buf_free(&w->fbuf);
+
361 fuse_chan_put(w->ch);
+
362 free(w);
+
363}
+
364
+
365int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
366FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
367int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
368{
+
369int err;
+
370 struct fuse_mt mt;
+
371 struct fuse_worker *w;
+
372 int created_config = 0;
+
373
+
374 if (config) {
+
375 err = fuse_loop_cfg_verify(config);
+
376 if (err)
+
377 return err;
+
378 } else {
+
379 /* The caller does not care about parameters - use the default */
+
380 config = fuse_loop_cfg_create();
+
381 created_config = 1;
+
382 }
+
383
+
384
+
385 memset(&mt, 0, sizeof(struct fuse_mt));
+
386 mt.se = se;
+
387 mt.clone_fd = config->clone_fd;
+
388 mt.error = 0;
+
389 mt.numworker = 0;
+
390 mt.numavail = 0;
+
391 mt.max_idle = config->max_idle_threads;
+
392 mt.max_threads = config->max_threads;
+
393 mt.main.thread_id = pthread_self();
+
394 mt.main.prev = mt.main.next = &mt.main;
+
395 sem_init(&mt.finish, 0, 0);
+
396 pthread_mutex_init(&mt.lock, NULL);
+
397
+
398 pthread_mutex_lock(&mt.lock);
+
399 err = fuse_loop_start_thread(&mt);
+
400 pthread_mutex_unlock(&mt.lock);
+
401 if (!err) {
+
402 /* sem_wait() is interruptible */
+
403 while (!fuse_session_exited(se))
+
404 sem_wait(&mt.finish);
+
405
+
406 pthread_mutex_lock(&mt.lock);
+
407 for (w = mt.main.next; w != &mt.main; w = w->next)
+
408 pthread_cancel(w->thread_id);
+
409 mt.exit = 1;
+
410 pthread_mutex_unlock(&mt.lock);
+
411
+
412 while (mt.main.next != &mt.main)
+
413 fuse_join_worker(&mt, mt.main.next);
+
414
+
415 err = mt.error;
+
416 }
+
417
+
418 pthread_mutex_destroy(&mt.lock);
+
419 sem_destroy(&mt.finish);
+
420 if(se->error != 0)
+
421 err = se->error;
+ +
423
+
424 if (created_config) {
+
425 fuse_loop_cfg_destroy(config);
+
426 config = NULL;
+
427 }
+
428
+
429 return err;
+
430}
+
431
+
432int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
433FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
434int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
435{
+
436 int err;
+
437 struct fuse_loop_config *config = NULL;
+
438
+
439 if (config_v1 != NULL) {
+
440 /* convert the given v1 config */
+
441 config = fuse_loop_cfg_create();
+
442 if (config == NULL)
+
443 return ENOMEM;
+
444
+
445 fuse_loop_cfg_convert(config, config_v1);
+
446 }
+
447
+
448 err = fuse_session_loop_mt_312(se, config);
+
449
+
450 fuse_loop_cfg_destroy(config);
+
451
+
452 return err;
+
453}
+
454
+
455
+
456int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
457FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
458int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
459{
+
460 int err;
+
461 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
462 if (clone_fd > 0)
+
463 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
464 err = fuse_session_loop_mt_312(se, config);
+
465
+
466 fuse_loop_cfg_destroy(config);
+
467
+
468 return err;
+
469}
+
470
+
471struct fuse_loop_config *fuse_loop_cfg_create(void)
+
472{
+
473 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
474 if (config == NULL)
+
475 return NULL;
+
476
+
477 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
478 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
479 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
480 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
481
+
482 return config;
+
483}
+
484
+
485void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
486{
+
487 free(config);
+
488}
+
489
+
490int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
491{
+
492 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
493 return -EINVAL;
+
494
+
495 return 0;
+
496}
+
497
+
498void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
499 struct fuse_loop_config_v1 *v1_conf)
+
500{
+
501 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
502
+
503 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
504}
+
505
+
506void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
507 unsigned int value)
+
508{
+
509 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
510 if (value != UINT_MAX)
+
511 fuse_log(FUSE_LOG_ERR,
+
512 "Ignoring invalid max threads value "
+
513 "%u > max (%u).\n", value,
+
514 FUSE_LOOP_MT_MAX_THREADS);
+
515 return;
+
516 }
+
517 config->max_idle_threads = value;
+
518}
+
519
+
520void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
521 unsigned int value)
+
522{
+
523 config->max_threads = value;
+
524}
+
525
+
526void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
527 unsigned int value)
+
528{
+
529 config->clone_fd = value;
+
530}
+
531
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+
void fuse_session_reset(struct fuse_session *se)
+ + + +
unsigned int max_threads
Definition fuse_i.h:156
+
unsigned int max_idle_threads
+
+ + + + diff --git a/doc/html/lib_2fuse__lowlevel_8c_source.html b/doc/html/lib_2fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..7c35377 --- /dev/null +++ b/doc/html/lib_2fuse__lowlevel_8c_source.html @@ -0,0 +1,3748 @@ + + + + + + + +libfuse: lib/fuse_lowlevel.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21
+
22#include <stdint.h>
+
23#include <stdbool.h>
+
24#include <stdio.h>
+
25#include <stdlib.h>
+
26#include <stddef.h>
+
27#include <stdalign.h>
+
28#include <string.h>
+
29#include <unistd.h>
+
30#include <limits.h>
+
31#include <errno.h>
+
32#include <assert.h>
+
33#include <sys/file.h>
+
34#include <sys/ioctl.h>
+
35
+
36#ifndef F_LINUX_SPECIFIC_BASE
+
37#define F_LINUX_SPECIFIC_BASE 1024
+
38#endif
+
39#ifndef F_SETPIPE_SZ
+
40#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
41#endif
+
42
+
43
+
44#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
45#define OFFSET_MAX 0x7fffffffffffffffLL
+
46
+
47#define container_of(ptr, type, member) ({ \
+
48 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+
49 (type *)( (char *)__mptr - offsetof(type,member) );})
+
50
+
51struct fuse_pollhandle {
+
52 uint64_t kh;
+
53 struct fuse_session *se;
+
54};
+
55
+
56static size_t pagesize;
+
57
+
58static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
59{
+
60 pagesize = getpagesize();
+
61}
+
62
+
63static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
64{
+
65 attr->ino = stbuf->st_ino;
+
66 attr->mode = stbuf->st_mode;
+
67 attr->nlink = stbuf->st_nlink;
+
68 attr->uid = stbuf->st_uid;
+
69 attr->gid = stbuf->st_gid;
+
70 attr->rdev = stbuf->st_rdev;
+
71 attr->size = stbuf->st_size;
+
72 attr->blksize = stbuf->st_blksize;
+
73 attr->blocks = stbuf->st_blocks;
+
74 attr->atime = stbuf->st_atime;
+
75 attr->mtime = stbuf->st_mtime;
+
76 attr->ctime = stbuf->st_ctime;
+
77 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
78 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
79 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
80}
+
81
+
82static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
83{
+
84 stbuf->st_mode = attr->mode;
+
85 stbuf->st_uid = attr->uid;
+
86 stbuf->st_gid = attr->gid;
+
87 stbuf->st_size = attr->size;
+
88 stbuf->st_atime = attr->atime;
+
89 stbuf->st_mtime = attr->mtime;
+
90 stbuf->st_ctime = attr->ctime;
+
91 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
92 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
93 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
94}
+
95
+
96static size_t iov_length(const struct iovec *iov, size_t count)
+
97{
+
98 size_t seg;
+
99 size_t ret = 0;
+
100
+
101 for (seg = 0; seg < count; seg++)
+
102 ret += iov[seg].iov_len;
+
103 return ret;
+
104}
+
105
+
106static void list_init_req(struct fuse_req *req)
+
107{
+
108 req->next = req;
+
109 req->prev = req;
+
110}
+
111
+
112static void list_del_req(struct fuse_req *req)
+
113{
+
114 struct fuse_req *prev = req->prev;
+
115 struct fuse_req *next = req->next;
+
116 prev->next = next;
+
117 next->prev = prev;
+
118}
+
119
+
120static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
121{
+
122 struct fuse_req *prev = next->prev;
+
123 req->next = next;
+
124 req->prev = prev;
+
125 prev->next = req;
+
126 next->prev = req;
+
127}
+
128
+
129static void destroy_req(fuse_req_t req)
+
130{
+
131 assert(req->ch == NULL);
+
132 pthread_mutex_destroy(&req->lock);
+
133 free(req);
+
134}
+
135
+
136void fuse_free_req(fuse_req_t req)
+
137{
+
138 int ctr;
+
139 struct fuse_session *se = req->se;
+
140
+
141 if (se->conn.no_interrupt) {
+
142 ctr = --req->ref_cnt;
+
143 fuse_chan_put(req->ch);
+
144 req->ch = NULL;
+
145 } else {
+
146 pthread_mutex_lock(&se->lock);
+
147 req->u.ni.func = NULL;
+
148 req->u.ni.data = NULL;
+
149 list_del_req(req);
+
150 ctr = --req->ref_cnt;
+
151 fuse_chan_put(req->ch);
+
152 req->ch = NULL;
+
153 pthread_mutex_unlock(&se->lock);
+
154 }
+
155 if (!ctr)
+
156 destroy_req(req);
+
157}
+
158
+
159static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
160{
+
161 struct fuse_req *req;
+
162
+
163 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
164 if (req == NULL) {
+
165 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
166 } else {
+
167 req->se = se;
+
168 req->ref_cnt = 1;
+
169 list_init_req(req);
+
170 pthread_mutex_init(&req->lock, NULL);
+
171 }
+
172
+
173 return req;
+
174}
+
175
+
176/* Send data. If *ch* is NULL, send via session master fd */
+
177static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
178 struct iovec *iov, int count)
+
179{
+
180 struct fuse_out_header *out = iov[0].iov_base;
+
181
+
182 assert(se != NULL);
+
183 out->len = iov_length(iov, count);
+
184 if (se->debug) {
+
185 if (out->unique == 0) {
+
186 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
187 out->error, out->len);
+
188 } else if (out->error) {
+
189 fuse_log(FUSE_LOG_DEBUG,
+
190 " unique: %llu, error: %i (%s), outsize: %i\n",
+
191 (unsigned long long) out->unique, out->error,
+
192 strerror(-out->error), out->len);
+
193 } else {
+
194 fuse_log(FUSE_LOG_DEBUG,
+
195 " unique: %llu, success, outsize: %i\n",
+
196 (unsigned long long) out->unique, out->len);
+
197 }
+
198 }
+
199
+
200 ssize_t res;
+
201 if (se->io != NULL)
+
202 /* se->io->writev is never NULL if se->io is not NULL as
+
203 specified by fuse_session_custom_io()*/
+
204 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
205 se->userdata);
+
206 else
+
207 res = writev(ch ? ch->fd : se->fd, iov, count);
+
208
+
209 int err = errno;
+
210
+
211 if (res == -1) {
+
212 /* ENOENT means the operation was interrupted */
+
213 if (!fuse_session_exited(se) && err != ENOENT)
+
214 perror("fuse: writing device");
+
215 return -err;
+
216 }
+
217
+
218 return 0;
+
219}
+
220
+
221
+
222int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
223 int count)
+
224{
+
225 struct fuse_out_header out;
+
226
+
227#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
228 const char *str = strerrordesc_np(error * -1);
+
229 if ((str == NULL && error != 0) || error > 0) {
+
230#else
+
231 if (error <= -1000 || error > 0) {
+
232#endif
+
233 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
234 error = -ERANGE;
+
235 }
+
236
+
237 out.unique = req->unique;
+
238 out.error = error;
+
239
+
240 iov[0].iov_base = &out;
+
241 iov[0].iov_len = sizeof(struct fuse_out_header);
+
242
+
243 return fuse_send_msg(req->se, req->ch, iov, count);
+
244}
+
245
+
246static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
247 int count)
+
248{
+
249 int res;
+
250
+
251 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
252 fuse_free_req(req);
+
253 return res;
+
254}
+
255
+
256static int send_reply(fuse_req_t req, int error, const void *arg,
+
257 size_t argsize)
+
258{
+
259 struct iovec iov[2];
+
260 int count = 1;
+
261 if (argsize) {
+
262 iov[1].iov_base = (void *) arg;
+
263 iov[1].iov_len = argsize;
+
264 count++;
+
265 }
+
266 return send_reply_iov(req, error, iov, count);
+
267}
+
268
+
269int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
270{
+
271 int res;
+
272 struct iovec *padded_iov;
+
273
+
274 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
275 if (padded_iov == NULL)
+
276 return fuse_reply_err(req, ENOMEM);
+
277
+
278 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
279 count++;
+
280
+
281 res = send_reply_iov(req, 0, padded_iov, count);
+
282 free(padded_iov);
+
283
+
284 return res;
+
285}
+
286
+
287
+
288/* `buf` is allowed to be empty so that the proper size may be
+
289 allocated by the caller */
+
290size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
291 const char *name, const struct stat *stbuf, off_t off)
+
292{
+
293 (void)req;
+
294 size_t namelen;
+
295 size_t entlen;
+
296 size_t entlen_padded;
+
297 struct fuse_dirent *dirent;
+
298
+
299 namelen = strlen(name);
+
300 entlen = FUSE_NAME_OFFSET + namelen;
+
301 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
302
+
303 if ((buf == NULL) || (entlen_padded > bufsize))
+
304 return entlen_padded;
+
305
+
306 dirent = (struct fuse_dirent*) buf;
+
307 dirent->ino = stbuf->st_ino;
+
308 dirent->off = off;
+
309 dirent->namelen = namelen;
+
310 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
311 memcpy(dirent->name, name, namelen);
+
312 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
313
+
314 return entlen_padded;
+
315}
+
316
+
317static void convert_statfs(const struct statvfs *stbuf,
+
318 struct fuse_kstatfs *kstatfs)
+
319{
+
320 kstatfs->bsize = stbuf->f_bsize;
+
321 kstatfs->frsize = stbuf->f_frsize;
+
322 kstatfs->blocks = stbuf->f_blocks;
+
323 kstatfs->bfree = stbuf->f_bfree;
+
324 kstatfs->bavail = stbuf->f_bavail;
+
325 kstatfs->files = stbuf->f_files;
+
326 kstatfs->ffree = stbuf->f_ffree;
+
327 kstatfs->namelen = stbuf->f_namemax;
+
328}
+
329
+
330static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
331{
+
332 return send_reply(req, 0, arg, argsize);
+
333}
+
334
+
335int fuse_reply_err(fuse_req_t req, int err)
+
336{
+
337 return send_reply(req, -err, NULL, 0);
+
338}
+
339
+ +
341{
+
342 fuse_free_req(req);
+
343}
+
344
+
345static unsigned long calc_timeout_sec(double t)
+
346{
+
347 if (t > (double) ULONG_MAX)
+
348 return ULONG_MAX;
+
349 else if (t < 0.0)
+
350 return 0;
+
351 else
+
352 return (unsigned long) t;
+
353}
+
354
+
355static unsigned int calc_timeout_nsec(double t)
+
356{
+
357 double f = t - (double) calc_timeout_sec(t);
+
358 if (f < 0.0)
+
359 return 0;
+
360 else if (f >= 0.999999999)
+
361 return 999999999;
+
362 else
+
363 return (unsigned int) (f * 1.0e9);
+
364}
+
365
+
366static void fill_entry(struct fuse_entry_out *arg,
+
367 const struct fuse_entry_param *e)
+
368{
+
369 arg->nodeid = e->ino;
+
370 arg->generation = e->generation;
+
371 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
372 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
373 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
374 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
375 convert_stat(&e->attr, &arg->attr);
+
376}
+
377
+
378/* `buf` is allowed to be empty so that the proper size may be
+
379 allocated by the caller */
+
380size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
381 const char *name,
+
382 const struct fuse_entry_param *e, off_t off)
+
383{
+
384 (void)req;
+
385 size_t namelen;
+
386 size_t entlen;
+
387 size_t entlen_padded;
+
388
+
389 namelen = strlen(name);
+
390 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
391 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
392 if ((buf == NULL) || (entlen_padded > bufsize))
+
393 return entlen_padded;
+
394
+
395 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
396 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
397 fill_entry(&dp->entry_out, e);
+
398
+
399 struct fuse_dirent *dirent = &dp->dirent;
+
400 dirent->ino = e->attr.st_ino;
+
401 dirent->off = off;
+
402 dirent->namelen = namelen;
+
403 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
404 memcpy(dirent->name, name, namelen);
+
405 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
406
+
407 return entlen_padded;
+
408}
+
409
+
410static void fill_open(struct fuse_open_out *arg,
+
411 const struct fuse_file_info *f)
+
412{
+
413 arg->fh = f->fh;
+
414 if (f->backing_id > 0) {
+
415 arg->backing_id = f->backing_id;
+
416 arg->open_flags |= FOPEN_PASSTHROUGH;
+
417 }
+
418 if (f->direct_io)
+
419 arg->open_flags |= FOPEN_DIRECT_IO;
+
420 if (f->keep_cache)
+
421 arg->open_flags |= FOPEN_KEEP_CACHE;
+
422 if (f->cache_readdir)
+
423 arg->open_flags |= FOPEN_CACHE_DIR;
+
424 if (f->nonseekable)
+
425 arg->open_flags |= FOPEN_NONSEEKABLE;
+
426 if (f->noflush)
+
427 arg->open_flags |= FOPEN_NOFLUSH;
+ +
429 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
430}
+
431
+
432int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
433{
+
434 struct fuse_entry_out arg;
+
435 size_t size = req->se->conn.proto_minor < 9 ?
+
436 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
437
+
438 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
439 negative entry */
+
440 if (!e->ino && req->se->conn.proto_minor < 4)
+
441 return fuse_reply_err(req, ENOENT);
+
442
+
443 memset(&arg, 0, sizeof(arg));
+
444 fill_entry(&arg, e);
+
445 return send_reply_ok(req, &arg, size);
+
446}
+
447
+
448int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
449 const struct fuse_file_info *f)
+
450{
+
451 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
452 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
453 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
454 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
455 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
456
+
457 memset(buf, 0, sizeof(buf));
+
458 fill_entry(earg, e);
+
459 fill_open(oarg, f);
+
460 return send_reply_ok(req, buf,
+
461 entrysize + sizeof(struct fuse_open_out));
+
462}
+
463
+
464int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
465 double attr_timeout)
+
466{
+
467 struct fuse_attr_out arg;
+
468 size_t size = req->se->conn.proto_minor < 9 ?
+
469 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
470
+
471 memset(&arg, 0, sizeof(arg));
+
472 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
473 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
474 convert_stat(attr, &arg.attr);
+
475
+
476 return send_reply_ok(req, &arg, size);
+
477}
+
478
+
479int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
480{
+
481 return send_reply_ok(req, linkname, strlen(linkname));
+
482}
+
483
+
484int fuse_passthrough_open(fuse_req_t req, int fd)
+
485{
+
486 struct fuse_backing_map map = { .fd = fd };
+
487 int ret;
+
488
+
489 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
490 if (ret <= 0) {
+
491 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
492 return 0;
+
493 }
+
494
+
495 return ret;
+
496}
+
497
+
498int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
499{
+
500 int ret;
+
501
+
502 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
503 if (ret < 0)
+
504 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
505
+
506 return ret;
+
507}
+
508
+
509int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
+
510{
+
511 struct fuse_open_out arg;
+
512
+
513 memset(&arg, 0, sizeof(arg));
+
514 fill_open(&arg, f);
+
515 return send_reply_ok(req, &arg, sizeof(arg));
+
516}
+
517
+
518int fuse_reply_write(fuse_req_t req, size_t count)
+
519{
+
520 struct fuse_write_out arg;
+
521
+
522 memset(&arg, 0, sizeof(arg));
+
523 arg.size = count;
+
524
+
525 return send_reply_ok(req, &arg, sizeof(arg));
+
526}
+
527
+
528int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
529{
+
530 return send_reply_ok(req, buf, size);
+
531}
+
532
+
533static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
534 struct fuse_chan *ch,
+
535 struct iovec *iov, int iov_count,
+
536 struct fuse_bufvec *buf,
+
537 size_t len)
+
538{
+
539 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
540 void *mbuf;
+
541 int res;
+
542
+
543 /* Optimize common case */
+
544 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
545 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
546 /* FIXME: also avoid memory copy if there are multiple buffers
+
547 but none of them contain an fd */
+
548
+
549 iov[iov_count].iov_base = buf->buf[0].mem;
+
550 iov[iov_count].iov_len = len;
+
551 iov_count++;
+
552 return fuse_send_msg(se, ch, iov, iov_count);
+
553 }
+
554
+
555 res = posix_memalign(&mbuf, pagesize, len);
+
556 if (res != 0)
+
557 return res;
+
558
+
559 mem_buf.buf[0].mem = mbuf;
+
560 res = fuse_buf_copy(&mem_buf, buf, 0);
+
561 if (res < 0) {
+
562 free(mbuf);
+
563 return -res;
+
564 }
+
565 len = res;
+
566
+
567 iov[iov_count].iov_base = mbuf;
+
568 iov[iov_count].iov_len = len;
+
569 iov_count++;
+
570 res = fuse_send_msg(se, ch, iov, iov_count);
+
571 free(mbuf);
+
572
+
573 return res;
+
574}
+
575
+
576struct fuse_ll_pipe {
+
577 size_t size;
+
578 int can_grow;
+
579 int pipe[2];
+
580};
+
581
+
582static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
583{
+
584 close(llp->pipe[0]);
+
585 close(llp->pipe[1]);
+
586 free(llp);
+
587}
+
588
+
589#ifdef HAVE_SPLICE
+
590#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
591static int fuse_pipe(int fds[2])
+
592{
+
593 int rv = pipe(fds);
+
594
+
595 if (rv == -1)
+
596 return rv;
+
597
+
598 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
599 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
600 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
601 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
602 close(fds[0]);
+
603 close(fds[1]);
+
604 rv = -1;
+
605 }
+
606 return rv;
+
607}
+
608#else
+
609static int fuse_pipe(int fds[2])
+
610{
+
611 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
612}
+
613#endif
+
614
+
615static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
616{
+
617 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
618 if (llp == NULL) {
+
619 int res;
+
620
+
621 llp = malloc(sizeof(struct fuse_ll_pipe));
+
622 if (llp == NULL)
+
623 return NULL;
+
624
+
625 res = fuse_pipe(llp->pipe);
+
626 if (res == -1) {
+
627 free(llp);
+
628 return NULL;
+
629 }
+
630
+
631 /*
+
632 *the default size is 16 pages on linux
+
633 */
+
634 llp->size = pagesize * 16;
+
635 llp->can_grow = 1;
+
636
+
637 pthread_setspecific(se->pipe_key, llp);
+
638 }
+
639
+
640 return llp;
+
641}
+
642#endif
+
643
+
644static void fuse_ll_clear_pipe(struct fuse_session *se)
+
645{
+
646 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
647 if (llp) {
+
648 pthread_setspecific(se->pipe_key, NULL);
+
649 fuse_ll_pipe_free(llp);
+
650 }
+
651}
+
652
+
653#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
654static int read_back(int fd, char *buf, size_t len)
+
655{
+
656 int res;
+
657
+
658 res = read(fd, buf, len);
+
659 if (res == -1) {
+
660 fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+
661 return -EIO;
+
662 }
+
663 if (res != len) {
+
664 fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+
665 return -EIO;
+
666 }
+
667 return 0;
+
668}
+
669
+
670static int grow_pipe_to_max(int pipefd)
+
671{
+
672 int res;
+
673 long max;
+
674 long maxfd;
+
675 char buf[32];
+
676
+
677 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY);
+
678 if (maxfd < 0)
+
679 return -errno;
+
680
+
681 res = read(maxfd, buf, sizeof(buf) - 1);
+
682 if (res < 0) {
+
683 int saved_errno;
+
684
+
685 saved_errno = errno;
+
686 close(maxfd);
+
687 return -saved_errno;
+
688 }
+
689 close(maxfd);
+
690 buf[res] = '\0';
+
691
+
692 res = libfuse_strtol(buf, &max);
+
693 if (res)
+
694 return res;
+
695 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
696 if (res < 0)
+
697 return -errno;
+
698 return max;
+
699}
+
700
+
701static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
702 struct iovec *iov, int iov_count,
+
703 struct fuse_bufvec *buf, unsigned int flags)
+
704{
+
705 int res;
+
706 size_t len = fuse_buf_size(buf);
+
707 struct fuse_out_header *out = iov[0].iov_base;
+
708 struct fuse_ll_pipe *llp;
+
709 int splice_flags;
+
710 size_t pipesize;
+
711 size_t total_buf_size;
+
712 size_t idx;
+
713 size_t headerlen;
+
714 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
715
+
716 if (se->broken_splice_nonblock)
+
717 goto fallback;
+
718
+
719 if (flags & FUSE_BUF_NO_SPLICE)
+
720 goto fallback;
+
721
+
722 total_buf_size = 0;
+
723 for (idx = buf->idx; idx < buf->count; idx++) {
+
724 total_buf_size += buf->buf[idx].size;
+
725 if (idx == buf->idx)
+
726 total_buf_size -= buf->off;
+
727 }
+
728 if (total_buf_size < 2 * pagesize)
+
729 goto fallback;
+
730
+
731 if (se->conn.proto_minor < 14 ||
+
732 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
733 goto fallback;
+
734
+
735 llp = fuse_ll_get_pipe(se);
+
736 if (llp == NULL)
+
737 goto fallback;
+
738
+
739
+
740 headerlen = iov_length(iov, iov_count);
+
741
+
742 out->len = headerlen + len;
+
743
+
744 /*
+
745 * Heuristic for the required pipe size, does not work if the
+
746 * source contains less than page size fragments
+
747 */
+
748 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
749
+
750 if (llp->size < pipesize) {
+
751 if (llp->can_grow) {
+
752 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
753 if (res == -1) {
+
754 res = grow_pipe_to_max(llp->pipe[0]);
+
755 if (res > 0)
+
756 llp->size = res;
+
757 llp->can_grow = 0;
+
758 goto fallback;
+
759 }
+
760 llp->size = res;
+
761 }
+
762 if (llp->size < pipesize)
+
763 goto fallback;
+
764 }
+
765
+
766
+
767 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
768 if (res == -1)
+
769 goto fallback;
+
770
+
771 if (res != headerlen) {
+
772 res = -EIO;
+
773 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
774 headerlen);
+
775 goto clear_pipe;
+
776 }
+
777
+
778 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
779 pipe_buf.buf[0].fd = llp->pipe[1];
+
780
+
781 res = fuse_buf_copy(&pipe_buf, buf,
+ +
783 if (res < 0) {
+
784 if (res == -EAGAIN || res == -EINVAL) {
+
785 /*
+
786 * Should only get EAGAIN on kernels with
+
787 * broken SPLICE_F_NONBLOCK support (<=
+
788 * 2.6.35) where this error or a short read is
+
789 * returned even if the pipe itself is not
+
790 * full
+
791 *
+
792 * EINVAL might mean that splice can't handle
+
793 * this combination of input and output.
+
794 */
+
795 if (res == -EAGAIN)
+
796 se->broken_splice_nonblock = 1;
+
797
+
798 pthread_setspecific(se->pipe_key, NULL);
+
799 fuse_ll_pipe_free(llp);
+
800 goto fallback;
+
801 }
+
802 res = -res;
+
803 goto clear_pipe;
+
804 }
+
805
+
806 if (res != 0 && res < len) {
+
807 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
808 void *mbuf;
+
809 size_t now_len = res;
+
810 /*
+
811 * For regular files a short count is either
+
812 * 1) due to EOF, or
+
813 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
814 *
+
815 * For other inputs it's possible that we overflowed
+
816 * the pipe because of small buffer fragments.
+
817 */
+
818
+
819 res = posix_memalign(&mbuf, pagesize, len);
+
820 if (res != 0)
+
821 goto clear_pipe;
+
822
+
823 mem_buf.buf[0].mem = mbuf;
+
824 mem_buf.off = now_len;
+
825 res = fuse_buf_copy(&mem_buf, buf, 0);
+
826 if (res > 0) {
+
827 char *tmpbuf;
+
828 size_t extra_len = res;
+
829 /*
+
830 * Trickiest case: got more data. Need to get
+
831 * back the data from the pipe and then fall
+
832 * back to regular write.
+
833 */
+
834 tmpbuf = malloc(headerlen);
+
835 if (tmpbuf == NULL) {
+
836 free(mbuf);
+
837 res = ENOMEM;
+
838 goto clear_pipe;
+
839 }
+
840 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
841 free(tmpbuf);
+
842 if (res != 0) {
+
843 free(mbuf);
+
844 goto clear_pipe;
+
845 }
+
846 res = read_back(llp->pipe[0], mbuf, now_len);
+
847 if (res != 0) {
+
848 free(mbuf);
+
849 goto clear_pipe;
+
850 }
+
851 len = now_len + extra_len;
+
852 iov[iov_count].iov_base = mbuf;
+
853 iov[iov_count].iov_len = len;
+
854 iov_count++;
+
855 res = fuse_send_msg(se, ch, iov, iov_count);
+
856 free(mbuf);
+
857 return res;
+
858 }
+
859 free(mbuf);
+
860 res = now_len;
+
861 }
+
862 len = res;
+
863 out->len = headerlen + len;
+
864
+
865 if (se->debug) {
+
866 fuse_log(FUSE_LOG_DEBUG,
+
867 " unique: %llu, success, outsize: %i (splice)\n",
+
868 (unsigned long long) out->unique, out->len);
+
869 }
+
870
+
871 splice_flags = 0;
+
872 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
873 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
874 splice_flags |= SPLICE_F_MOVE;
+
875
+
876 if (se->io != NULL && se->io->splice_send != NULL) {
+
877 res = se->io->splice_send(llp->pipe[0], NULL,
+
878 ch ? ch->fd : se->fd, NULL, out->len,
+
879 splice_flags, se->userdata);
+
880 } else {
+
881 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
882 out->len, splice_flags);
+
883 }
+
884 if (res == -1) {
+
885 res = -errno;
+
886 perror("fuse: splice from pipe");
+
887 goto clear_pipe;
+
888 }
+
889 if (res != out->len) {
+
890 res = -EIO;
+
891 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
892 res, out->len);
+
893 goto clear_pipe;
+
894 }
+
895 return 0;
+
896
+
897clear_pipe:
+
898 fuse_ll_clear_pipe(se);
+
899 return res;
+
900
+
901fallback:
+
902 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
903}
+
904#else
+
905static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
906 struct iovec *iov, int iov_count,
+
907 struct fuse_bufvec *buf, unsigned int flags)
+
908{
+
909 size_t len = fuse_buf_size(buf);
+
910 (void) flags;
+
911
+
912 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+
913}
+
914#endif
+
915
+
916int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+
917 enum fuse_buf_copy_flags flags)
+
918{
+
919 struct iovec iov[2];
+
920 struct fuse_out_header out;
+
921 int res;
+
922
+
923 iov[0].iov_base = &out;
+
924 iov[0].iov_len = sizeof(struct fuse_out_header);
+
925
+
926 out.unique = req->unique;
+
927 out.error = 0;
+
928
+
929 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags);
+
930 if (res <= 0) {
+
931 fuse_free_req(req);
+
932 return res;
+
933 } else {
+
934 return fuse_reply_err(req, res);
+
935 }
+
936}
+
937
+
938int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
939{
+
940 struct fuse_statfs_out arg;
+
941 size_t size = req->se->conn.proto_minor < 4 ?
+
942 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
943
+
944 memset(&arg, 0, sizeof(arg));
+
945 convert_statfs(stbuf, &arg.st);
+
946
+
947 return send_reply_ok(req, &arg, size);
+
948}
+
949
+
950int fuse_reply_xattr(fuse_req_t req, size_t count)
+
951{
+
952 struct fuse_getxattr_out arg;
+
953
+
954 memset(&arg, 0, sizeof(arg));
+
955 arg.size = count;
+
956
+
957 return send_reply_ok(req, &arg, sizeof(arg));
+
958}
+
959
+
960int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
961{
+
962 struct fuse_lk_out arg;
+
963
+
964 memset(&arg, 0, sizeof(arg));
+
965 arg.lk.type = lock->l_type;
+
966 if (lock->l_type != F_UNLCK) {
+
967 arg.lk.start = lock->l_start;
+
968 if (lock->l_len == 0)
+
969 arg.lk.end = OFFSET_MAX;
+
970 else
+
971 arg.lk.end = lock->l_start + lock->l_len - 1;
+
972 }
+
973 arg.lk.pid = lock->l_pid;
+
974 return send_reply_ok(req, &arg, sizeof(arg));
+
975}
+
976
+
977int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
978{
+
979 struct fuse_bmap_out arg;
+
980
+
981 memset(&arg, 0, sizeof(arg));
+
982 arg.block = idx;
+
983
+
984 return send_reply_ok(req, &arg, sizeof(arg));
+
985}
+
986
+
987static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
988 size_t count)
+
989{
+
990 struct fuse_ioctl_iovec *fiov;
+
991 size_t i;
+
992
+
993 fiov = malloc(sizeof(fiov[0]) * count);
+
994 if (!fiov)
+
995 return NULL;
+
996
+
997 for (i = 0; i < count; i++) {
+
998 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
999 fiov[i].len = iov[i].iov_len;
+
1000 }
+
1001
+
1002 return fiov;
+
1003}
+
1004
+ +
1006 const struct iovec *in_iov, size_t in_count,
+
1007 const struct iovec *out_iov, size_t out_count)
+
1008{
+
1009 struct fuse_ioctl_out arg;
+
1010 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1011 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1012 struct iovec iov[4];
+
1013 size_t count = 1;
+
1014 int res;
+
1015
+
1016 memset(&arg, 0, sizeof(arg));
+
1017 arg.flags |= FUSE_IOCTL_RETRY;
+
1018 arg.in_iovs = in_count;
+
1019 arg.out_iovs = out_count;
+
1020 iov[count].iov_base = &arg;
+
1021 iov[count].iov_len = sizeof(arg);
+
1022 count++;
+
1023
+
1024 if (req->se->conn.proto_minor < 16) {
+
1025 if (in_count) {
+
1026 iov[count].iov_base = (void *)in_iov;
+
1027 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1028 count++;
+
1029 }
+
1030
+
1031 if (out_count) {
+
1032 iov[count].iov_base = (void *)out_iov;
+
1033 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1034 count++;
+
1035 }
+
1036 } else {
+
1037 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1038 if (sizeof(void *) == 4 && req->ioctl_64bit) {
+
1039 res = fuse_reply_err(req, EINVAL);
+
1040 goto out;
+
1041 }
+
1042
+
1043 if (in_count) {
+
1044 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1045 if (!in_fiov)
+
1046 goto enomem;
+
1047
+
1048 iov[count].iov_base = (void *)in_fiov;
+
1049 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1050 count++;
+
1051 }
+
1052 if (out_count) {
+
1053 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1054 if (!out_fiov)
+
1055 goto enomem;
+
1056
+
1057 iov[count].iov_base = (void *)out_fiov;
+
1058 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1059 count++;
+
1060 }
+
1061 }
+
1062
+
1063 res = send_reply_iov(req, 0, iov, count);
+
1064out:
+
1065 free(in_fiov);
+
1066 free(out_fiov);
+
1067
+
1068 return res;
+
1069
+
1070enomem:
+
1071 res = fuse_reply_err(req, ENOMEM);
+
1072 goto out;
+
1073}
+
1074
+
1075int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1076{
+
1077 struct fuse_ioctl_out arg;
+
1078 struct iovec iov[3];
+
1079 size_t count = 1;
+
1080
+
1081 memset(&arg, 0, sizeof(arg));
+
1082 arg.result = result;
+
1083 iov[count].iov_base = &arg;
+
1084 iov[count].iov_len = sizeof(arg);
+
1085 count++;
+
1086
+
1087 if (size) {
+
1088 iov[count].iov_base = (char *) buf;
+
1089 iov[count].iov_len = size;
+
1090 count++;
+
1091 }
+
1092
+
1093 return send_reply_iov(req, 0, iov, count);
+
1094}
+
1095
+
1096int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1097 int count)
+
1098{
+
1099 struct iovec *padded_iov;
+
1100 struct fuse_ioctl_out arg;
+
1101 int res;
+
1102
+
1103 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1104 if (padded_iov == NULL)
+
1105 return fuse_reply_err(req, ENOMEM);
+
1106
+
1107 memset(&arg, 0, sizeof(arg));
+
1108 arg.result = result;
+
1109 padded_iov[1].iov_base = &arg;
+
1110 padded_iov[1].iov_len = sizeof(arg);
+
1111
+
1112 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1113
+
1114 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1115 free(padded_iov);
+
1116
+
1117 return res;
+
1118}
+
1119
+
1120int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1121{
+
1122 struct fuse_poll_out arg;
+
1123
+
1124 memset(&arg, 0, sizeof(arg));
+
1125 arg.revents = revents;
+
1126
+
1127 return send_reply_ok(req, &arg, sizeof(arg));
+
1128}
+
1129
+
1130int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1131{
+
1132 struct fuse_lseek_out arg;
+
1133
+
1134 memset(&arg, 0, sizeof(arg));
+
1135 arg.offset = off;
+
1136
+
1137 return send_reply_ok(req, &arg, sizeof(arg));
+
1138}
+
1139
+
1140static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1141{
+
1142 char *name = (char *) inarg;
+
1143
+
1144 if (req->se->op.lookup)
+
1145 req->se->op.lookup(req, nodeid, name);
+
1146 else
+
1147 fuse_reply_err(req, ENOSYS);
+
1148}
+
1149
+
1150static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1151{
+
1152 struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
1153
+
1154 if (req->se->op.forget)
+
1155 req->se->op.forget(req, nodeid, arg->nlookup);
+
1156 else
+
1157 fuse_reply_none(req);
+
1158}
+
1159
+
1160static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+
1161 const void *inarg)
+
1162{
+
1163 struct fuse_batch_forget_in *arg = (void *) inarg;
+
1164 struct fuse_forget_one *param = (void *) PARAM(arg);
+
1165 unsigned int i;
+
1166
+
1167 (void) nodeid;
+
1168
+
1169 if (req->se->op.forget_multi) {
+
1170 req->se->op.forget_multi(req, arg->count,
+
1171 (struct fuse_forget_data *) param);
+
1172 } else if (req->se->op.forget) {
+
1173 for (i = 0; i < arg->count; i++) {
+
1174 struct fuse_forget_one *forget = &param[i];
+
1175 struct fuse_req *dummy_req;
+
1176
+
1177 dummy_req = fuse_ll_alloc_req(req->se);
+
1178 if (dummy_req == NULL)
+
1179 break;
+
1180
+
1181 dummy_req->unique = req->unique;
+
1182 dummy_req->ctx = req->ctx;
+
1183 dummy_req->ch = NULL;
+
1184
+
1185 req->se->op.forget(dummy_req, forget->nodeid,
+
1186 forget->nlookup);
+
1187 }
+
1188 fuse_reply_none(req);
+
1189 } else {
+
1190 fuse_reply_none(req);
+
1191 }
+
1192}
+
1193
+
1194static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1195{
+
1196 struct fuse_file_info *fip = NULL;
+
1197 struct fuse_file_info fi;
+
1198
+
1199 if (req->se->conn.proto_minor >= 9) {
+
1200 struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
1201
+
1202 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1203 memset(&fi, 0, sizeof(fi));
+
1204 fi.fh = arg->fh;
+
1205 fip = &fi;
+
1206 }
+
1207 }
+
1208
+
1209 if (req->se->op.getattr)
+
1210 req->se->op.getattr(req, nodeid, fip);
+
1211 else
+
1212 fuse_reply_err(req, ENOSYS);
+
1213}
+
1214
+
1215static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1216{
+
1217 struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
1218
+
1219 if (req->se->op.setattr) {
+
1220 struct fuse_file_info *fi = NULL;
+
1221 struct fuse_file_info fi_store;
+
1222 struct stat stbuf;
+
1223 memset(&stbuf, 0, sizeof(stbuf));
+
1224 convert_attr(arg, &stbuf);
+
1225 if (arg->valid & FATTR_FH) {
+
1226 arg->valid &= ~FATTR_FH;
+
1227 memset(&fi_store, 0, sizeof(fi_store));
+
1228 fi = &fi_store;
+
1229 fi->fh = arg->fh;
+
1230 }
+
1231 arg->valid &=
+
1232 FUSE_SET_ATTR_MODE |
+
1233 FUSE_SET_ATTR_UID |
+
1234 FUSE_SET_ATTR_GID |
+
1235 FUSE_SET_ATTR_SIZE |
+
1236 FUSE_SET_ATTR_ATIME |
+
1237 FUSE_SET_ATTR_MTIME |
+
1238 FUSE_SET_ATTR_KILL_SUID |
+
1239 FUSE_SET_ATTR_KILL_SGID |
+
1240 FUSE_SET_ATTR_ATIME_NOW |
+
1241 FUSE_SET_ATTR_MTIME_NOW |
+
1242 FUSE_SET_ATTR_CTIME;
+
1243
+
1244 req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+
1245 } else
+
1246 fuse_reply_err(req, ENOSYS);
+
1247}
+
1248
+
1249static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1250{
+
1251 struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
1252
+
1253 if (req->se->op.access)
+
1254 req->se->op.access(req, nodeid, arg->mask);
+
1255 else
+
1256 fuse_reply_err(req, ENOSYS);
+
1257}
+
1258
+
1259static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1260{
+
1261 (void) inarg;
+
1262
+
1263 if (req->se->op.readlink)
+
1264 req->se->op.readlink(req, nodeid);
+
1265 else
+
1266 fuse_reply_err(req, ENOSYS);
+
1267}
+
1268
+
1269static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1270{
+
1271 struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+
1272 char *name = PARAM(arg);
+
1273
+
1274 if (req->se->conn.proto_minor >= 12)
+
1275 req->ctx.umask = arg->umask;
+
1276 else
+
1277 name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1278
+
1279 if (req->se->op.mknod)
+
1280 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1281 else
+
1282 fuse_reply_err(req, ENOSYS);
+
1283}
+
1284
+
1285static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1286{
+
1287 struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
1288
+
1289 if (req->se->conn.proto_minor >= 12)
+
1290 req->ctx.umask = arg->umask;
+
1291
+
1292 if (req->se->op.mkdir)
+
1293 req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+
1294 else
+
1295 fuse_reply_err(req, ENOSYS);
+
1296}
+
1297
+
1298static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1299{
+
1300 char *name = (char *) inarg;
+
1301
+
1302 if (req->se->op.unlink)
+
1303 req->se->op.unlink(req, nodeid, name);
+
1304 else
+
1305 fuse_reply_err(req, ENOSYS);
+
1306}
+
1307
+
1308static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1309{
+
1310 char *name = (char *) inarg;
+
1311
+
1312 if (req->se->op.rmdir)
+
1313 req->se->op.rmdir(req, nodeid, name);
+
1314 else
+
1315 fuse_reply_err(req, ENOSYS);
+
1316}
+
1317
+
1318static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1319{
+
1320 char *name = (char *) inarg;
+
1321 char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
1322
+
1323 if (req->se->op.symlink)
+
1324 req->se->op.symlink(req, linkname, nodeid, name);
+
1325 else
+
1326 fuse_reply_err(req, ENOSYS);
+
1327}
+
1328
+
1329static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1330{
+
1331 struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+
1332 char *oldname = PARAM(arg);
+
1333 char *newname = oldname + strlen(oldname) + 1;
+
1334
+
1335 if (req->se->op.rename)
+
1336 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1337 0);
+
1338 else
+
1339 fuse_reply_err(req, ENOSYS);
+
1340}
+
1341
+
1342static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1343{
+
1344 struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg;
+
1345 char *oldname = PARAM(arg);
+
1346 char *newname = oldname + strlen(oldname) + 1;
+
1347
+
1348 if (req->se->op.rename)
+
1349 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1350 arg->flags);
+
1351 else
+
1352 fuse_reply_err(req, ENOSYS);
+
1353}
+
1354
+
1355static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1356{
+
1357 struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
1358
+
1359 if (req->se->op.link)
+
1360 req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+
1361 else
+
1362 fuse_reply_err(req, ENOSYS);
+
1363}
+
1364
+
1365static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1366{
+
1367 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1368
+
1369 if (req->se->op.tmpfile) {
+
1370 struct fuse_file_info fi;
+
1371
+
1372 memset(&fi, 0, sizeof(fi));
+
1373 fi.flags = arg->flags;
+
1374
+
1375 if (req->se->conn.proto_minor >= 12)
+
1376 req->ctx.umask = arg->umask;
+
1377
+
1378 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1379 } else
+
1380 fuse_reply_err(req, ENOSYS);
+
1381}
+
1382
+
1383static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1384{
+
1385 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1386
+
1387 if (req->se->op.create) {
+
1388 struct fuse_file_info fi;
+
1389 char *name = PARAM(arg);
+
1390
+
1391 memset(&fi, 0, sizeof(fi));
+
1392 fi.flags = arg->flags;
+
1393
+
1394 if (req->se->conn.proto_minor >= 12)
+
1395 req->ctx.umask = arg->umask;
+
1396 else
+
1397 name = (char *) inarg + sizeof(struct fuse_open_in);
+
1398
+
1399 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1400 } else
+
1401 fuse_reply_err(req, ENOSYS);
+
1402}
+
1403
+
1404static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1405{
+
1406 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1407 struct fuse_file_info fi;
+
1408
+
1409 memset(&fi, 0, sizeof(fi));
+
1410 fi.flags = arg->flags;
+
1411
+
1412 if (req->se->op.open)
+
1413 req->se->op.open(req, nodeid, &fi);
+
1414 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1415 fuse_reply_err(req, ENOSYS);
+
1416 else
+
1417 fuse_reply_open(req, &fi);
+
1418}
+
1419
+
1420static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1421{
+
1422 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1423
+
1424 if (req->se->op.read) {
+
1425 struct fuse_file_info fi;
+
1426
+
1427 memset(&fi, 0, sizeof(fi));
+
1428 fi.fh = arg->fh;
+
1429 if (req->se->conn.proto_minor >= 9) {
+
1430 fi.lock_owner = arg->lock_owner;
+
1431 fi.flags = arg->flags;
+
1432 }
+
1433 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1434 } else
+
1435 fuse_reply_err(req, ENOSYS);
+
1436}
+
1437
+
1438static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1439{
+
1440 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1441 struct fuse_file_info fi;
+
1442 char *param;
+
1443
+
1444 memset(&fi, 0, sizeof(fi));
+
1445 fi.fh = arg->fh;
+
1446 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1447
+
1448 if (req->se->conn.proto_minor < 9) {
+
1449 param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1450 } else {
+
1451 fi.lock_owner = arg->lock_owner;
+
1452 fi.flags = arg->flags;
+
1453 param = PARAM(arg);
+
1454 }
+
1455
+
1456 if (req->se->op.write)
+
1457 req->se->op.write(req, nodeid, param, arg->size,
+
1458 arg->offset, &fi);
+
1459 else
+
1460 fuse_reply_err(req, ENOSYS);
+
1461}
+
1462
+
1463static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+
1464 const struct fuse_buf *ibuf)
+
1465{
+
1466 struct fuse_session *se = req->se;
+
1467 struct fuse_bufvec bufv = {
+
1468 .buf[0] = *ibuf,
+
1469 .count = 1,
+
1470 };
+
1471 struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+
1472 struct fuse_file_info fi;
+
1473
+
1474 memset(&fi, 0, sizeof(fi));
+
1475 fi.fh = arg->fh;
+
1476 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1477
+
1478 if (se->conn.proto_minor < 9) {
+
1479 bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1480 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1481 FUSE_COMPAT_WRITE_IN_SIZE;
+
1482 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1483 } else {
+
1484 fi.lock_owner = arg->lock_owner;
+
1485 fi.flags = arg->flags;
+
1486 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1487 bufv.buf[0].mem = PARAM(arg);
+
1488
+
1489 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1490 sizeof(struct fuse_write_in);
+
1491 }
+
1492 if (bufv.buf[0].size < arg->size) {
+
1493 fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n");
+
1494 fuse_reply_err(req, EIO);
+
1495 goto out;
+
1496 }
+
1497 bufv.buf[0].size = arg->size;
+
1498
+
1499 se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
1500
+
1501out:
+
1502 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1503 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1504 fuse_ll_clear_pipe(se);
+
1505}
+
1506
+
1507static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1508{
+
1509 struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+
1510 struct fuse_file_info fi;
+
1511
+
1512 memset(&fi, 0, sizeof(fi));
+
1513 fi.fh = arg->fh;
+
1514 fi.flush = 1;
+
1515 if (req->se->conn.proto_minor >= 7)
+
1516 fi.lock_owner = arg->lock_owner;
+
1517
+
1518 if (req->se->op.flush)
+
1519 req->se->op.flush(req, nodeid, &fi);
+
1520 else
+
1521 fuse_reply_err(req, ENOSYS);
+
1522}
+
1523
+
1524static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1525{
+
1526 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1527 struct fuse_file_info fi;
+
1528
+
1529 memset(&fi, 0, sizeof(fi));
+
1530 fi.flags = arg->flags;
+
1531 fi.fh = arg->fh;
+
1532 if (req->se->conn.proto_minor >= 8) {
+
1533 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1534 fi.lock_owner = arg->lock_owner;
+
1535 }
+
1536 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1537 fi.flock_release = 1;
+
1538 fi.lock_owner = arg->lock_owner;
+
1539 }
+
1540
+
1541 if (req->se->op.release)
+
1542 req->se->op.release(req, nodeid, &fi);
+
1543 else
+
1544 fuse_reply_err(req, 0);
+
1545}
+
1546
+
1547static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1548{
+
1549 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1550 struct fuse_file_info fi;
+
1551 int datasync = arg->fsync_flags & 1;
+
1552
+
1553 memset(&fi, 0, sizeof(fi));
+
1554 fi.fh = arg->fh;
+
1555
+
1556 if (req->se->op.fsync)
+
1557 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1558 else
+
1559 fuse_reply_err(req, ENOSYS);
+
1560}
+
1561
+
1562static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1563{
+
1564 struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+
1565 struct fuse_file_info fi;
+
1566
+
1567 memset(&fi, 0, sizeof(fi));
+
1568 fi.flags = arg->flags;
+
1569
+
1570 if (req->se->op.opendir)
+
1571 req->se->op.opendir(req, nodeid, &fi);
+
1572 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1573 fuse_reply_err(req, ENOSYS);
+
1574 else
+
1575 fuse_reply_open(req, &fi);
+
1576}
+
1577
+
1578static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1579{
+
1580 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1581 struct fuse_file_info fi;
+
1582
+
1583 memset(&fi, 0, sizeof(fi));
+
1584 fi.fh = arg->fh;
+
1585
+
1586 if (req->se->op.readdir)
+
1587 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1588 else
+
1589 fuse_reply_err(req, ENOSYS);
+
1590}
+
1591
+
1592static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1593{
+
1594 struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
1595 struct fuse_file_info fi;
+
1596
+
1597 memset(&fi, 0, sizeof(fi));
+
1598 fi.fh = arg->fh;
+
1599
+
1600 if (req->se->op.readdirplus)
+
1601 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1602 else
+
1603 fuse_reply_err(req, ENOSYS);
+
1604}
+
1605
+
1606static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1607{
+
1608 struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+
1609 struct fuse_file_info fi;
+
1610
+
1611 memset(&fi, 0, sizeof(fi));
+
1612 fi.flags = arg->flags;
+
1613 fi.fh = arg->fh;
+
1614
+
1615 if (req->se->op.releasedir)
+
1616 req->se->op.releasedir(req, nodeid, &fi);
+
1617 else
+
1618 fuse_reply_err(req, 0);
+
1619}
+
1620
+
1621static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1622{
+
1623 struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+
1624 struct fuse_file_info fi;
+
1625 int datasync = arg->fsync_flags & 1;
+
1626
+
1627 memset(&fi, 0, sizeof(fi));
+
1628 fi.fh = arg->fh;
+
1629
+
1630 if (req->se->op.fsyncdir)
+
1631 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
1632 else
+
1633 fuse_reply_err(req, ENOSYS);
+
1634}
+
1635
+
1636static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1637{
+
1638 (void) nodeid;
+
1639 (void) inarg;
+
1640
+
1641 if (req->se->op.statfs)
+
1642 req->se->op.statfs(req, nodeid);
+
1643 else {
+
1644 struct statvfs buf = {
+
1645 .f_namemax = 255,
+
1646 .f_bsize = 512,
+
1647 };
+
1648 fuse_reply_statfs(req, &buf);
+
1649 }
+
1650}
+
1651
+
1652static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1653{
+
1654 struct fuse_session *se = req->se;
+
1655 unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT);
+
1656 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+
1657 char *name = xattr_ext ? PARAM(arg) :
+
1658 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
1659 char *value = name + strlen(name) + 1;
+
1660
+
1661 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
1662 if (req->se->op.setxattr)
+
1663 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
1664 arg->flags);
+
1665 else
+
1666 fuse_reply_err(req, ENOSYS);
+
1667}
+
1668
+
1669static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1670{
+
1671 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1672
+
1673 if (req->se->op.getxattr)
+
1674 req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+
1675 else
+
1676 fuse_reply_err(req, ENOSYS);
+
1677}
+
1678
+
1679static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1680{
+
1681 struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
1682
+
1683 if (req->se->op.listxattr)
+
1684 req->se->op.listxattr(req, nodeid, arg->size);
+
1685 else
+
1686 fuse_reply_err(req, ENOSYS);
+
1687}
+
1688
+
1689static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1690{
+
1691 char *name = (char *) inarg;
+
1692
+
1693 if (req->se->op.removexattr)
+
1694 req->se->op.removexattr(req, nodeid, name);
+
1695 else
+
1696 fuse_reply_err(req, ENOSYS);
+
1697}
+
1698
+
1699static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+
1700 struct flock *flock)
+
1701{
+
1702 memset(flock, 0, sizeof(struct flock));
+
1703 flock->l_type = fl->type;
+
1704 flock->l_whence = SEEK_SET;
+
1705 flock->l_start = fl->start;
+
1706 if (fl->end == OFFSET_MAX)
+
1707 flock->l_len = 0;
+
1708 else
+
1709 flock->l_len = fl->end - fl->start + 1;
+
1710 flock->l_pid = fl->pid;
+
1711}
+
1712
+
1713static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1714{
+
1715 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1716 struct fuse_file_info fi;
+
1717 struct flock flock;
+
1718
+
1719 memset(&fi, 0, sizeof(fi));
+
1720 fi.fh = arg->fh;
+
1721 fi.lock_owner = arg->owner;
+
1722
+
1723 convert_fuse_file_lock(&arg->lk, &flock);
+
1724 if (req->se->op.getlk)
+
1725 req->se->op.getlk(req, nodeid, &fi, &flock);
+
1726 else
+
1727 fuse_reply_err(req, ENOSYS);
+
1728}
+
1729
+
1730static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+
1731 const void *inarg, int sleep)
+
1732{
+
1733 struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+
1734 struct fuse_file_info fi;
+
1735 struct flock flock;
+
1736
+
1737 memset(&fi, 0, sizeof(fi));
+
1738 fi.fh = arg->fh;
+
1739 fi.lock_owner = arg->owner;
+
1740
+
1741 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
1742 int op = 0;
+
1743
+
1744 switch (arg->lk.type) {
+
1745 case F_RDLCK:
+
1746 op = LOCK_SH;
+
1747 break;
+
1748 case F_WRLCK:
+
1749 op = LOCK_EX;
+
1750 break;
+
1751 case F_UNLCK:
+
1752 op = LOCK_UN;
+
1753 break;
+
1754 }
+
1755 if (!sleep)
+
1756 op |= LOCK_NB;
+
1757
+
1758 if (req->se->op.flock)
+
1759 req->se->op.flock(req, nodeid, &fi, op);
+
1760 else
+
1761 fuse_reply_err(req, ENOSYS);
+
1762 } else {
+
1763 convert_fuse_file_lock(&arg->lk, &flock);
+
1764 if (req->se->op.setlk)
+
1765 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
1766 else
+
1767 fuse_reply_err(req, ENOSYS);
+
1768 }
+
1769}
+
1770
+
1771static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1772{
+
1773 do_setlk_common(req, nodeid, inarg, 0);
+
1774}
+
1775
+
1776static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1777{
+
1778 do_setlk_common(req, nodeid, inarg, 1);
+
1779}
+
1780
+
1781static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
1782{
+
1783 struct fuse_req *curr;
+
1784
+
1785 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
1786 if (curr->unique == req->u.i.unique) {
+ +
1788 void *data;
+
1789
+
1790 curr->ref_cnt++;
+
1791 pthread_mutex_unlock(&se->lock);
+
1792
+
1793 /* Ugh, ugly locking */
+
1794 pthread_mutex_lock(&curr->lock);
+
1795 pthread_mutex_lock(&se->lock);
+
1796 curr->interrupted = 1;
+
1797 func = curr->u.ni.func;
+
1798 data = curr->u.ni.data;
+
1799 pthread_mutex_unlock(&se->lock);
+
1800 if (func)
+
1801 func(curr, data);
+
1802 pthread_mutex_unlock(&curr->lock);
+
1803
+
1804 pthread_mutex_lock(&se->lock);
+
1805 curr->ref_cnt--;
+
1806 if (!curr->ref_cnt) {
+
1807 destroy_req(curr);
+
1808 }
+
1809
+
1810 return 1;
+
1811 }
+
1812 }
+
1813 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1814 curr = curr->next) {
+
1815 if (curr->u.i.unique == req->u.i.unique)
+
1816 return 1;
+
1817 }
+
1818 return 0;
+
1819}
+
1820
+
1821static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1822{
+
1823 struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+
1824 struct fuse_session *se = req->se;
+
1825
+
1826 (void) nodeid;
+
1827 if (se->debug)
+
1828 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
1829 (unsigned long long) arg->unique);
+
1830
+
1831 req->u.i.unique = arg->unique;
+
1832
+
1833 pthread_mutex_lock(&se->lock);
+
1834 if (find_interrupted(se, req)) {
+
1835 fuse_chan_put(req->ch);
+
1836 req->ch = NULL;
+
1837 destroy_req(req);
+
1838 } else
+
1839 list_add_req(req, &se->interrupts);
+
1840 pthread_mutex_unlock(&se->lock);
+
1841}
+
1842
+
1843static struct fuse_req *check_interrupt(struct fuse_session *se,
+
1844 struct fuse_req *req)
+
1845{
+
1846 struct fuse_req *curr;
+
1847
+
1848 for (curr = se->interrupts.next; curr != &se->interrupts;
+
1849 curr = curr->next) {
+
1850 if (curr->u.i.unique == req->unique) {
+
1851 req->interrupted = 1;
+
1852 list_del_req(curr);
+
1853 fuse_chan_put(curr->ch);
+
1854 curr->ch = NULL;
+
1855 destroy_req(curr);
+
1856 return NULL;
+
1857 }
+
1858 }
+
1859 curr = se->interrupts.next;
+
1860 if (curr != &se->interrupts) {
+
1861 list_del_req(curr);
+
1862 list_init_req(curr);
+
1863 return curr;
+
1864 } else
+
1865 return NULL;
+
1866}
+
1867
+
1868static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1869{
+
1870 struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
1871
+
1872 if (req->se->op.bmap)
+
1873 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
1874 else
+
1875 fuse_reply_err(req, ENOSYS);
+
1876}
+
1877
+
1878static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1879{
+
1880 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+
1881 unsigned int flags = arg->flags;
+
1882 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
1883 struct fuse_file_info fi;
+
1884
+
1885 if (flags & FUSE_IOCTL_DIR &&
+
1886 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
1887 fuse_reply_err(req, ENOTTY);
+
1888 return;
+
1889 }
+
1890
+
1891 memset(&fi, 0, sizeof(fi));
+
1892 fi.fh = arg->fh;
+
1893
+
1894 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
1895 !(flags & FUSE_IOCTL_32BIT)) {
+
1896 req->ioctl_64bit = 1;
+
1897 }
+
1898
+
1899 if (req->se->op.ioctl)
+
1900 req->se->op.ioctl(req, nodeid, arg->cmd,
+
1901 (void *)(uintptr_t)arg->arg, &fi, flags,
+
1902 in_buf, arg->in_size, arg->out_size);
+
1903 else
+
1904 fuse_reply_err(req, ENOSYS);
+
1905}
+
1906
+
1907void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
1908{
+
1909 free(ph);
+
1910}
+
1911
+
1912static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1913{
+
1914 struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+
1915 struct fuse_file_info fi;
+
1916
+
1917 memset(&fi, 0, sizeof(fi));
+
1918 fi.fh = arg->fh;
+
1919 fi.poll_events = arg->events;
+
1920
+
1921 if (req->se->op.poll) {
+
1922 struct fuse_pollhandle *ph = NULL;
+
1923
+
1924 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
1925 ph = malloc(sizeof(struct fuse_pollhandle));
+
1926 if (ph == NULL) {
+
1927 fuse_reply_err(req, ENOMEM);
+
1928 return;
+
1929 }
+
1930 ph->kh = arg->kh;
+
1931 ph->se = req->se;
+
1932 }
+
1933
+
1934 req->se->op.poll(req, nodeid, &fi, ph);
+
1935 } else {
+
1936 fuse_reply_err(req, ENOSYS);
+
1937 }
+
1938}
+
1939
+
1940static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1941{
+
1942 struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+
1943 struct fuse_file_info fi;
+
1944
+
1945 memset(&fi, 0, sizeof(fi));
+
1946 fi.fh = arg->fh;
+
1947
+
1948 if (req->se->op.fallocate)
+
1949 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+
1950 else
+
1951 fuse_reply_err(req, ENOSYS);
+
1952}
+
1953
+
1954static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
+
1955{
+
1956 struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
+
1957 struct fuse_file_info fi_in, fi_out;
+
1958
+
1959 memset(&fi_in, 0, sizeof(fi_in));
+
1960 fi_in.fh = arg->fh_in;
+
1961
+
1962 memset(&fi_out, 0, sizeof(fi_out));
+
1963 fi_out.fh = arg->fh_out;
+
1964
+
1965
+
1966 if (req->se->op.copy_file_range)
+
1967 req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
+
1968 &fi_in, arg->nodeid_out,
+
1969 arg->off_out, &fi_out, arg->len,
+
1970 arg->flags);
+
1971 else
+
1972 fuse_reply_err(req, ENOSYS);
+
1973}
+
1974
+
1975static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1976{
+
1977 struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg;
+
1978 struct fuse_file_info fi;
+
1979
+
1980 memset(&fi, 0, sizeof(fi));
+
1981 fi.fh = arg->fh;
+
1982
+
1983 if (req->se->op.lseek)
+
1984 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
1985 else
+
1986 fuse_reply_err(req, ENOSYS);
+
1987}
+
1988
+
1989static bool want_flags_valid(uint64_t capable, uint64_t want)
+
1990{
+
1991 uint64_t unknown_flags = want & (~capable);
+
1992 if (unknown_flags != 0) {
+
1993 fuse_log(FUSE_LOG_ERR,
+
1994 "fuse: unknown connection 'want' flags: 0x%08lx\n",
+
1995 unknown_flags);
+
1996 return false;
+
1997 }
+
1998 return true;
+
1999}
+
2000
+
2001/* Prevent bogus data races (bogus since "init" is called before
+
2002 * multi-threading becomes relevant */
+
2003static __attribute__((no_sanitize("thread")))
+
2004void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2005{
+
2006 struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+
2007 struct fuse_init_out outarg;
+
2008 struct fuse_session *se = req->se;
+
2009 size_t bufsize = se->bufsize;
+
2010 size_t outargsize = sizeof(outarg);
+
2011 uint64_t inargflags = 0;
+
2012 uint64_t outargflags = 0;
+
2013 bool buf_reallocable = se->buf_reallocable;
+
2014 (void) nodeid;
+
2015 if (se->debug) {
+
2016 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2017 if (arg->major == 7 && arg->minor >= 6) {
+
2018 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2019 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2020 arg->max_readahead);
+
2021 }
+
2022 }
+
2023 se->conn.proto_major = arg->major;
+
2024 se->conn.proto_minor = arg->minor;
+
2025 se->conn.capable_ext = 0;
+
2026 se->conn.want_ext = 0;
+
2027
+
2028 memset(&outarg, 0, sizeof(outarg));
+
2029 outarg.major = FUSE_KERNEL_VERSION;
+
2030 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2031
+
2032 if (arg->major < 7) {
+
2033 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2034 arg->major, arg->minor);
+
2035 fuse_reply_err(req, EPROTO);
+
2036 return;
+
2037 }
+
2038
+
2039 if (arg->major > 7) {
+
2040 /* Wait for a second INIT request with a 7.X version */
+
2041 send_reply_ok(req, &outarg, sizeof(outarg));
+
2042 return;
+
2043 }
+
2044
+
2045 if (arg->minor >= 6) {
+
2046 if (arg->max_readahead < se->conn.max_readahead)
+
2047 se->conn.max_readahead = arg->max_readahead;
+
2048 inargflags = arg->flags;
+
2049 if (inargflags & FUSE_INIT_EXT)
+
2050 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2051 if (inargflags & FUSE_ASYNC_READ)
+
2052 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2053 if (inargflags & FUSE_POSIX_LOCKS)
+
2054 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2055 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2056 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2057 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2058 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2059 if (inargflags & FUSE_DONT_MASK)
+
2060 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2061 if (inargflags & FUSE_FLOCK_LOCKS)
+
2062 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2063 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2064 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2065 if (inargflags & FUSE_DO_READDIRPLUS)
+
2066 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2067 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2068 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2069 if (inargflags & FUSE_ASYNC_DIO)
+
2070 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2071 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2072 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2073 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2074 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2075 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2076 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2077 if (inargflags & FUSE_POSIX_ACL)
+
2078 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2079 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2080 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2081 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2082 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2083 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2084 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2085 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2086 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2087 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2088 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2089 if (inargflags & FUSE_SETXATTR_EXT)
+
2090 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2091 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2092 size_t max_bufsize =
+
2093 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2094 + FUSE_BUFFER_HEADER_SIZE;
+
2095 if (bufsize > max_bufsize) {
+
2096 bufsize = max_bufsize;
+
2097 }
+
2098 buf_reallocable = false;
+
2099 }
+
2100 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2101 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2102 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2103 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2104 if (inargflags & FUSE_PASSTHROUGH)
+
2105 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2106 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2107 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2108 } else {
+
2109 se->conn.max_readahead = 0;
+
2110 }
+
2111
+
2112 if (se->conn.proto_minor >= 14) {
+
2113#ifdef HAVE_SPLICE
+
2114#ifdef HAVE_VMSPLICE
+
2115 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2116 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2118 }
+
2119#endif
+
2120 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2121 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2122 }
+
2123#endif
+
2124 }
+
2125 if (se->conn.proto_minor >= 18)
+
2126 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2127
+
2128 /* Default settings for modern filesystems.
+
2129 *
+
2130 * Most of these capabilities were disabled by default in
+
2131 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2132 * we can finally enable them by default (as long as they're
+
2133 * supported by the kernel).
+
2134 */
+
2135#define LL_SET_DEFAULT(cond, cap) \
+
2136 if ((cond)) \
+
2137 fuse_set_feature_flag(&se->conn, cap)
+
2138
+
2139 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2140 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2141 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2142 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2143 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2144 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2145 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2147 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2148 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2149 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2151
+
2152 /* This could safely become default, but libfuse needs an API extension
+
2153 * to support it
+
2154 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2155 */
+
2156
+
2157 se->conn.time_gran = 1;
+
2158
+
2159 se->got_init = 1;
+
2160 if (se->op.init) {
+
2161 uint64_t want_ext_default = se->conn.want_ext;
+
2162 uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext);
+
2163 int rc;
+
2164
+
2165 // Apply the first 32 bits of capable_ext to capable
+
2166 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2167 se->conn.want = want_default;
+
2168
+
2169 se->op.init(se->userdata, &se->conn);
+
2170
+
2171 /*
+
2172 * se->conn.want is 32-bit value and deprecated in favour of
+
2173 * se->conn.want_ext
+
2174 * Userspace might still use conn.want - we need to convert it
+
2175 */
+
2176 rc = convert_to_conn_want_ext(&se->conn, want_ext_default,
+
2177 want_default);
+
2178 if (rc != 0) {
+
2179 fuse_reply_err(req, EPROTO);
+
2180 se->error = -EPROTO;
+ +
2182 return;
+
2183 }
+
2184 }
+
2185
+
2186 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2187 fuse_reply_err(req, EPROTO);
+
2188 se->error = -EPROTO;
+ +
2190 return;
+
2191 }
+
2192
+
2193 unsigned max_read_mo = get_max_read(se->mo);
+
2194 if (se->conn.max_read != max_read_mo) {
+
2195 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2196 "requested different maximum read size (%u vs %u)\n",
+
2197 se->conn.max_read, max_read_mo);
+
2198 fuse_reply_err(req, EPROTO);
+
2199 se->error = -EPROTO;
+ +
2201 return;
+
2202 }
+
2203
+
2204 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2205 fuse_log(FUSE_LOG_ERR,
+
2206 "fuse: warning: buffer size too small: %zu\n",
+
2207 bufsize);
+
2208 bufsize = FUSE_MIN_READ_BUFFER;
+
2209 }
+
2210
+
2211 if (buf_reallocable)
+
2212 bufsize = UINT_MAX;
+
2213 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2214 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2215
+
2216 if (arg->flags & FUSE_MAX_PAGES) {
+
2217 outarg.flags |= FUSE_MAX_PAGES;
+
2218 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2219 }
+
2220 outargflags = outarg.flags;
+
2221 /* Always enable big writes, this is superseded
+
2222 by the max_write option */
+
2223 outargflags |= FUSE_BIG_WRITES;
+
2224
+
2225 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2226 outargflags |= FUSE_ASYNC_READ;
+
2227 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2228 outargflags |= FUSE_POSIX_LOCKS;
+
2229 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2230 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2231 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2232 outargflags |= FUSE_EXPORT_SUPPORT;
+
2233 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2234 outargflags |= FUSE_DONT_MASK;
+
2235 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2236 outargflags |= FUSE_FLOCK_LOCKS;
+
2237 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2238 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2239 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2240 outargflags |= FUSE_DO_READDIRPLUS;
+
2241 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2242 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2243 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2244 outargflags |= FUSE_ASYNC_DIO;
+
2245 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2246 outargflags |= FUSE_WRITEBACK_CACHE;
+
2247 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2248 outargflags |= FUSE_PARALLEL_DIROPS;
+
2249 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2250 outargflags |= FUSE_POSIX_ACL;
+
2251 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2252 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2253 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2254 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2255 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2256 outargflags |= FUSE_CACHE_SYMLINKS;
+
2257 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2258 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2259 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2260 outargflags |= FUSE_SETXATTR_EXT;
+
2261 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2262 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2263 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2264 outargflags |= FUSE_PASSTHROUGH;
+
2265 /*
+
2266 * outarg.max_stack_depth includes the fuse stack layer,
+
2267 * so it is one more than max_backing_stack_depth.
+
2268 */
+
2269 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2270 }
+
2271 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2272 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2273
+
2274 if (inargflags & FUSE_INIT_EXT) {
+
2275 outargflags |= FUSE_INIT_EXT;
+
2276 outarg.flags2 = outargflags >> 32;
+
2277 }
+
2278
+
2279 outarg.flags = outargflags;
+
2280
+
2281 outarg.max_readahead = se->conn.max_readahead;
+
2282 outarg.max_write = se->conn.max_write;
+
2283 if (se->conn.proto_minor >= 13) {
+
2284 if (se->conn.max_background >= (1 << 16))
+
2285 se->conn.max_background = (1 << 16) - 1;
+
2286 if (se->conn.congestion_threshold > se->conn.max_background)
+
2287 se->conn.congestion_threshold = se->conn.max_background;
+
2288 if (!se->conn.congestion_threshold) {
+
2289 se->conn.congestion_threshold =
+
2290 se->conn.max_background * 3 / 4;
+
2291 }
+
2292
+
2293 outarg.max_background = se->conn.max_background;
+
2294 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2295 }
+
2296 if (se->conn.proto_minor >= 23)
+
2297 outarg.time_gran = se->conn.time_gran;
+
2298
+
2299 if (se->debug) {
+
2300 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2301 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2302 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2303 outarg.max_readahead);
+
2304 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2305 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2306 outarg.max_background);
+
2307 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2308 outarg.congestion_threshold);
+
2309 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2310 outarg.time_gran);
+
2311 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2312 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2313 outarg.max_stack_depth);
+
2314 }
+
2315 if (arg->minor < 5)
+
2316 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2317 else if (arg->minor < 23)
+
2318 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2319
+
2320 send_reply_ok(req, &outarg, outargsize);
+
2321}
+
2322
+
2323static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2324{
+
2325 struct fuse_session *se = req->se;
+
2326
+
2327 (void) nodeid;
+
2328 (void) inarg;
+
2329
+
2330 se->got_destroy = 1;
+
2331 se->got_init = 0;
+
2332 if (se->op.destroy)
+
2333 se->op.destroy(se->userdata);
+
2334
+
2335 send_reply_ok(req, NULL, 0);
+
2336}
+
2337
+
2338static void list_del_nreq(struct fuse_notify_req *nreq)
+
2339{
+
2340 struct fuse_notify_req *prev = nreq->prev;
+
2341 struct fuse_notify_req *next = nreq->next;
+
2342 prev->next = next;
+
2343 next->prev = prev;
+
2344}
+
2345
+
2346static void list_add_nreq(struct fuse_notify_req *nreq,
+
2347 struct fuse_notify_req *next)
+
2348{
+
2349 struct fuse_notify_req *prev = next->prev;
+
2350 nreq->next = next;
+
2351 nreq->prev = prev;
+
2352 prev->next = nreq;
+
2353 next->prev = nreq;
+
2354}
+
2355
+
2356static void list_init_nreq(struct fuse_notify_req *nreq)
+
2357{
+
2358 nreq->next = nreq;
+
2359 nreq->prev = nreq;
+
2360}
+
2361
+
2362static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
2363 const void *inarg, const struct fuse_buf *buf)
+
2364{
+
2365 struct fuse_session *se = req->se;
+
2366 struct fuse_notify_req *nreq;
+
2367 struct fuse_notify_req *head;
+
2368
+
2369 pthread_mutex_lock(&se->lock);
+
2370 head = &se->notify_list;
+
2371 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
2372 if (nreq->unique == req->unique) {
+
2373 list_del_nreq(nreq);
+
2374 break;
+
2375 }
+
2376 }
+
2377 pthread_mutex_unlock(&se->lock);
+
2378
+
2379 if (nreq != head)
+
2380 nreq->reply(nreq, req, nodeid, inarg, buf);
+
2381}
+
2382
+
2383static int send_notify_iov(struct fuse_session *se, int notify_code,
+
2384 struct iovec *iov, int count)
+
2385{
+
2386 struct fuse_out_header out;
+
2387
+
2388 if (!se->got_init)
+
2389 return -ENOTCONN;
+
2390
+
2391 out.unique = 0;
+
2392 out.error = notify_code;
+
2393 iov[0].iov_base = &out;
+
2394 iov[0].iov_len = sizeof(struct fuse_out_header);
+
2395
+
2396 return fuse_send_msg(se, NULL, iov, count);
+
2397}
+
2398
+
2399int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
2400{
+
2401 if (ph != NULL) {
+
2402 struct fuse_notify_poll_wakeup_out outarg;
+
2403 struct iovec iov[2];
+
2404
+
2405 outarg.kh = ph->kh;
+
2406
+
2407 iov[1].iov_base = &outarg;
+
2408 iov[1].iov_len = sizeof(outarg);
+
2409
+
2410 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
2411 } else {
+
2412 return 0;
+
2413 }
+
2414}
+
2415
+
2416int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
2417 off_t off, off_t len)
+
2418{
+
2419 struct fuse_notify_inval_inode_out outarg;
+
2420 struct iovec iov[2];
+
2421
+
2422 if (!se)
+
2423 return -EINVAL;
+
2424
+
2425 if (se->conn.proto_minor < 12)
+
2426 return -ENOSYS;
+
2427
+
2428 outarg.ino = ino;
+
2429 outarg.off = off;
+
2430 outarg.len = len;
+
2431
+
2432 iov[1].iov_base = &outarg;
+
2433 iov[1].iov_len = sizeof(outarg);
+
2434
+
2435 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
2436}
+
2437
+
2457static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
2458 const char *name, size_t namelen,
+
2459 enum fuse_notify_entry_flags flags)
+
2460{
+
2461 struct fuse_notify_inval_entry_out outarg;
+
2462 struct iovec iov[3];
+
2463
+
2464 if (!se)
+
2465 return -EINVAL;
+
2466
+
2467 if (se->conn.proto_minor < 12)
+
2468 return -ENOSYS;
+
2469
+
2470 outarg.parent = parent;
+
2471 outarg.namelen = namelen;
+
2472 outarg.flags = 0;
+
2473 if (flags & FUSE_LL_EXPIRE_ONLY)
+
2474 outarg.flags |= FUSE_EXPIRE_ONLY;
+
2475
+
2476 iov[1].iov_base = &outarg;
+
2477 iov[1].iov_len = sizeof(outarg);
+
2478 iov[2].iov_base = (void *)name;
+
2479 iov[2].iov_len = namelen + 1;
+
2480
+
2481 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
2482}
+
2483
+
2484int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
2485 const char *name, size_t namelen)
+
2486{
+
2487 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
2488}
+
2489
+
2490int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
2491 const char *name, size_t namelen)
+
2492{
+
2493 if (!se)
+
2494 return -EINVAL;
+
2495
+
2496 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
2497 return -ENOSYS;
+
2498
+
2499 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
2500}
+
2501
+
2502
+
2503int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
2504 fuse_ino_t parent, fuse_ino_t child,
+
2505 const char *name, size_t namelen)
+
2506{
+
2507 struct fuse_notify_delete_out outarg;
+
2508 struct iovec iov[3];
+
2509
+
2510 if (!se)
+
2511 return -EINVAL;
+
2512
+
2513 if (se->conn.proto_minor < 18)
+
2514 return -ENOSYS;
+
2515
+
2516 outarg.parent = parent;
+
2517 outarg.child = child;
+
2518 outarg.namelen = namelen;
+
2519 outarg.padding = 0;
+
2520
+
2521 iov[1].iov_base = &outarg;
+
2522 iov[1].iov_len = sizeof(outarg);
+
2523 iov[2].iov_base = (void *)name;
+
2524 iov[2].iov_len = namelen + 1;
+
2525
+
2526 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
2527}
+
2528
+
2529int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
2530 off_t offset, struct fuse_bufvec *bufv,
+
2531 enum fuse_buf_copy_flags flags)
+
2532{
+
2533 struct fuse_out_header out;
+
2534 struct fuse_notify_store_out outarg;
+
2535 struct iovec iov[3];
+
2536 size_t size = fuse_buf_size(bufv);
+
2537 int res;
+
2538
+
2539 if (!se)
+
2540 return -EINVAL;
+
2541
+
2542 if (se->conn.proto_minor < 15)
+
2543 return -ENOSYS;
+
2544
+
2545 out.unique = 0;
+
2546 out.error = FUSE_NOTIFY_STORE;
+
2547
+
2548 outarg.nodeid = ino;
+
2549 outarg.offset = offset;
+
2550 outarg.size = size;
+
2551 outarg.padding = 0;
+
2552
+
2553 iov[0].iov_base = &out;
+
2554 iov[0].iov_len = sizeof(out);
+
2555 iov[1].iov_base = &outarg;
+
2556 iov[1].iov_len = sizeof(outarg);
+
2557
+
2558 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags);
+
2559 if (res > 0)
+
2560 res = -res;
+
2561
+
2562 return res;
+
2563}
+
2564
+
2565struct fuse_retrieve_req {
+
2566 struct fuse_notify_req nreq;
+
2567 void *cookie;
+
2568};
+
2569
+
2570static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
2571 fuse_req_t req, fuse_ino_t ino,
+
2572 const void *inarg,
+
2573 const struct fuse_buf *ibuf)
+
2574{
+
2575 struct fuse_session *se = req->se;
+
2576 struct fuse_retrieve_req *rreq =
+
2577 container_of(nreq, struct fuse_retrieve_req, nreq);
+
2578 const struct fuse_notify_retrieve_in *arg = inarg;
+
2579 struct fuse_bufvec bufv = {
+
2580 .buf[0] = *ibuf,
+
2581 .count = 1,
+
2582 };
+
2583
+
2584 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
2585 bufv.buf[0].mem = PARAM(arg);
+
2586
+
2587 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
2588 sizeof(struct fuse_notify_retrieve_in);
+
2589
+
2590 if (bufv.buf[0].size < arg->size) {
+
2591 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
2592 fuse_reply_none(req);
+
2593 goto out;
+
2594 }
+
2595 bufv.buf[0].size = arg->size;
+
2596
+
2597 if (se->op.retrieve_reply) {
+
2598 se->op.retrieve_reply(req, rreq->cookie, ino,
+
2599 arg->offset, &bufv);
+
2600 } else {
+
2601 fuse_reply_none(req);
+
2602 }
+
2603out:
+
2604 free(rreq);
+
2605 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
2606 fuse_ll_clear_pipe(se);
+
2607}
+
2608
+
2609int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
2610 size_t size, off_t offset, void *cookie)
+
2611{
+
2612 struct fuse_notify_retrieve_out outarg;
+
2613 struct iovec iov[2];
+
2614 struct fuse_retrieve_req *rreq;
+
2615 int err;
+
2616
+
2617 if (!se)
+
2618 return -EINVAL;
+
2619
+
2620 if (se->conn.proto_minor < 15)
+
2621 return -ENOSYS;
+
2622
+
2623 rreq = malloc(sizeof(*rreq));
+
2624 if (rreq == NULL)
+
2625 return -ENOMEM;
+
2626
+
2627 pthread_mutex_lock(&se->lock);
+
2628 rreq->cookie = cookie;
+
2629 rreq->nreq.unique = se->notify_ctr++;
+
2630 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
2631 list_add_nreq(&rreq->nreq, &se->notify_list);
+
2632 pthread_mutex_unlock(&se->lock);
+
2633
+
2634 outarg.notify_unique = rreq->nreq.unique;
+
2635 outarg.nodeid = ino;
+
2636 outarg.offset = offset;
+
2637 outarg.size = size;
+
2638 outarg.padding = 0;
+
2639
+
2640 iov[1].iov_base = &outarg;
+
2641 iov[1].iov_len = sizeof(outarg);
+
2642
+
2643 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
2644 if (err) {
+
2645 pthread_mutex_lock(&se->lock);
+
2646 list_del_nreq(&rreq->nreq);
+
2647 pthread_mutex_unlock(&se->lock);
+
2648 free(rreq);
+
2649 }
+
2650
+
2651 return err;
+
2652}
+
2653
+ +
2655{
+
2656 return req->se->userdata;
+
2657}
+
2658
+
2659const struct fuse_ctx *fuse_req_ctx(fuse_req_t req)
+
2660{
+
2661 return &req->ctx;
+
2662}
+
2663
+ +
2665 void *data)
+
2666{
+
2667 pthread_mutex_lock(&req->lock);
+
2668 pthread_mutex_lock(&req->se->lock);
+
2669 req->u.ni.func = func;
+
2670 req->u.ni.data = data;
+
2671 pthread_mutex_unlock(&req->se->lock);
+
2672 if (req->interrupted && func)
+
2673 func(req, data);
+
2674 pthread_mutex_unlock(&req->lock);
+
2675}
+
2676
+ +
2678{
+
2679 int interrupted;
+
2680
+
2681 pthread_mutex_lock(&req->se->lock);
+
2682 interrupted = req->interrupted;
+
2683 pthread_mutex_unlock(&req->se->lock);
+
2684
+
2685 return interrupted;
+
2686}
+
2687
+
2688static struct {
+
2689 void (*func)(fuse_req_t, fuse_ino_t, const void *);
+
2690 const char *name;
+
2691} fuse_ll_ops[] = {
+
2692 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
2693 [FUSE_FORGET] = { do_forget, "FORGET" },
+
2694 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
2695 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
2696 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
2697 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
2698 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
2699 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
2700 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
2701 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
2702 [FUSE_RENAME] = { do_rename, "RENAME" },
+
2703 [FUSE_LINK] = { do_link, "LINK" },
+
2704 [FUSE_OPEN] = { do_open, "OPEN" },
+
2705 [FUSE_READ] = { do_read, "READ" },
+
2706 [FUSE_WRITE] = { do_write, "WRITE" },
+
2707 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
2708 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
2709 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
2710 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
2711 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
2712 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
2713 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
2714 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
2715 [FUSE_INIT] = { do_init, "INIT" },
+
2716 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
2717 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
2718 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
2719 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
2720 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
2721 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
2722 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
2723 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
2724 [FUSE_CREATE] = { do_create, "CREATE" },
+
2725 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
2726 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
2727 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
2728 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
2729 [FUSE_POLL] = { do_poll, "POLL" },
+
2730 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
2731 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
2732 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
2733 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
2734 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
2735 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
2736 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
2737 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
2738 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
2739};
+
2740
+
2741/*
+
2742 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
2743 * Without ABI compatibility we could use the size of the array.
+
2744 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
2745 */
+
2746#define FUSE_MAXOP (CUSE_INIT + 1)
+
2747
+
2748static const char *opname(enum fuse_opcode opcode)
+
2749{
+
2750 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
2751 return "???";
+
2752 else
+
2753 return fuse_ll_ops[opcode].name;
+
2754}
+
2755
+
2756static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
2757 struct fuse_bufvec *src)
+
2758{
+
2759 ssize_t res = fuse_buf_copy(dst, src, 0);
+
2760 if (res < 0) {
+
2761 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
2762 return res;
+
2763 }
+
2764 if ((size_t)res < fuse_buf_size(dst)) {
+
2765 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
2766 return -1;
+
2767 }
+
2768 return 0;
+
2769}
+
2770
+
2771void fuse_session_process_buf(struct fuse_session *se,
+
2772 const struct fuse_buf *buf)
+
2773{
+
2774 fuse_session_process_buf_internal(se, buf, NULL);
+
2775}
+
2776
+
2777/* libfuse internal handler */
+
2778void fuse_session_process_buf_internal(struct fuse_session *se,
+
2779 const struct fuse_buf *buf, struct fuse_chan *ch)
+
2780{
+
2781 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
2782 sizeof(struct fuse_write_in);
+
2783 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
2784 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
2785 struct fuse_in_header *in;
+
2786 const void *inarg;
+
2787 struct fuse_req *req;
+
2788 void *mbuf = NULL;
+
2789 int err;
+
2790 int res;
+
2791
+
2792 if (buf->flags & FUSE_BUF_IS_FD) {
+
2793 if (buf->size < tmpbuf.buf[0].size)
+
2794 tmpbuf.buf[0].size = buf->size;
+
2795
+
2796 mbuf = malloc(tmpbuf.buf[0].size);
+
2797 if (mbuf == NULL) {
+
2798 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
2799 goto clear_pipe;
+
2800 }
+
2801 tmpbuf.buf[0].mem = mbuf;
+
2802
+
2803 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2804 if (res < 0)
+
2805 goto clear_pipe;
+
2806
+
2807 in = mbuf;
+
2808 } else {
+
2809 in = buf->mem;
+
2810 }
+
2811
+
2812 if (se->debug) {
+
2813 fuse_log(FUSE_LOG_DEBUG,
+
2814 "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
2815 (unsigned long long) in->unique,
+
2816 opname((enum fuse_opcode) in->opcode), in->opcode,
+
2817 (unsigned long long) in->nodeid, buf->size, in->pid);
+
2818 }
+
2819
+
2820 req = fuse_ll_alloc_req(se);
+
2821 if (req == NULL) {
+
2822 struct fuse_out_header out = {
+
2823 .unique = in->unique,
+
2824 .error = -ENOMEM,
+
2825 };
+
2826 struct iovec iov = {
+
2827 .iov_base = &out,
+
2828 .iov_len = sizeof(struct fuse_out_header),
+
2829 };
+
2830
+
2831 fuse_send_msg(se, ch, &iov, 1);
+
2832 goto clear_pipe;
+
2833 }
+
2834
+
2835 req->unique = in->unique;
+
2836 req->ctx.uid = in->uid;
+
2837 req->ctx.gid = in->gid;
+
2838 req->ctx.pid = in->pid;
+
2839 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
2840
+
2841 err = EIO;
+
2842 if (!se->got_init) {
+
2843 enum fuse_opcode expected;
+
2844
+
2845 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
2846 if (in->opcode != expected)
+
2847 goto reply_err;
+
2848 } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+
2849 goto reply_err;
+
2850
+
2851 err = EACCES;
+
2852 /* Implement -o allow_root */
+
2853 if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
+
2854 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+
2855 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+
2856 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+
2857 in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+
2858 in->opcode != FUSE_NOTIFY_REPLY &&
+
2859 in->opcode != FUSE_READDIRPLUS)
+
2860 goto reply_err;
+
2861
+
2862 err = ENOSYS;
+
2863 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
2864 goto reply_err;
+
2865 /* Do not process interrupt request */
+
2866 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
2867 if (se->debug)
+
2868 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
2869 goto reply_err;
+
2870 }
+
2871 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
2872 struct fuse_req *intr;
+
2873 pthread_mutex_lock(&se->lock);
+
2874 intr = check_interrupt(se, req);
+
2875 list_add_req(req, &se->list);
+
2876 pthread_mutex_unlock(&se->lock);
+
2877 if (intr)
+
2878 fuse_reply_err(intr, EAGAIN);
+
2879 }
+
2880
+
2881 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
2882 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
2883 in->opcode != FUSE_NOTIFY_REPLY) {
+
2884 void *newmbuf;
+
2885
+
2886 err = ENOMEM;
+
2887 newmbuf = realloc(mbuf, buf->size);
+
2888 if (newmbuf == NULL)
+
2889 goto reply_err;
+
2890 mbuf = newmbuf;
+
2891
+
2892 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
2893 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
2894
+
2895 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
2896 err = -res;
+
2897 if (res < 0)
+
2898 goto reply_err;
+
2899
+
2900 in = mbuf;
+
2901 }
+
2902
+
2903 inarg = (void *) &in[1];
+
2904 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
2905 do_write_buf(req, in->nodeid, inarg, buf);
+
2906 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
2907 do_notify_reply(req, in->nodeid, inarg, buf);
+
2908 else
+
2909 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
2910
+
2911out_free:
+
2912 free(mbuf);
+
2913 return;
+
2914
+
2915reply_err:
+
2916 fuse_reply_err(req, err);
+
2917clear_pipe:
+
2918 if (buf->flags & FUSE_BUF_IS_FD)
+
2919 fuse_ll_clear_pipe(se);
+
2920 goto out_free;
+
2921}
+
2922
+
2923#define LL_OPTION(n,o,v) \
+
2924 { n, offsetof(struct fuse_session, o), v }
+
2925
+
2926static const struct fuse_opt fuse_ll_opts[] = {
+
2927 LL_OPTION("debug", debug, 1),
+
2928 LL_OPTION("-d", debug, 1),
+
2929 LL_OPTION("--debug", debug, 1),
+
2930 LL_OPTION("allow_root", deny_others, 1),
+ +
2932};
+
2933
+
2934void fuse_lowlevel_version(void)
+
2935{
+
2936 printf("using FUSE kernel interface version %i.%i\n",
+
2937 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
2938 fuse_mount_version();
+
2939}
+
2940
+
2941void fuse_lowlevel_help(void)
+
2942{
+
2943 /* These are not all options, but the ones that are
+
2944 potentially of interest to an end-user */
+
2945 printf(
+
2946" -o allow_other allow access by all users\n"
+
2947" -o allow_root allow access by root\n"
+
2948" -o auto_unmount auto unmount on process termination\n");
+
2949}
+
2950
+
2951void fuse_session_destroy(struct fuse_session *se)
+
2952{
+
2953 struct fuse_ll_pipe *llp;
+
2954
+
2955 if (se->got_init && !se->got_destroy) {
+
2956 if (se->op.destroy)
+
2957 se->op.destroy(se->userdata);
+
2958 }
+
2959 llp = pthread_getspecific(se->pipe_key);
+
2960 if (llp != NULL)
+
2961 fuse_ll_pipe_free(llp);
+
2962 pthread_key_delete(se->pipe_key);
+
2963 pthread_mutex_destroy(&se->lock);
+
2964 free(se->cuse_data);
+
2965 if (se->fd != -1)
+
2966 close(se->fd);
+
2967 if (se->io != NULL)
+
2968 free(se->io);
+
2969 destroy_mount_opts(se->mo);
+
2970 free(se);
+
2971}
+
2972
+
2973
+
2974static void fuse_ll_pipe_destructor(void *data)
+
2975{
+
2976 struct fuse_ll_pipe *llp = data;
+
2977 fuse_ll_pipe_free(llp);
+
2978}
+
2979
+
2980void fuse_buf_free(struct fuse_buf *buf)
+
2981{
+
2982 if (buf->mem == NULL)
+
2983 return;
+
2984
+
2985 size_t write_header_sz =
+
2986 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
2987
+
2988 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
2989 free(ptr);
+
2990 buf->mem = NULL;
+
2991}
+
2992
+
2993/*
+
2994 * This is used to allocate buffers that hold fuse requests
+
2995 */
+
2996static void *buf_alloc(size_t size, bool internal)
+
2997{
+
2998 /*
+
2999 * For libfuse internal caller add in alignment. That cannot be done
+
3000 * for an external caller, as it is not guaranteed that the external
+
3001 * caller frees the raw pointer.
+
3002 */
+
3003 if (internal) {
+
3004 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3005 sizeof(struct fuse_write_in);
+
3006 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3007
+
3008 char *buf = aligned_alloc(pagesize, new_size);
+
3009 if (buf == NULL)
+
3010 return NULL;
+
3011
+
3012 buf += pagesize - write_header_sz;
+
3013
+
3014 return buf;
+
3015 } else {
+
3016 return malloc(size);
+
3017 }
+
3018}
+
3019
+
3020/*
+
3021 *@param internal true if called from libfuse internal code
+
3022 */
+
3023static int _fuse_session_receive_buf(struct fuse_session *se,
+
3024 struct fuse_buf *buf, struct fuse_chan *ch,
+
3025 bool internal)
+
3026{
+
3027 int err;
+
3028 ssize_t res;
+
3029 size_t bufsize;
+
3030#ifdef HAVE_SPLICE
+
3031 struct fuse_ll_pipe *llp;
+
3032 struct fuse_buf tmpbuf;
+
3033
+
3034pipe_retry:
+
3035 bufsize = se->bufsize;
+
3036
+
3037 if (se->conn.proto_minor < 14 ||
+
3038 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3039 goto fallback;
+
3040
+
3041 llp = fuse_ll_get_pipe(se);
+
3042 if (llp == NULL)
+
3043 goto fallback;
+
3044
+
3045 if (llp->size < bufsize) {
+
3046 if (llp->can_grow) {
+
3047 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3048 if (res == -1) {
+
3049 llp->can_grow = 0;
+
3050 res = grow_pipe_to_max(llp->pipe[0]);
+
3051 if (res > 0)
+
3052 llp->size = res;
+
3053 goto fallback;
+
3054 }
+
3055 llp->size = res;
+
3056 }
+
3057 if (llp->size < bufsize)
+
3058 goto fallback;
+
3059 }
+
3060
+
3061 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3062 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3063 llp->pipe[1], NULL, bufsize, 0,
+
3064 se->userdata);
+
3065 } else {
+
3066 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3067 bufsize, 0);
+
3068 }
+
3069 err = errno;
+
3070
+
3071 if (fuse_session_exited(se))
+
3072 return 0;
+
3073
+
3074 if (res == -1) {
+
3075 if (err == ENODEV) {
+
3076 /* Filesystem was unmounted, or connection was aborted
+
3077 via /sys/fs/fuse/connections */
+ +
3079 return 0;
+
3080 }
+
3081
+
3082 /* FUSE_INIT might have increased the required bufsize */
+
3083 if (err == EINVAL && bufsize < se->bufsize) {
+
3084 fuse_ll_clear_pipe(se);
+
3085 goto pipe_retry;
+
3086 }
+
3087
+
3088 if (err != EINTR && err != EAGAIN)
+
3089 perror("fuse: splice from device");
+
3090 return -err;
+
3091 }
+
3092
+
3093 if (res < sizeof(struct fuse_in_header)) {
+
3094 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3095 return -EIO;
+
3096 }
+
3097
+
3098 tmpbuf = (struct fuse_buf){
+
3099 .size = res,
+
3100 .flags = FUSE_BUF_IS_FD,
+
3101 .fd = llp->pipe[0],
+
3102 };
+
3103
+
3104 /*
+
3105 * Don't bother with zero copy for small requests.
+
3106 * fuse_loop_mt() needs to check for FORGET so this more than
+
3107 * just an optimization.
+
3108 */
+
3109 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
3110 pagesize) {
+
3111 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
3112 struct fuse_bufvec dst = { .count = 1 };
+
3113
+
3114 if (!buf->mem) {
+
3115 buf->mem = buf_alloc(bufsize, internal);
+
3116 if (!buf->mem) {
+
3117 fuse_log(
+
3118 FUSE_LOG_ERR,
+
3119 "fuse: failed to allocate read buffer\n");
+
3120 return -ENOMEM;
+
3121 }
+
3122 buf->mem_size = bufsize;
+
3123 }
+
3124 buf->size = bufsize;
+
3125 buf->flags = 0;
+
3126 dst.buf[0] = *buf;
+
3127
+
3128 res = fuse_buf_copy(&dst, &src, 0);
+
3129 if (res < 0) {
+
3130 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
3131 strerror(-res));
+
3132 fuse_ll_clear_pipe(se);
+
3133 return res;
+
3134 }
+
3135 if (res < tmpbuf.size) {
+
3136 fuse_log(FUSE_LOG_ERR,
+
3137 "fuse: copy from pipe: short read\n");
+
3138 fuse_ll_clear_pipe(se);
+
3139 return -EIO;
+
3140 }
+
3141 assert(res == tmpbuf.size);
+
3142
+
3143 } else {
+
3144 /* Don't overwrite buf->mem, as that would cause a leak */
+
3145 buf->fd = tmpbuf.fd;
+
3146 buf->flags = tmpbuf.flags;
+
3147 }
+
3148 buf->size = tmpbuf.size;
+
3149
+
3150 return res;
+
3151
+
3152fallback:
+
3153#endif
+
3154 bufsize = internal ? buf->mem_size : se->bufsize;
+
3155 if (!buf->mem) {
+
3156 bufsize = se->bufsize; /* might have changed */
+
3157 buf->mem = buf_alloc(bufsize, internal);
+
3158 if (!buf->mem) {
+
3159 fuse_log(FUSE_LOG_ERR,
+
3160 "fuse: failed to allocate read buffer\n");
+
3161 return -ENOMEM;
+
3162 }
+
3163
+
3164 if (internal)
+
3165 buf->mem_size = bufsize;
+
3166 }
+
3167
+
3168restart:
+
3169 if (se->io != NULL) {
+
3170 /* se->io->read is never NULL if se->io is not NULL as
+
3171 specified by fuse_session_custom_io()*/
+
3172 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
3173 se->userdata);
+
3174 } else {
+
3175 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
3176 }
+
3177 err = errno;
+
3178
+
3179 if (fuse_session_exited(se))
+
3180 return 0;
+
3181 if (res == -1) {
+
3182 if (err == EINVAL && internal && se->bufsize > bufsize) {
+
3183 /* FUSE_INIT might have increased the required bufsize */
+
3184 bufsize = se->bufsize;
+
3185 void *newbuf = buf_alloc(bufsize, internal);
+
3186 if (!newbuf) {
+
3187 fuse_log(
+
3188 FUSE_LOG_ERR,
+
3189 "fuse: failed to (re)allocate read buffer\n");
+
3190 return -ENOMEM;
+
3191 }
+
3192 fuse_buf_free(buf);
+
3193 buf->mem = newbuf;
+
3194 buf->mem_size = bufsize;
+
3195 goto restart;
+
3196 }
+
3197
+
3198 /* ENOENT means the operation was interrupted, it's safe
+
3199 to restart */
+
3200 if (err == ENOENT)
+
3201 goto restart;
+
3202
+
3203 if (err == ENODEV) {
+
3204 /* Filesystem was unmounted, or connection was aborted
+
3205 via /sys/fs/fuse/connections */
+ +
3207 return 0;
+
3208 }
+
3209 /* Errors occurring during normal operation: EINTR (read
+
3210 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
3211 umounted) */
+
3212 if (err != EINTR && err != EAGAIN)
+
3213 perror("fuse: reading device");
+
3214 return -err;
+
3215 }
+
3216 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
3217 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
3218 return -EIO;
+
3219 }
+
3220
+
3221 buf->size = res;
+
3222
+
3223 return res;
+
3224}
+
3225
+
3226int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
3227{
+
3228 return _fuse_session_receive_buf(se, buf, NULL, false);
+
3229}
+
3230
+
3231/* libfuse internal handler */
+
3232int fuse_session_receive_buf_internal(struct fuse_session *se,
+
3233 struct fuse_buf *buf,
+
3234 struct fuse_chan *ch)
+
3235{
+
3236 /*
+
3237 * if run internally thread buffers are from libfuse - we can
+
3238 * reallocate them
+
3239 */
+
3240 if (unlikely(!se->got_init) && !se->buf_reallocable)
+
3241 se->buf_reallocable = true;
+
3242
+
3243 return _fuse_session_receive_buf(se, buf, ch, true);
+
3244}
+
3245
+
3246struct fuse_session *
+
3247fuse_session_new_versioned(struct fuse_args *args,
+
3248 const struct fuse_lowlevel_ops *op, size_t op_size,
+
3249 struct libfuse_version *version, void *userdata)
+
3250{
+
3251 int err;
+
3252 struct fuse_session *se;
+
3253 struct mount_opts *mo;
+
3254
+
3255 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
3256 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3257 op_size = sizeof(struct fuse_lowlevel_ops);
+
3258 }
+
3259
+
3260 if (args->argc == 0) {
+
3261 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
3262 return NULL;
+
3263 }
+
3264
+
3265 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
3266 if (se == NULL) {
+
3267 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
3268 goto out1;
+
3269 }
+
3270 se->fd = -1;
+
3271 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
3272 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
3273 se->conn.max_readahead = UINT_MAX;
+
3274
+
3275 /* Parse options */
+
3276 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
3277 goto out2;
+
3278 if(se->deny_others) {
+
3279 /* Allowing access only by root is done by instructing
+
3280 * kernel to allow access by everyone, and then restricting
+
3281 * access to root and mountpoint owner in libfuse.
+
3282 */
+
3283 // We may be adding the option a second time, but
+
3284 // that doesn't hurt.
+
3285 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
3286 goto out2;
+
3287 }
+
3288 mo = parse_mount_opts(args);
+
3289 if (mo == NULL)
+
3290 goto out3;
+
3291
+
3292 if(args->argc == 1 &&
+
3293 args->argv[0][0] == '-') {
+
3294 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
3295 "will be ignored\n");
+
3296 } else if (args->argc != 1) {
+
3297 int i;
+
3298 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
3299 for(i = 1; i < args->argc-1; i++)
+
3300 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
3301 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
3302 goto out4;
+
3303 }
+
3304
+
3305 if (se->debug)
+
3306 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
3307
+
3308 list_init_req(&se->list);
+
3309 list_init_req(&se->interrupts);
+
3310 list_init_nreq(&se->notify_list);
+
3311 se->notify_ctr = 1;
+
3312 pthread_mutex_init(&se->lock, NULL);
+
3313
+
3314 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
3315 if (err) {
+
3316 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
3317 strerror(err));
+
3318 goto out5;
+
3319 }
+
3320
+
3321 memcpy(&se->op, op, op_size);
+
3322 se->owner = getuid();
+
3323 se->userdata = userdata;
+
3324
+
3325 se->mo = mo;
+
3326
+
3327 /* Fuse server application should pass the version it was compiled
+
3328 * against and pass it. If a libfuse version accidentally introduces an
+
3329 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
3330 * by checking the version numbers.
+
3331 */
+
3332 se->version = *version;
+
3333
+
3334 return se;
+
3335
+
3336out5:
+
3337 pthread_mutex_destroy(&se->lock);
+
3338out4:
+
3339 fuse_opt_free_args(args);
+
3340out3:
+
3341 if (mo != NULL)
+
3342 destroy_mount_opts(mo);
+
3343out2:
+
3344 free(se);
+
3345out1:
+
3346 return NULL;
+
3347}
+
3348
+
3349struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3350 const struct fuse_lowlevel_ops *op,
+
3351 size_t op_size, void *userdata);
+
3352struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
3353 const struct fuse_lowlevel_ops *op,
+
3354 size_t op_size,
+
3355 void *userdata)
+
3356{
+
3357 /* unknown version */
+
3358 struct libfuse_version version = { 0 };
+
3359
+
3360 return fuse_session_new_versioned(args, op, op_size, &version,
+
3361 userdata);
+
3362}
+
3363
+
3364FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
3365int fuse_session_custom_io_317(struct fuse_session *se,
+
3366 const struct fuse_custom_io *io, size_t op_size, int fd)
+
3367{
+
3368 if (sizeof(struct fuse_custom_io) < op_size) {
+
3369 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
3370 op_size = sizeof(struct fuse_custom_io);
+
3371 }
+
3372
+
3373 if (fd < 0) {
+
3374 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
3375 "fuse_session_custom_io()\n", fd);
+
3376 return -EBADF;
+
3377 }
+
3378 if (io == NULL) {
+
3379 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
3380 "fuse_session_custom_io()\n");
+
3381 return -EINVAL;
+
3382 } else if (io->read == NULL || io->writev == NULL) {
+
3383 /* If the user provides their own file descriptor, we can't
+
3384 guarantee that the default behavior of the io operations made
+
3385 in libfuse will function properly. Therefore, we enforce the
+
3386 user to implement these io operations when using custom io. */
+
3387 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
3388 "implement both io->read() and io->writev\n");
+
3389 return -EINVAL;
+
3390 }
+
3391
+
3392 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
3393 if (se->io == NULL) {
+
3394 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
3395 "Error: %s\n", strerror(errno));
+
3396 return -errno;
+
3397 }
+
3398
+
3399 se->fd = fd;
+
3400 memcpy(se->io, io, op_size);
+
3401 return 0;
+
3402}
+
3403
+
3404int fuse_session_custom_io_30(struct fuse_session *se,
+
3405 const struct fuse_custom_io *io, int fd);
+
3406FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
3407int fuse_session_custom_io_30(struct fuse_session *se,
+
3408 const struct fuse_custom_io *io, int fd)
+
3409{
+
3410 return fuse_session_custom_io_317(se, io,
+
3411 offsetof(struct fuse_custom_io, clone_fd), fd);
+
3412}
+
3413
+
3414int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
3415{
+
3416 int fd;
+
3417
+
3418 if (mountpoint == NULL) {
+
3419 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
3420 return -1;
+
3421 }
+
3422
+
3423 /*
+
3424 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
3425 * would ensue.
+
3426 */
+
3427 do {
+
3428 fd = open("/dev/null", O_RDWR);
+
3429 if (fd > 2)
+
3430 close(fd);
+
3431 } while (fd >= 0 && fd <= 2);
+
3432
+
3433 /*
+
3434 * To allow FUSE daemons to run without privileges, the caller may open
+
3435 * /dev/fuse before launching the file system and pass on the file
+
3436 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
3437 * parent process takes care of performing the mount in this case.
+
3438 */
+
3439 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
3440 if (fd != -1) {
+
3441 if (fcntl(fd, F_GETFD) == -1) {
+
3442 fuse_log(FUSE_LOG_ERR,
+
3443 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
3444 fd);
+
3445 return -1;
+
3446 }
+
3447 se->fd = fd;
+
3448 return 0;
+
3449 }
+
3450
+
3451 /* Open channel */
+
3452 fd = fuse_kern_mount(mountpoint, se->mo);
+
3453 if (fd == -1)
+
3454 return -1;
+
3455 se->fd = fd;
+
3456
+
3457 /* Save mountpoint */
+
3458 se->mountpoint = strdup(mountpoint);
+
3459 if (se->mountpoint == NULL)
+
3460 goto error_out;
+
3461
+
3462 return 0;
+
3463
+
3464error_out:
+
3465 fuse_kern_unmount(mountpoint, fd);
+
3466 return -1;
+
3467}
+
3468
+
3469int fuse_session_fd(struct fuse_session *se)
+
3470{
+
3471 return se->fd;
+
3472}
+
3473
+
3474void fuse_session_unmount(struct fuse_session *se)
+
3475{
+
3476 if (se->mountpoint != NULL) {
+
3477 fuse_kern_unmount(se->mountpoint, se->fd);
+
3478 se->fd = -1;
+
3479 free(se->mountpoint);
+
3480 se->mountpoint = NULL;
+
3481 }
+
3482}
+
3483
+
3484#ifdef linux
+
3485int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3486{
+
3487 char *buf;
+
3488 size_t bufsize = 1024;
+
3489 char path[128];
+
3490 int ret;
+
3491 int fd;
+
3492 unsigned long pid = req->ctx.pid;
+
3493 char *s;
+
3494
+
3495 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
3496
+
3497retry:
+
3498 buf = malloc(bufsize);
+
3499 if (buf == NULL)
+
3500 return -ENOMEM;
+
3501
+
3502 ret = -EIO;
+
3503 fd = open(path, O_RDONLY);
+
3504 if (fd == -1)
+
3505 goto out_free;
+
3506
+
3507 ret = read(fd, buf, bufsize);
+
3508 close(fd);
+
3509 if (ret < 0) {
+
3510 ret = -EIO;
+
3511 goto out_free;
+
3512 }
+
3513
+
3514 if ((size_t)ret == bufsize) {
+
3515 free(buf);
+
3516 bufsize *= 4;
+
3517 goto retry;
+
3518 }
+
3519
+
3520 buf[ret] = '\0';
+
3521 ret = -EIO;
+
3522 s = strstr(buf, "\nGroups:");
+
3523 if (s == NULL)
+
3524 goto out_free;
+
3525
+
3526 s += 8;
+
3527 ret = 0;
+
3528 while (1) {
+
3529 char *end;
+
3530 unsigned long val = strtoul(s, &end, 0);
+
3531 if (end == s)
+
3532 break;
+
3533
+
3534 s = end;
+
3535 if (ret < size)
+
3536 list[ret] = val;
+
3537 ret++;
+
3538 }
+
3539
+
3540out_free:
+
3541 free(buf);
+
3542 return ret;
+
3543}
+
3544#else /* linux */
+
3545/*
+
3546 * This is currently not implemented on other than Linux...
+
3547 */
+
3548int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
3549{
+
3550 (void) req; (void) size; (void) list;
+
3551 return -ENOSYS;
+
3552}
+
3553#endif
+
3554
+
3555/* Prevent spurious data race warning - we don't care
+
3556 * about races for this flag */
+
3557__attribute__((no_sanitize_thread))
+
3558void fuse_session_exit(struct fuse_session *se)
+
3559{
+
3560 se->exited = 1;
+
3561}
+
3562
+
3563__attribute__((no_sanitize_thread))
+
3564void fuse_session_reset(struct fuse_session *se)
+
3565{
+
3566 se->exited = 0;
+
3567 se->error = 0;
+
3568}
+
3569
+
3570__attribute__((no_sanitize_thread))
+
3571int fuse_session_exited(struct fuse_session *se)
+
3572{
+
3573 return se->exited;
+
3574}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
@ FUSE_BUF_IS_FD
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
struct fuse_req * fuse_req_t
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
void fuse_session_unmount(struct fuse_session *se)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
uint64_t fuse_ino_t
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+ +
void * mem
+
size_t size
+ + +
struct fuse_buf buf[1]
+ + + +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:68
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:97
+
uint32_t nonseekable
Definition fuse_common.h:86
+
int32_t backing_id
+
uint32_t parallel_direct_writes
+
uint32_t noflush
+
uint32_t flush
Definition fuse_common.h:82
+
uint32_t direct_io
Definition fuse_common.h:71
+
uint32_t keep_cache
Definition fuse_common.h:77
+ + + + +
+ + + + diff --git a/doc/html/lib_2fuse__misc_8h_source.html b/doc/html/lib_2fuse__misc_8h_source.html new file mode 100644 index 0000000..42c19de --- /dev/null +++ b/doc/html/lib_2fuse__misc_8h_source.html @@ -0,0 +1,111 @@ + + + + + + + +libfuse: lib/fuse_misc.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + + + diff --git a/doc/html/lib_2fuse__opt_8c_source.html b/doc/html/lib_2fuse__opt_8c_source.html new file mode 100644 index 0000000..80577b3 --- /dev/null +++ b/doc/html/lib_2fuse__opt_8c_source.html @@ -0,0 +1,504 @@ + + + + + + + +libfuse: lib/fuse_opt.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file COPYING.LIB
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
143
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
unsigned long offset
Definition fuse_opt.h:85
+
const char * templ
Definition fuse_opt.h:79
+
int value
Definition fuse_opt.h:91
+
+ + + + diff --git a/doc/html/lib_2fuse__signals_8c_source.html b/doc/html/lib_2fuse__signals_8c_source.html new file mode 100644 index 0000000..188e193 --- /dev/null +++ b/doc/html/lib_2fuse__signals_8c_source.html @@ -0,0 +1,268 @@ + + + + + + + +libfuse: lib/fuse_signals.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
26static int ignore_sigs[] = { SIGPIPE};
+
27static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
28static struct fuse_session *fuse_instance;
+
29
+
30#define BT_STACK_SZ (1024 * 1024)
+
31static void *backtrace_buffer[BT_STACK_SZ];
+
32
+
33static void dump_stack(void)
+
34{
+
35#ifdef HAVE_BACKTRACE
+
36 char **strings;
+
37
+
38 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
39 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
40
+
41 if (strings == NULL) {
+
42 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
43 strerror(errno));
+
44 return;
+
45 }
+
46
+
47 for (int idx = 0; idx < nptrs; idx++)
+
48 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
49
+
50 free(strings);
+
51#endif
+
52}
+
53
+
54static void exit_handler(int sig)
+
55{
+
56 if (fuse_instance == NULL)
+
57 return;
+
58
+
59 fuse_session_exit(fuse_instance);
+
60
+
61 if (sig < 0) {
+
62 fuse_log(FUSE_LOG_ERR,
+
63 "assertion error: signal value <= 0\n");
+
64 dump_stack();
+
65 abort();
+
66 fuse_instance->error = sig;
+
67 }
+
68
+
69 fuse_instance->error = sig;
+
70}
+
71
+
72static void exit_backtrace(int sig)
+
73{
+
74 if (fuse_instance == NULL)
+
75 return;
+
76
+
77 fuse_session_exit(fuse_instance);
+
78
+
79 fuse_remove_signal_handlers(fuse_instance);
+
80 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
81 dump_stack();
+
82 abort();
+
83}
+
84
+
85
+
86static void do_nothing(int sig)
+
87{
+
88 (void) sig;
+
89}
+
90
+
91static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
92{
+
93 struct sigaction sa;
+
94 struct sigaction old_sa;
+
95
+
96 memset(&sa, 0, sizeof(struct sigaction));
+
97 sa.sa_handler = remove ? SIG_DFL : handler;
+
98 sigemptyset(&(sa.sa_mask));
+
99 sa.sa_flags = 0;
+
100
+
101 if (sigaction(sig, NULL, &old_sa) == -1) {
+
102 perror("fuse: cannot get old signal handler");
+
103 return -1;
+
104 }
+
105
+
106 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
107 sigaction(sig, &sa, NULL) == -1) {
+
108 perror("fuse: cannot set signal handler");
+
109 return -1;
+
110 }
+
111 return 0;
+
112}
+
113
+
114static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
115 void (*handler)(int))
+
116{
+
117 for (int idx = 0; idx < nr_signals; idx++) {
+
118 int signal = signals[idx];
+
119
+
120 /*
+
121 * If we used SIG_IGN instead of the do_nothing function,
+
122 * then we would be unable to tell if we set SIG_IGN (and
+
123 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
124 * or if it was already set to SIG_IGN (and should be left
+
125 * untouched.
+
126 */
+
127 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
128 fuse_log(FUSE_LOG_ERR,
+
129 "Failed to install signal handler for sig %d\n",
+
130 signal);
+
131 return -1;
+
132 }
+
133 }
+
134
+
135 return 0;
+
136}
+
137
+
138int fuse_set_signal_handlers(struct fuse_session *se)
+
139{
+
140 size_t nr_signals;
+
141 int rc;
+
142
+
143 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
144 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
145 if (rc < 0)
+
146 return rc;
+
147
+
148 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
149 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
150 if (rc < 0)
+
151 return rc;
+
152
+
153 /*
+
154 * needs to be set independently if already set, as some applications
+
155 * may have multiple sessions and might rely on traditional behavior
+
156 * that the last session is used.
+
157 */
+
158 fuse_instance = se;
+
159
+
160 return 0;
+
161}
+
162
+
163int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
164{
+
165 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
166
+
167 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
168 exit_backtrace);
+
169 if (rc < 0)
+
170 return rc;
+
171
+
172 /* See fuse_set_signal_handlers, why set unconditionally */
+
173 fuse_instance = se;
+
174
+
175 return 0;
+
176}
+
177
+
178static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
179 void (*handler)(int))
+
180{
+
181 for (int idx = 0; idx < nr_signals; idx++)
+
182 set_one_signal_handler(signals[idx], handler, 1);
+
183}
+
184
+
185void fuse_remove_signal_handlers(struct fuse_session *se)
+
186{
+
187 size_t nr_signals;
+
188
+
189 if (fuse_instance != se)
+
190 fuse_log(FUSE_LOG_ERR,
+
191 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
192 else
+
193 fuse_instance = NULL;
+
194
+
195 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
196 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
197
+
198 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
199 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
200
+
201 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
202 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
203}
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_session_exit(struct fuse_session *se)
+
+ + + + diff --git a/doc/html/lib_2helper_8c_source.html b/doc/html/lib_2helper_8c_source.html new file mode 100644 index 0000000..80352f4 --- /dev/null +++ b/doc/html/lib_2helper_8c_source.html @@ -0,0 +1,599 @@ + + + + + + + +libfuse: lib/helper.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file COPYING.LIB.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
130void fuse_cmdline_help(void)
+
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
237int fuse_parse_cmdline_30(struct fuse_args *args,
+
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
252
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 return -1;
+
273 case 0:
+
274 break;
+
275 default:
+
276 (void) read(waiter[0], &completed, sizeof(completed));
+
277 _exit(0);
+
278 }
+
279
+
280 if (setsid() == -1) {
+
281 perror("fuse_daemonize: setsid");
+
282 return -1;
+
283 }
+
284
+
285 (void) chdir("/");
+
286
+
287 nullfd = open("/dev/null", O_RDWR, 0);
+
288 if (nullfd != -1) {
+
289 (void) dup2(nullfd, 0);
+
290 (void) dup2(nullfd, 1);
+
291 (void) dup2(nullfd, 2);
+
292 if (nullfd > 2)
+
293 close(nullfd);
+
294 }
+
295
+
296 /* Propagate completion of daemon initialization */
+
297 completed = 1;
+
298 (void) write(waiter[1], &completed, sizeof(completed));
+
299 close(waiter[0]);
+
300 close(waiter[1]);
+
301 } else {
+
302 (void) chdir("/");
+
303 }
+
304 return 0;
+
305}
+
306
+
307int fuse_main_real_versioned(int argc, char *argv[],
+
308 const struct fuse_operations *op, size_t op_size,
+
309 struct libfuse_version *version, void *user_data)
+
310{
+
311 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
312 struct fuse *fuse;
+
313 struct fuse_cmdline_opts opts;
+
314 int res;
+
315 struct fuse_loop_config *loop_config = NULL;
+
316
+
317 if (fuse_parse_cmdline(&args, &opts) != 0)
+
318 return 1;
+
319
+
320 if (opts.show_version) {
+
321 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
323 res = 0;
+
324 goto out1;
+
325 }
+
326
+
327 if (opts.show_help) {
+
328 if(args.argv[0][0] != '\0')
+
329 printf("usage: %s [options] <mountpoint>\n\n",
+
330 args.argv[0]);
+
331 printf("FUSE options:\n");
+ +
333 fuse_lib_help(&args);
+
334 res = 0;
+
335 goto out1;
+
336 }
+
337
+
338 if (!opts.show_help &&
+
339 !opts.mountpoint) {
+
340 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
341 res = 2;
+
342 goto out1;
+
343 }
+
344
+
345 struct fuse *_fuse_new_31(struct fuse_args *args,
+
346 const struct fuse_operations *op, size_t op_size,
+
347 struct libfuse_version *version,
+
348 void *user_data);
+
349 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
350 if (fuse == NULL) {
+
351 res = 3;
+
352 goto out1;
+
353 }
+
354
+
355 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
356 res = 4;
+
357 goto out2;
+
358 }
+
359
+
360 if (fuse_daemonize(opts.foreground) != 0) {
+
361 res = 5;
+
362 goto out3;
+
363 }
+
364
+
365 struct fuse_session *se = fuse_get_session(fuse);
+
366 if (fuse_set_signal_handlers(se) != 0) {
+
367 res = 6;
+
368 goto out3;
+
369 }
+
370
+
371 if (opts.singlethread)
+
372 res = fuse_loop(fuse);
+
373 else {
+
374 loop_config = fuse_loop_cfg_create();
+
375 if (loop_config == NULL) {
+
376 res = 7;
+
377 goto out3;
+
378 }
+
379
+
380 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
381
+
382 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
383 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
384 res = fuse_loop_mt(fuse, loop_config);
+
385 }
+
386 if (res)
+
387 res = 8;
+
388
+ +
390out3:
+
391 fuse_unmount(fuse);
+
392out2:
+
393 fuse_destroy(fuse);
+
394out1:
+
395 fuse_loop_cfg_destroy(loop_config);
+
396 free(opts.mountpoint);
+
397 fuse_opt_free_args(&args);
+
398 return res;
+
399}
+
400
+
401/* Not symboled, as not part of the official API */
+
402int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
403 size_t op_size, void *user_data);
+
404int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
405 size_t op_size, void *user_data)
+
406{
+
407 struct libfuse_version version = { 0 };
+
408 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
409 user_data);
+
410}
+
411
+
412void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
413 struct fuse_conn_info *conn)
+
414{
+
415 if(opts->set_max_write)
+
416 conn->max_write = opts->max_write;
+
417 if(opts->set_max_background)
+
418 conn->max_background = opts->max_background;
+
419 if(opts->set_congestion_threshold)
+
420 conn->congestion_threshold = opts->congestion_threshold;
+
421 if(opts->set_time_gran)
+
422 conn->time_gran = opts->time_gran;
+
423 if(opts->set_max_readahead)
+
424 conn->max_readahead = opts->max_readahead;
+
425
+
426#define LL_ENABLE(cond,cap) \
+
427 if (cond) conn->want_ext |= (cap)
+
428#define LL_DISABLE(cond,cap) \
+
429 if (cond) conn->want_ext &= ~(cap)
+
430
+
431 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
432 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
433
+
434 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
435 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
436
+
437 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
438 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
439
+
440 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
441 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
442
+
443 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
444 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
445
+
446 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
447 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
448
+
449 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
450 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
451
+
452 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
453 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
454
+
455 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
456 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
457}
+
458
+
459struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
460{
+
461 struct fuse_conn_info_opts *opts;
+
462
+
463 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
464 if(opts == NULL) {
+
465 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
466 return NULL;
+
467 }
+
468 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
469 free(opts);
+
470 return NULL;
+
471 }
+
472 return opts;
+
473}
+
474
+
475int fuse_open_channel(const char *mountpoint, const char* options)
+
476{
+
477 struct mount_opts *opts = NULL;
+
478 int fd = -1;
+
479 const char *argv[] = { "", "-o", options };
+
480 int argc = sizeof(argv) / sizeof(argv[0]);
+
481 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
482
+
483 opts = parse_mount_opts(&args);
+
484 if (opts == NULL)
+
485 return -1;
+
486
+
487 fd = fuse_kern_mount(mountpoint, opts);
+
488 destroy_mount_opts(opts);
+
489
+
490 return fd;
+
491}
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5220
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5169
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:307
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4602
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4769
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:475
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4545
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5225
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:459
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:412
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + + + diff --git a/doc/html/lib_2modules_2iconv_8c_source.html b/doc/html/lib_2modules_2iconv_8c_source.html new file mode 100644 index 0000000..526975f --- /dev/null +++ b/doc/html/lib_2modules_2iconv_8c_source.html @@ -0,0 +1,816 @@ + + + + + + + +libfuse: lib/modules/iconv.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571static void *iconv_init(struct fuse_conn_info *conn,
+
572 struct fuse_config *cfg)
+
573{
+
574 struct iconv *ic = iconv_get();
+
575 fuse_fs_init(ic->next, conn, cfg);
+
576 /* Don't touch cfg->nullpath_ok, we can work with
+
577 either */
+
578 return ic;
+
579}
+
580
+
581static void iconv_destroy(void *data)
+
582{
+
583 struct iconv *ic = data;
+
584 fuse_fs_destroy(ic->next);
+
585 iconv_close(ic->tofs);
+
586 iconv_close(ic->fromfs);
+
587 pthread_mutex_destroy(&ic->lock);
+
588 free(ic->from_code);
+
589 free(ic->to_code);
+
590 free(ic);
+
591}
+
592
+
593static const struct fuse_operations iconv_oper = {
+
594 .destroy = iconv_destroy,
+
595 .init = iconv_init,
+
596 .getattr = iconv_getattr,
+
597 .access = iconv_access,
+
598 .readlink = iconv_readlink,
+
599 .opendir = iconv_opendir,
+
600 .readdir = iconv_readdir,
+
601 .releasedir = iconv_releasedir,
+
602 .mknod = iconv_mknod,
+
603 .mkdir = iconv_mkdir,
+
604 .symlink = iconv_symlink,
+
605 .unlink = iconv_unlink,
+
606 .rmdir = iconv_rmdir,
+
607 .rename = iconv_rename,
+
608 .link = iconv_link,
+
609 .chmod = iconv_chmod,
+
610 .chown = iconv_chown,
+
611 .truncate = iconv_truncate,
+
612 .utimens = iconv_utimens,
+
613 .create = iconv_create,
+
614 .open = iconv_open_file,
+
615 .read_buf = iconv_read_buf,
+
616 .write_buf = iconv_write_buf,
+
617 .statfs = iconv_statfs,
+
618 .flush = iconv_flush,
+
619 .release = iconv_release,
+
620 .fsync = iconv_fsync,
+
621 .fsyncdir = iconv_fsyncdir,
+
622 .setxattr = iconv_setxattr,
+
623 .getxattr = iconv_getxattr,
+
624 .listxattr = iconv_listxattr,
+
625 .removexattr = iconv_removexattr,
+
626 .lock = iconv_lock,
+
627 .flock = iconv_flock,
+
628 .bmap = iconv_bmap,
+
629 .lseek = iconv_lseek,
+
630};
+
631
+
632static const struct fuse_opt iconv_opts[] = {
+
633 FUSE_OPT_KEY("-h", 0),
+
634 FUSE_OPT_KEY("--help", 0),
+
635 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
636 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
638};
+
639
+
640static void iconv_help(void)
+
641{
+
642 char *charmap;
+
643 const char *old = setlocale(LC_CTYPE, "");
+
644
+
645 charmap = strdup(nl_langinfo(CODESET));
+
646 if (old)
+
647 setlocale(LC_CTYPE, old);
+
648 else
+
649 perror("setlocale");
+
650
+
651 printf(
+
652" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
653" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
654 charmap);
+
655 free(charmap);
+
656}
+
657
+
658static int iconv_opt_proc(void *data, const char *arg, int key,
+
659 struct fuse_args *outargs)
+
660{
+
661 (void) data; (void) arg; (void) outargs;
+
662
+
663 if (!key) {
+
664 iconv_help();
+
665 return -1;
+
666 }
+
667
+
668 return 1;
+
669}
+
670
+
671static struct fuse_fs *iconv_new(struct fuse_args *args,
+
672 struct fuse_fs *next[])
+
673{
+
674 struct fuse_fs *fs;
+
675 struct iconv *ic;
+
676 const char *old = NULL;
+
677 const char *from;
+
678 const char *to;
+
679
+
680 ic = calloc(1, sizeof(struct iconv));
+
681 if (ic == NULL) {
+
682 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
683 return NULL;
+
684 }
+
685
+
686 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
687 goto out_free;
+
688
+
689 if (!next[0] || next[1]) {
+
690 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
691 goto out_free;
+
692 }
+
693
+
694 from = ic->from_code ? ic->from_code : "UTF-8";
+
695 to = ic->to_code ? ic->to_code : "";
+
696 /* FIXME: detect charset equivalence? */
+
697 if (!to[0])
+
698 old = setlocale(LC_CTYPE, "");
+
699 ic->tofs = iconv_open(from, to);
+
700 if (ic->tofs == (iconv_t) -1) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
702 to, from);
+
703 goto out_free;
+
704 }
+
705 ic->fromfs = iconv_open(to, from);
+
706 if (ic->tofs == (iconv_t) -1) {
+
707 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
708 from, to);
+
709 goto out_iconv_close_to;
+
710 }
+
711 if (old) {
+
712 setlocale(LC_CTYPE, old);
+
713 old = NULL;
+
714 }
+
715
+
716 ic->next = next[0];
+
717 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
718 if (!fs)
+
719 goto out_iconv_close_from;
+
720
+
721 return fs;
+
722
+
723out_iconv_close_from:
+
724 iconv_close(ic->fromfs);
+
725out_iconv_close_to:
+
726 iconv_close(ic->tofs);
+
727out_free:
+
728 free(ic->from_code);
+
729 free(ic->to_code);
+
730 free(ic);
+
731 if (old) {
+
732 setlocale(LC_CTYPE, old);
+
733 }
+
734 return NULL;
+
735}
+
736
+
737FUSE_REGISTER_MODULE(iconv, iconv_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
fuse_fill_dir_flags
Definition fuse.h:58
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1398
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/lib_2modules_2subdir_8c_source.html b/doc/html/lib_2modules_2subdir_8c_source.html new file mode 100644 index 0000000..aa02465 --- /dev/null +++ b/doc/html/lib_2modules_2subdir_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: lib/modules/subdir.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
subdir.c
+
+
+
1/*
+
2 fuse subdir module: offset paths with a base directory
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17
+
18struct subdir {
+
19 char *base;
+
20 size_t baselen;
+
21 int rellinks;
+
22 struct fuse_fs *next;
+
23};
+
24
+
25static struct subdir *subdir_get(void)
+
26{
+ +
28}
+
29
+
30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+
31{
+
32 char *newpath = NULL;
+
33
+
34 if (path != NULL) {
+
35 unsigned newlen = d->baselen + strlen(path);
+
36
+
37 newpath = malloc(newlen + 2);
+
38 if (!newpath)
+
39 return -ENOMEM;
+
40
+
41 if (path[0] == '/')
+
42 path++;
+
43 strcpy(newpath, d->base);
+
44 strcpy(newpath + d->baselen, path);
+
45 if (!newpath[0])
+
46 strcpy(newpath, ".");
+
47 }
+
48 *newpathp = newpath;
+
49
+
50 return 0;
+
51}
+
52
+
53static int subdir_getattr(const char *path, struct stat *stbuf,
+
54 struct fuse_file_info *fi)
+
55{
+
56 struct subdir *d = subdir_get();
+
57 char *newpath;
+
58 int err = subdir_addpath(d, path, &newpath);
+
59 if (!err) {
+
60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+
61 free(newpath);
+
62 }
+
63 return err;
+
64}
+
65
+
66static int subdir_access(const char *path, int mask)
+
67{
+
68 struct subdir *d = subdir_get();
+
69 char *newpath;
+
70 int err = subdir_addpath(d, path, &newpath);
+
71 if (!err) {
+
72 err = fuse_fs_access(d->next, newpath, mask);
+
73 free(newpath);
+
74 }
+
75 return err;
+
76}
+
77
+
78
+
79static int count_components(const char *p)
+
80{
+
81 int ctr;
+
82
+
83 for (; *p == '/'; p++);
+
84 for (ctr = 0; *p; ctr++) {
+
85 for (; *p && *p != '/'; p++);
+
86 for (; *p == '/'; p++);
+
87 }
+
88 return ctr;
+
89}
+
90
+
91static void strip_common(const char **sp, const char **tp)
+
92{
+
93 const char *s = *sp;
+
94 const char *t = *tp;
+
95 do {
+
96 for (; *s == '/'; s++);
+
97 for (; *t == '/'; t++);
+
98 *tp = t;
+
99 *sp = s;
+
100 for (; *s == *t && *s && *s != '/'; s++, t++);
+
101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+
102}
+
103
+
104static void transform_symlink(struct subdir *d, const char *path,
+
105 char *buf, size_t size)
+
106{
+
107 const char *l = buf;
+
108 size_t llen;
+
109 char *s;
+
110 int dotdots;
+
111 int i;
+
112
+
113 if (l[0] != '/' || d->base[0] != '/')
+
114 return;
+
115
+
116 strip_common(&l, &path);
+
117 if (l - buf < (long) d->baselen)
+
118 return;
+
119
+
120 dotdots = count_components(path);
+
121 if (!dotdots)
+
122 return;
+
123 dotdots--;
+
124
+
125 llen = strlen(l);
+
126 if (dotdots * 3 + llen + 2 > size)
+
127 return;
+
128
+
129 s = buf + dotdots * 3;
+
130 if (llen)
+
131 memmove(s, l, llen + 1);
+
132 else if (!dotdots)
+
133 strcpy(s, ".");
+
134 else
+
135 *s = '\0';
+
136
+
137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
+
138 memcpy(s, "../", 3);
+
139}
+
140
+
141
+
142static int subdir_readlink(const char *path, char *buf, size_t size)
+
143{
+
144 struct subdir *d = subdir_get();
+
145 char *newpath;
+
146 int err = subdir_addpath(d, path, &newpath);
+
147 if (!err) {
+
148 err = fuse_fs_readlink(d->next, newpath, buf, size);
+
149 if (!err && d->rellinks)
+
150 transform_symlink(d, newpath, buf, size);
+
151 free(newpath);
+
152 }
+
153 return err;
+
154}
+
155
+
156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+
157{
+
158 struct subdir *d = subdir_get();
+
159 char *newpath;
+
160 int err = subdir_addpath(d, path, &newpath);
+
161 if (!err) {
+
162 err = fuse_fs_opendir(d->next, newpath, fi);
+
163 free(newpath);
+
164 }
+
165 return err;
+
166}
+
167
+
168static int subdir_readdir(const char *path, void *buf,
+
169 fuse_fill_dir_t filler, off_t offset,
+
170 struct fuse_file_info *fi,
+
171 enum fuse_readdir_flags flags)
+
172{
+
173 struct subdir *d = subdir_get();
+
174 char *newpath;
+
175 int err = subdir_addpath(d, path, &newpath);
+
176 if (!err) {
+
177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+
178 fi, flags);
+
179 free(newpath);
+
180 }
+
181 return err;
+
182}
+
183
+
184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+
185{
+
186 struct subdir *d = subdir_get();
+
187 char *newpath;
+
188 int err = subdir_addpath(d, path, &newpath);
+
189 if (!err) {
+
190 err = fuse_fs_releasedir(d->next, newpath, fi);
+
191 free(newpath);
+
192 }
+
193 return err;
+
194}
+
195
+
196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+
197{
+
198 struct subdir *d = subdir_get();
+
199 char *newpath;
+
200 int err = subdir_addpath(d, path, &newpath);
+
201 if (!err) {
+
202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+
203 free(newpath);
+
204 }
+
205 return err;
+
206}
+
207
+
208static int subdir_mkdir(const char *path, mode_t mode)
+
209{
+
210 struct subdir *d = subdir_get();
+
211 char *newpath;
+
212 int err = subdir_addpath(d, path, &newpath);
+
213 if (!err) {
+
214 err = fuse_fs_mkdir(d->next, newpath, mode);
+
215 free(newpath);
+
216 }
+
217 return err;
+
218}
+
219
+
220static int subdir_unlink(const char *path)
+
221{
+
222 struct subdir *d = subdir_get();
+
223 char *newpath;
+
224 int err = subdir_addpath(d, path, &newpath);
+
225 if (!err) {
+
226 err = fuse_fs_unlink(d->next, newpath);
+
227 free(newpath);
+
228 }
+
229 return err;
+
230}
+
231
+
232static int subdir_rmdir(const char *path)
+
233{
+
234 struct subdir *d = subdir_get();
+
235 char *newpath;
+
236 int err = subdir_addpath(d, path, &newpath);
+
237 if (!err) {
+
238 err = fuse_fs_rmdir(d->next, newpath);
+
239 free(newpath);
+
240 }
+
241 return err;
+
242}
+
243
+
244static int subdir_symlink(const char *from, const char *path)
+
245{
+
246 struct subdir *d = subdir_get();
+
247 char *newpath;
+
248 int err = subdir_addpath(d, path, &newpath);
+
249 if (!err) {
+
250 err = fuse_fs_symlink(d->next, from, newpath);
+
251 free(newpath);
+
252 }
+
253 return err;
+
254}
+
255
+
256static int subdir_rename(const char *from, const char *to, unsigned int flags)
+
257{
+
258 struct subdir *d = subdir_get();
+
259 char *newfrom;
+
260 char *newto;
+
261 int err = subdir_addpath(d, from, &newfrom);
+
262 if (!err) {
+
263 err = subdir_addpath(d, to, &newto);
+
264 if (!err) {
+
265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
+
266 free(newto);
+
267 }
+
268 free(newfrom);
+
269 }
+
270 return err;
+
271}
+
272
+
273static int subdir_link(const char *from, const char *to)
+
274{
+
275 struct subdir *d = subdir_get();
+
276 char *newfrom;
+
277 char *newto;
+
278 int err = subdir_addpath(d, from, &newfrom);
+
279 if (!err) {
+
280 err = subdir_addpath(d, to, &newto);
+
281 if (!err) {
+
282 err = fuse_fs_link(d->next, newfrom, newto);
+
283 free(newto);
+
284 }
+
285 free(newfrom);
+
286 }
+
287 return err;
+
288}
+
289
+
290static int subdir_chmod(const char *path, mode_t mode,
+
291 struct fuse_file_info *fi)
+
292{
+
293 struct subdir *d = subdir_get();
+
294 char *newpath;
+
295 int err = subdir_addpath(d, path, &newpath);
+
296 if (!err) {
+
297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
+
298 free(newpath);
+
299 }
+
300 return err;
+
301}
+
302
+
303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+
304 struct fuse_file_info *fi)
+
305{
+
306 struct subdir *d = subdir_get();
+
307 char *newpath;
+
308 int err = subdir_addpath(d, path, &newpath);
+
309 if (!err) {
+
310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+
311 free(newpath);
+
312 }
+
313 return err;
+
314}
+
315
+
316static int subdir_truncate(const char *path, off_t size,
+
317 struct fuse_file_info *fi)
+
318{
+
319 struct subdir *d = subdir_get();
+
320 char *newpath;
+
321 int err = subdir_addpath(d, path, &newpath);
+
322 if (!err) {
+
323 err = fuse_fs_truncate(d->next, newpath, size, fi);
+
324 free(newpath);
+
325 }
+
326 return err;
+
327}
+
328
+
329static int subdir_utimens(const char *path, const struct timespec ts[2],
+
330 struct fuse_file_info *fi)
+
331{
+
332 struct subdir *d = subdir_get();
+
333 char *newpath;
+
334 int err = subdir_addpath(d, path, &newpath);
+
335 if (!err) {
+
336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
+
337 free(newpath);
+
338 }
+
339 return err;
+
340}
+
341
+
342static int subdir_create(const char *path, mode_t mode,
+
343 struct fuse_file_info *fi)
+
344{
+
345 struct subdir *d = subdir_get();
+
346 char *newpath;
+
347 int err = subdir_addpath(d, path, &newpath);
+
348 if (!err) {
+
349 err = fuse_fs_create(d->next, newpath, mode, fi);
+
350 free(newpath);
+
351 }
+
352 return err;
+
353}
+
354
+
355static int subdir_open(const char *path, struct fuse_file_info *fi)
+
356{
+
357 struct subdir *d = subdir_get();
+
358 char *newpath;
+
359 int err = subdir_addpath(d, path, &newpath);
+
360 if (!err) {
+
361 err = fuse_fs_open(d->next, newpath, fi);
+
362 free(newpath);
+
363 }
+
364 return err;
+
365}
+
366
+
367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+
368 size_t size, off_t offset, struct fuse_file_info *fi)
+
369{
+
370 struct subdir *d = subdir_get();
+
371 char *newpath;
+
372 int err = subdir_addpath(d, path, &newpath);
+
373 if (!err) {
+
374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+
375 free(newpath);
+
376 }
+
377 return err;
+
378}
+
379
+
380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+
381 off_t offset, struct fuse_file_info *fi)
+
382{
+
383 struct subdir *d = subdir_get();
+
384 char *newpath;
+
385 int err = subdir_addpath(d, path, &newpath);
+
386 if (!err) {
+
387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+
388 free(newpath);
+
389 }
+
390 return err;
+
391}
+
392
+
393static int subdir_statfs(const char *path, struct statvfs *stbuf)
+
394{
+
395 struct subdir *d = subdir_get();
+
396 char *newpath;
+
397 int err = subdir_addpath(d, path, &newpath);
+
398 if (!err) {
+
399 err = fuse_fs_statfs(d->next, newpath, stbuf);
+
400 free(newpath);
+
401 }
+
402 return err;
+
403}
+
404
+
405static int subdir_flush(const char *path, struct fuse_file_info *fi)
+
406{
+
407 struct subdir *d = subdir_get();
+
408 char *newpath;
+
409 int err = subdir_addpath(d, path, &newpath);
+
410 if (!err) {
+
411 err = fuse_fs_flush(d->next, newpath, fi);
+
412 free(newpath);
+
413 }
+
414 return err;
+
415}
+
416
+
417static int subdir_release(const char *path, struct fuse_file_info *fi)
+
418{
+
419 struct subdir *d = subdir_get();
+
420 char *newpath;
+
421 int err = subdir_addpath(d, path, &newpath);
+
422 if (!err) {
+
423 err = fuse_fs_release(d->next, newpath, fi);
+
424 free(newpath);
+
425 }
+
426 return err;
+
427}
+
428
+
429static int subdir_fsync(const char *path, int isdatasync,
+
430 struct fuse_file_info *fi)
+
431{
+
432 struct subdir *d = subdir_get();
+
433 char *newpath;
+
434 int err = subdir_addpath(d, path, &newpath);
+
435 if (!err) {
+
436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+
437 free(newpath);
+
438 }
+
439 return err;
+
440}
+
441
+
442static int subdir_fsyncdir(const char *path, int isdatasync,
+
443 struct fuse_file_info *fi)
+
444{
+
445 struct subdir *d = subdir_get();
+
446 char *newpath;
+
447 int err = subdir_addpath(d, path, &newpath);
+
448 if (!err) {
+
449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+
450 free(newpath);
+
451 }
+
452 return err;
+
453}
+
454
+
455static int subdir_setxattr(const char *path, const char *name,
+
456 const char *value, size_t size, int flags)
+
457{
+
458 struct subdir *d = subdir_get();
+
459 char *newpath;
+
460 int err = subdir_addpath(d, path, &newpath);
+
461 if (!err) {
+
462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+
463 flags);
+
464 free(newpath);
+
465 }
+
466 return err;
+
467}
+
468
+
469static int subdir_getxattr(const char *path, const char *name, char *value,
+
470 size_t size)
+
471{
+
472 struct subdir *d = subdir_get();
+
473 char *newpath;
+
474 int err = subdir_addpath(d, path, &newpath);
+
475 if (!err) {
+
476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+
477 free(newpath);
+
478 }
+
479 return err;
+
480}
+
481
+
482static int subdir_listxattr(const char *path, char *list, size_t size)
+
483{
+
484 struct subdir *d = subdir_get();
+
485 char *newpath;
+
486 int err = subdir_addpath(d, path, &newpath);
+
487 if (!err) {
+
488 err = fuse_fs_listxattr(d->next, newpath, list, size);
+
489 free(newpath);
+
490 }
+
491 return err;
+
492}
+
493
+
494static int subdir_removexattr(const char *path, const char *name)
+
495{
+
496 struct subdir *d = subdir_get();
+
497 char *newpath;
+
498 int err = subdir_addpath(d, path, &newpath);
+
499 if (!err) {
+
500 err = fuse_fs_removexattr(d->next, newpath, name);
+
501 free(newpath);
+
502 }
+
503 return err;
+
504}
+
505
+
506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
507 struct flock *lock)
+
508{
+
509 struct subdir *d = subdir_get();
+
510 char *newpath;
+
511 int err = subdir_addpath(d, path, &newpath);
+
512 if (!err) {
+
513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+
514 free(newpath);
+
515 }
+
516 return err;
+
517}
+
518
+
519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+
520{
+
521 struct subdir *d = subdir_get();
+
522 char *newpath;
+
523 int err = subdir_addpath(d, path, &newpath);
+
524 if (!err) {
+
525 err = fuse_fs_flock(d->next, newpath, fi, op);
+
526 free(newpath);
+
527 }
+
528 return err;
+
529}
+
530
+
531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
532{
+
533 struct subdir *d = subdir_get();
+
534 char *newpath;
+
535 int err = subdir_addpath(d, path, &newpath);
+
536 if (!err) {
+
537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+
538 free(newpath);
+
539 }
+
540 return err;
+
541}
+
542
+
543static off_t subdir_lseek(const char *path, off_t off, int whence,
+
544 struct fuse_file_info *fi)
+
545{
+
546 struct subdir *ic = subdir_get();
+
547 char *newpath;
+
548 int res = subdir_addpath(ic, path, &newpath);
+
549 if (!res) {
+
550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
551 free(newpath);
+
552 }
+
553 return res;
+
554}
+
555
+
556static void *subdir_init(struct fuse_conn_info *conn,
+
557 struct fuse_config *cfg)
+
558{
+
559 struct subdir *d = subdir_get();
+
560 fuse_fs_init(d->next, conn, cfg);
+
561 /* Don't touch cfg->nullpath_ok, we can work with
+
562 either */
+
563 return d;
+
564}
+
565
+
566static void subdir_destroy(void *data)
+
567{
+
568 struct subdir *d = data;
+
569 fuse_fs_destroy(d->next);
+
570 free(d->base);
+
571 free(d);
+
572}
+
573
+
574static const struct fuse_operations subdir_oper = {
+
575 .destroy = subdir_destroy,
+
576 .init = subdir_init,
+
577 .getattr = subdir_getattr,
+
578 .access = subdir_access,
+
579 .readlink = subdir_readlink,
+
580 .opendir = subdir_opendir,
+
581 .readdir = subdir_readdir,
+
582 .releasedir = subdir_releasedir,
+
583 .mknod = subdir_mknod,
+
584 .mkdir = subdir_mkdir,
+
585 .symlink = subdir_symlink,
+
586 .unlink = subdir_unlink,
+
587 .rmdir = subdir_rmdir,
+
588 .rename = subdir_rename,
+
589 .link = subdir_link,
+
590 .chmod = subdir_chmod,
+
591 .chown = subdir_chown,
+
592 .truncate = subdir_truncate,
+
593 .utimens = subdir_utimens,
+
594 .create = subdir_create,
+
595 .open = subdir_open,
+
596 .read_buf = subdir_read_buf,
+
597 .write_buf = subdir_write_buf,
+
598 .statfs = subdir_statfs,
+
599 .flush = subdir_flush,
+
600 .release = subdir_release,
+
601 .fsync = subdir_fsync,
+
602 .fsyncdir = subdir_fsyncdir,
+
603 .setxattr = subdir_setxattr,
+
604 .getxattr = subdir_getxattr,
+
605 .listxattr = subdir_listxattr,
+
606 .removexattr = subdir_removexattr,
+
607 .lock = subdir_lock,
+
608 .flock = subdir_flock,
+
609 .bmap = subdir_bmap,
+
610 .lseek = subdir_lseek,
+
611};
+
612
+
613static const struct fuse_opt subdir_opts[] = {
+
614 FUSE_OPT_KEY("-h", 0),
+
615 FUSE_OPT_KEY("--help", 0),
+
616 { "subdir=%s", offsetof(struct subdir, base), 0 },
+
617 { "rellinks", offsetof(struct subdir, rellinks), 1 },
+
618 { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ +
620};
+
621
+
622static void subdir_help(void)
+
623{
+
624 printf(
+
625" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+
626" -o [no]rellinks transform absolute symlinks to relative\n");
+
627}
+
628
+
629static int subdir_opt_proc(void *data, const char *arg, int key,
+
630 struct fuse_args *outargs)
+
631{
+
632 (void) data; (void) arg; (void) outargs;
+
633
+
634 if (!key) {
+
635 subdir_help();
+
636 return -1;
+
637 }
+
638
+
639 return 1;
+
640}
+
641
+
642static struct fuse_fs *subdir_new(struct fuse_args *args,
+
643 struct fuse_fs *next[])
+
644{
+
645 struct fuse_fs *fs;
+
646 struct subdir *d;
+
647
+
648 d = calloc(1, sizeof(struct subdir));
+
649 if (d == NULL) {
+
650 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
651 return NULL;
+
652 }
+
653
+
654 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+
655 goto out_free;
+
656
+
657 if (!next[0] || next[1]) {
+
658 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
+
659 goto out_free;
+
660 }
+
661
+
662 if (!d->base) {
+
663 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
+
664 goto out_free;
+
665 }
+
666
+
667 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+
668 char *tmp = realloc(d->base, strlen(d->base) + 2);
+
669 if (!tmp) {
+
670 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
+
671 goto out_free;
+
672 }
+
673 d->base = tmp;
+
674 strcat(d->base, "/");
+
675 }
+
676 d->baselen = strlen(d->base);
+
677 d->next = next[0];
+
678 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+
679 if (!fs)
+
680 goto out_free;
+
681 return fs;
+
682
+
683out_free:
+
684 free(d->base);
+
685 free(d);
+
686 return NULL;
+
687}
+
688
+
689FUSE_REGISTER_MODULE(subdir, subdir_new);
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4669
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:87
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4879
+
fuse_readdir_flags
Definition fuse.h:42
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1398
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:874
+ + +
void(* destroy)(void *private_data)
Definition fuse.h:649
+ +
+ + + + diff --git a/doc/html/lib_2mount_8c_source.html b/doc/html/lib_2mount_8c_source.html new file mode 100644 index 0000000..44469b4 --- /dev/null +++ b/doc/html/lib_2mount_8c_source.html @@ -0,0 +1,792 @@ + + + + + + + +libfuse: lib/mount.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture specific file system mounting (Linux).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11/* For environ */
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_misc.h"
+
17#include "fuse_opt.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <unistd.h>
+
23#include <stddef.h>
+
24#include <string.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <poll.h>
+
28#include <spawn.h>
+
29#include <sys/socket.h>
+
30#include <sys/un.h>
+
31#include <sys/wait.h>
+
32
+
33#include "fuse_mount_compat.h"
+
34
+
35#ifdef __NetBSD__
+
36#include <perfuse.h>
+
37
+
38#define MS_RDONLY MNT_RDONLY
+
39#define MS_NOSUID MNT_NOSUID
+
40#define MS_NODEV MNT_NODEV
+
41#define MS_NOEXEC MNT_NOEXEC
+
42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+
43#define MS_NOATIME MNT_NOATIME
+
44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
+
45
+
46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+
47#endif
+
48
+
49#define FUSERMOUNT_PROG "fusermount3"
+
50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
+
52
+
53#ifndef MS_DIRSYNC
+
54#define MS_DIRSYNC 128
+
55#endif
+
56
+
57enum {
+
58 KEY_KERN_FLAG,
+
59 KEY_KERN_OPT,
+
60 KEY_FUSERMOUNT_OPT,
+
61 KEY_SUBTYPE_OPT,
+
62 KEY_MTAB_OPT,
+
63 KEY_ALLOW_OTHER,
+
64 KEY_RO,
+
65};
+
66
+
67struct mount_opts {
+
68 int allow_other;
+
69 int flags;
+
70 int auto_unmount;
+
71 int blkdev;
+
72 char *fsname;
+
73 char *subtype;
+
74 char *subtype_opt;
+
75 char *mtab_opts;
+
76 char *fusermount_opts;
+
77 char *kernel_opts;
+
78 unsigned max_read;
+
79};
+
80
+
81#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
82
+
83static const struct fuse_opt fuse_mount_opts[] = {
+
84 FUSE_MOUNT_OPT("allow_other", allow_other),
+
85 FUSE_MOUNT_OPT("blkdev", blkdev),
+
86 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+
87 FUSE_MOUNT_OPT("fsname=%s", fsname),
+
88 FUSE_MOUNT_OPT("max_read=%u", max_read),
+
89 FUSE_MOUNT_OPT("subtype=%s", subtype),
+
90 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+
91 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+
92 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+
93 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+
94 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+
95 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+
96 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+
97 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+
98 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+
99 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+
100 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+
101 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+
102 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+
103 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
+
104 FUSE_OPT_KEY("-r", KEY_RO),
+
105 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+
106 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+
107 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+
108 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+
109 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+
110 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+
111 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+
112 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+
113 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+
114 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+
115 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+
116 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+
117 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
+
118 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
+
119 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
+
120 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
+ +
122};
+
123
+
124/*
+
125 * Running fusermount by calling 'posix_spawn'
+
126 *
+
127 * @param out_pid might be NULL
+
128 */
+
129static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
+
130 char const * const argv[], pid_t *out_pid)
+
131{
+
132 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
+
133 pid_t pid;
+
134
+
135 /* See man 7 environ for the global environ pointer */
+
136
+
137 /* first try the install path */
+
138 int status = posix_spawn(&pid, full_path, action, NULL,
+
139 (char * const *) argv, environ);
+
140 if (status != 0) {
+
141 /* if that fails, try a system install */
+
142 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
+
143 (char * const *) argv, environ);
+
144 }
+
145
+
146 if (status != 0) {
+
147 fuse_log(FUSE_LOG_ERR,
+
148 "On calling fusermount posix_spawn failed: %s\n",
+
149 strerror(status));
+
150 return -status;
+
151 }
+
152
+
153 if (out_pid)
+
154 *out_pid = pid;
+
155 else
+
156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
+
157
+
158 return 0;
+
159}
+
160
+
161void fuse_mount_version(void)
+
162{
+
163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
+
164 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
165
+
166 if(status != 0)
+
167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
+
168 FUSERMOUNT_PROG);
+
169}
+
170
+
171struct mount_flags {
+
172 const char *opt;
+
173 unsigned long flag;
+
174 int on;
+
175};
+
176
+
177static const struct mount_flags mount_flags[] = {
+
178 {"rw", MS_RDONLY, 0},
+
179 {"ro", MS_RDONLY, 1},
+
180 {"suid", MS_NOSUID, 0},
+
181 {"nosuid", MS_NOSUID, 1},
+
182 {"dev", MS_NODEV, 0},
+
183 {"nodev", MS_NODEV, 1},
+
184 {"exec", MS_NOEXEC, 0},
+
185 {"noexec", MS_NOEXEC, 1},
+
186 {"async", MS_SYNCHRONOUS, 0},
+
187 {"sync", MS_SYNCHRONOUS, 1},
+
188 {"noatime", MS_NOATIME, 1},
+
189 {"nodiratime", MS_NODIRATIME, 1},
+
190 {"norelatime", MS_RELATIME, 0},
+
191 {"nostrictatime", MS_STRICTATIME, 0},
+
192 {"symfollow", MS_NOSYMFOLLOW, 0},
+
193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
+
194#ifndef __NetBSD__
+
195 {"dirsync", MS_DIRSYNC, 1},
+
196#endif
+
197 {NULL, 0, 0}
+
198};
+
199
+
200unsigned get_max_read(struct mount_opts *o)
+
201{
+
202 return o->max_read;
+
203}
+
204
+
205static void set_mount_flag(const char *s, int *flags)
+
206{
+
207 int i;
+
208
+
209 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
210 const char *opt = mount_flags[i].opt;
+
211 if (strcmp(opt, s) == 0) {
+
212 if (mount_flags[i].on)
+
213 *flags |= mount_flags[i].flag;
+
214 else
+
215 *flags &= ~mount_flags[i].flag;
+
216 return;
+
217 }
+
218 }
+
219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
+
220 abort();
+
221}
+
222
+
223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
224 struct fuse_args *outargs)
+
225{
+
226 (void) outargs;
+
227 struct mount_opts *mo = data;
+
228
+
229 switch (key) {
+
230 case KEY_RO:
+
231 arg = "ro";
+
232 /* fall through */
+
233 case KEY_KERN_FLAG:
+
234 set_mount_flag(arg, &mo->flags);
+
235 return 0;
+
236
+
237 case KEY_KERN_OPT:
+
238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
239
+
240 case KEY_FUSERMOUNT_OPT:
+
241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
242
+
243 case KEY_SUBTYPE_OPT:
+
244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
245
+
246 case KEY_MTAB_OPT:
+
247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
248
+
249 /* Third party options like 'x-gvfs-notrash' */
+
250 case FUSE_OPT_KEY_OPT:
+
251 return (strncmp("x-", arg, 2) == 0) ?
+
252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
+
253 1;
+
254 }
+
255
+
256 /* Pass through unknown options */
+
257 return 1;
+
258}
+
259
+
260/* return value:
+
261 * >= 0 => fd
+
262 * -1 => error
+
263 */
+
264static int receive_fd(int fd)
+
265{
+
266 struct msghdr msg;
+
267 struct iovec iov;
+
268 char buf[1];
+
269 int rv;
+
270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+
271 struct cmsghdr *cmsg;
+
272
+
273 iov.iov_base = buf;
+
274 iov.iov_len = 1;
+
275
+
276 memset(&msg, 0, sizeof(msg));
+
277 msg.msg_name = 0;
+
278 msg.msg_namelen = 0;
+
279 msg.msg_iov = &iov;
+
280 msg.msg_iovlen = 1;
+
281 /* old BSD implementations should use msg_accrights instead of
+
282 * msg_control; the interface is different. */
+
283 msg.msg_control = ccmsg;
+
284 msg.msg_controllen = sizeof(ccmsg);
+
285
+
286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+
287 if (rv == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
+
289 return -1;
+
290 }
+
291 if(!rv) {
+
292 /* EOF */
+
293 return -1;
+
294 }
+
295
+
296 cmsg = CMSG_FIRSTHDR(&msg);
+
297 if (cmsg->cmsg_type != SCM_RIGHTS) {
+
298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
+
299 cmsg->cmsg_type);
+
300 return -1;
+
301 }
+
302 return *(int*)CMSG_DATA(cmsg);
+
303}
+
304
+
305void fuse_kern_unmount(const char *mountpoint, int fd)
+
306{
+
307 int res;
+
308
+
309 if (fd != -1) {
+
310 struct pollfd pfd;
+
311
+
312 pfd.fd = fd;
+
313 pfd.events = 0;
+
314 res = poll(&pfd, 1, 0);
+
315
+
316 /* Need to close file descriptor, otherwise synchronous umount
+
317 would recurse into filesystem, and deadlock.
+
318
+
319 Caller expects fuse_kern_unmount to close the fd, so close it
+
320 anyway. */
+
321 close(fd);
+
322
+
323 /* If file poll returns POLLERR on the device file descriptor,
+
324 then the filesystem is already unmounted or the connection
+
325 was severed via /sys/fs/fuse/connections/NNN/abort */
+
326 if (res == 1 && (pfd.revents & POLLERR))
+
327 return;
+
328 }
+
329
+
330 if (geteuid() == 0) {
+
331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+
332 return;
+
333 }
+
334
+
335 res = umount2(mountpoint, 2);
+
336 if (res == 0)
+
337 return;
+
338
+
339 char const * const argv[] =
+
340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
+
341 "--", mountpoint, NULL };
+
342 int status = fusermount_posix_spawn(NULL, argv, NULL);
+
343 if(status != 0) {
+
344 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
+
345 FUSERMOUNT_PROG, strerror(-status));
+
346 return;
+
347 }
+
348}
+
349
+
350static int setup_auto_unmount(const char *mountpoint, int quiet)
+
351{
+
352 int fds[2];
+
353 pid_t pid;
+
354 int res;
+
355
+
356 if (!mountpoint) {
+
357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
358 return -1;
+
359 }
+
360
+
361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
362 if(res == -1) {
+
363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed",
+
364 strerror(errno));
+
365 return -1;
+
366 }
+
367
+
368 char arg_fd_entry[30];
+
369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
371 /*
+
372 * This helps to identify the FD hold by parent process.
+
373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
375 * One potential use case is to satisfy FD-Leak checks.
+
376 */
+
377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
379
+
380 char const *const argv[] = {
+
381 FUSERMOUNT_PROG,
+
382 "--auto-unmount",
+
383 "--",
+
384 mountpoint,
+
385 NULL,
+
386 };
+
387
+
388 // TODO: add error handling for all manipulations of action.
+
389 posix_spawn_file_actions_t action;
+
390 posix_spawn_file_actions_init(&action);
+
391
+
392 if (quiet) {
+
393 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
394 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
395 }
+
396 posix_spawn_file_actions_addclose(&action, fds[1]);
+
397
+
398 /*
+
399 * auto-umount runs in the background - it is not waiting for the
+
400 * process
+
401 */
+
402 int status = fusermount_posix_spawn(&action, argv, &pid);
+
403
+
404 posix_spawn_file_actions_destroy(&action);
+
405
+
406 if(status != 0) {
+
407 close(fds[0]);
+
408 close(fds[1]);
+
409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
+
410 strerror(-status));
+
411 return -1;
+
412 }
+
413 // passed to child now, so can close here.
+
414 close(fds[0]);
+
415
+
416 // Now fusermount3 will only exit when fds[1] closes automatically when our
+
417 // process exits.
+
418 return 0;
+
419 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
+
420}
+
421
+
422static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+
423 const char *opts, int quiet)
+
424{
+
425 int fds[2];
+
426 pid_t pid;
+
427 int res;
+
428
+
429 if (!mountpoint) {
+
430 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
431 return -1;
+
432 }
+
433
+
434 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+
435 if(res == -1) {
+
436 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
+
437 FUSERMOUNT_PROG, strerror(errno));
+
438 return -1;
+
439 }
+
440
+
441 char arg_fd_entry[30];
+
442 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
+
443 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
+
444 /*
+
445 * This helps to identify the FD hold by parent process.
+
446 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
+
447 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
+
448 * One potential use case is to satisfy FD-Leak checks.
+
449 */
+
450 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
+
451 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
+
452
+
453 char const *const argv[] = {
+
454 FUSERMOUNT_PROG,
+
455 "-o", opts ? opts : "",
+
456 "--",
+
457 mountpoint,
+
458 NULL,
+
459 };
+
460
+
461
+
462 posix_spawn_file_actions_t action;
+
463 posix_spawn_file_actions_init(&action);
+
464
+
465 if (quiet) {
+
466 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+
467 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+
468 }
+
469 posix_spawn_file_actions_addclose(&action, fds[1]);
+
470
+
471 int status = fusermount_posix_spawn(&action, argv, &pid);
+
472
+
473 posix_spawn_file_actions_destroy(&action);
+
474
+
475 if(status != 0) {
+
476 close(fds[0]);
+
477 close(fds[1]);
+
478 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
+
479 FUSERMOUNT_PROG, strerror(-status));
+
480 return -1;
+
481 }
+
482
+
483 // passed to child now, so can close here.
+
484 close(fds[0]);
+
485
+
486 int fd = receive_fd(fds[1]);
+
487
+
488 if (!mo->auto_unmount) {
+
489 /* with auto_unmount option fusermount3 will not exit until
+
490 this socket is closed */
+
491 close(fds[1]);
+
492 waitpid(pid, NULL, 0); /* bury zombie */
+
493 }
+
494
+
495 if (fd >= 0)
+
496 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
497
+
498 return fd;
+
499}
+
500
+
501#ifndef O_CLOEXEC
+
502#define O_CLOEXEC 0
+
503#endif
+
504
+
505static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+
506 const char *mnt_opts)
+
507{
+
508 char tmp[128];
+
509 const char *devname = "/dev/fuse";
+
510 char *source = NULL;
+
511 char *type = NULL;
+
512 struct stat stbuf;
+
513 int fd;
+
514 int res;
+
515
+
516 if (!mnt) {
+
517 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
+
518 return -1;
+
519 }
+
520
+
521 res = stat(mnt, &stbuf);
+
522 if (res == -1) {
+
523 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
+
524 mnt, strerror(errno));
+
525 return -1;
+
526 }
+
527
+
528 fd = open(devname, O_RDWR | O_CLOEXEC);
+
529 if (fd == -1) {
+
530 if (errno == ENODEV || errno == ENOENT)
+
531 fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n");
+
532 else
+
533 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
+
534 devname, strerror(errno));
+
535 return -1;
+
536 }
+
537 if (!O_CLOEXEC)
+
538 fcntl(fd, F_SETFD, FD_CLOEXEC);
+
539
+
540 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
541 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
542
+
543 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+
544 if (res == -1)
+
545 goto out_close;
+
546
+
547 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+
548 (mo->subtype ? strlen(mo->subtype) : 0) +
+
549 strlen(devname) + 32);
+
550
+
551 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+
552 if (!type || !source) {
+
553 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
+
554 goto out_close;
+
555 }
+
556
+
557 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
558 if (mo->subtype) {
+
559 strcat(type, ".");
+
560 strcat(type, mo->subtype);
+
561 }
+
562 strcpy(source,
+
563 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
564
+
565 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
566 if (res == -1 && errno == ENODEV && mo->subtype) {
+
567 /* Probably missing subtype support */
+
568 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+
569 if (mo->fsname) {
+
570 if (!mo->blkdev)
+
571 sprintf(source, "%s#%s", mo->subtype,
+
572 mo->fsname);
+
573 } else {
+
574 strcpy(source, type);
+
575 }
+
576 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+
577 }
+
578 if (res == -1) {
+
579 /*
+
580 * Maybe kernel doesn't support unprivileged mounts, in this
+
581 * case try falling back to fusermount3
+
582 */
+
583 if (errno == EPERM) {
+
584 res = -2;
+
585 } else {
+
586 int errno_save = errno;
+
587 if (mo->blkdev && errno == ENODEV &&
+
588 !fuse_mnt_check_fuseblk())
+
589 fuse_log(FUSE_LOG_ERR,
+
590 "fuse: 'fuseblk' support missing\n");
+
591 else
+
592 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
+
593 strerror(errno_save));
+
594 }
+
595
+
596 goto out_close;
+
597 }
+
598
+
599#ifndef IGNORE_MTAB
+
600 if (geteuid() == 0) {
+
601 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+
602 res = -1;
+
603 if (!newmnt)
+
604 goto out_umount;
+
605
+
606 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+
607 mnt_opts);
+
608 free(newmnt);
+
609 if (res == -1)
+
610 goto out_umount;
+
611 }
+
612#endif /* IGNORE_MTAB */
+
613 free(type);
+
614 free(source);
+
615
+
616 return fd;
+
617
+
618out_umount:
+
619 umount2(mnt, 2); /* lazy umount */
+
620out_close:
+
621 free(type);
+
622 free(source);
+
623 close(fd);
+
624 return res;
+
625}
+
626
+
627static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+
628{
+
629 int i;
+
630
+
631 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+
632 return -1;
+
633
+
634 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
635 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
636 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+
637 return -1;
+
638 }
+
639 return 0;
+
640}
+
641
+
642struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
643{
+
644 struct mount_opts *mo;
+
645
+
646 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
647 if (mo == NULL)
+
648 return NULL;
+
649
+
650 memset(mo, 0, sizeof(struct mount_opts));
+
651 mo->flags = MS_NOSUID | MS_NODEV;
+
652
+
653 if (args &&
+
654 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
655 goto err_out;
+
656
+
657 return mo;
+
658
+
659err_out:
+
660 destroy_mount_opts(mo);
+
661 return NULL;
+
662}
+
663
+
664void destroy_mount_opts(struct mount_opts *mo)
+
665{
+
666 free(mo->fsname);
+
667 free(mo->subtype);
+
668 free(mo->fusermount_opts);
+
669 free(mo->subtype_opt);
+
670 free(mo->kernel_opts);
+
671 free(mo->mtab_opts);
+
672 free(mo);
+
673}
+
674
+
675
+
676int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
677{
+
678 int res = -1;
+
679 char *mnt_opts = NULL;
+
680
+
681 res = -1;
+
682 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+
683 goto out;
+
684 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+
685 goto out;
+
686 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+
687 goto out;
+
688
+
689 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+
690 if (res >= 0 && mo->auto_unmount) {
+
691 if(0 > setup_auto_unmount(mountpoint, 0)) {
+
692 // Something went wrong, let's umount like in fuse_mount_sys.
+
693 umount2(mountpoint, MNT_DETACH); /* lazy umount */
+
694 res = -1;
+
695 }
+
696 } else if (res == -2) {
+
697 if (mo->fusermount_opts &&
+
698 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+
699 goto out;
+
700
+
701 if (mo->subtype) {
+
702 char *tmp_opts = NULL;
+
703
+
704 res = -1;
+
705 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+
706 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+
707 free(tmp_opts);
+
708 goto out;
+
709 }
+
710
+
711 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+
712 free(tmp_opts);
+
713 if (res == -1)
+
714 res = fuse_mount_fusermount(mountpoint, mo,
+
715 mnt_opts, 0);
+
716 } else {
+
717 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+
718 }
+
719 }
+
720out:
+
721 free(mnt_opts);
+
722 return res;
+
723}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/lib_2mount__bsd_8c_source.html b/doc/html/lib_2mount__bsd_8c_source.html new file mode 100644 index 0000000..0266cbb --- /dev/null +++ b/doc/html/lib_2mount__bsd_8c_source.html @@ -0,0 +1,333 @@ + + + + + + + +libfuse: lib/mount_bsd.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_bsd.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
4
+
5 Architecture specific file system mounting (FreeBSD).
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_i.h"
+
13#include "fuse_misc.h"
+
14#include "fuse_opt.h"
+
15#include "util.h"
+
16
+
17#include <sys/param.h>
+
18#include "fuse_mount_compat.h"
+
19
+
20#include <sys/wait.h>
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <unistd.h>
+
24#include <stddef.h>
+
25#include <fcntl.h>
+
26#include <errno.h>
+
27#include <string.h>
+
28
+
29#define FUSERMOUNT_PROG "mount_fusefs"
+
30#define FUSE_DEV_TRUNK "/dev/fuse"
+
31
+
32enum {
+
33 KEY_RO,
+
34 KEY_KERN
+
35};
+
36
+
37struct mount_opts {
+
38 int allow_other;
+
39 char *kernel_opts;
+
40 unsigned max_read;
+
41};
+
42
+
43#define FUSE_DUAL_OPT_KEY(templ, key) \
+
44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
45
+
46static const struct fuse_opt fuse_mount_opts[] = {
+
47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+
48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+
49 FUSE_OPT_KEY("-r", KEY_RO),
+
50 /* standard FreeBSD mount options */
+
51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+
53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+
54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+
55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+
56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+
57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+
58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+
59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+
60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+
61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+
62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+
63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+
64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+
65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+
66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+
67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+
68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+
69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+
70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+
71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+
72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+
73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+
74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+
75 /* options supported under both Linux and FBSD */
+
76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+
77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+
78 FUSE_OPT_KEY("max_read=", KEY_KERN),
+
79 FUSE_OPT_KEY("subtype=", KEY_KERN),
+
80 /* FBSD FUSE specific mount options */
+
81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+
82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+
83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+
84#if __FreeBSD_version >= 1200519
+
85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
+
86#endif
+
87 /* stock FBSD mountopt parsing routine lets anything be negated... */
+
88 /*
+
89 * Linux specific mount options, but let just the mount util
+
90 * handle them
+
91 */
+
92 FUSE_OPT_KEY("fsname=", KEY_KERN),
+ +
94};
+
95
+
96void fuse_mount_version(void)
+
97{
+
98 system(FUSERMOUNT_PROG " --version");
+
99}
+
100
+
101unsigned get_max_read(struct mount_opts *o)
+
102{
+
103 return o->max_read;
+
104}
+
105
+
106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+
107 struct fuse_args *outargs)
+
108{
+
109 (void) outargs;
+
110 struct mount_opts *mo = data;
+
111
+
112 switch (key) {
+
113 case KEY_RO:
+
114 arg = "ro";
+
115 /* fall through */
+
116
+
117 case KEY_KERN:
+
118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
119 }
+
120
+
121 /* Pass through unknown options */
+
122 return 1;
+
123}
+
124
+
125void fuse_kern_unmount(const char *mountpoint, int fd)
+
126{
+
127 if (close(fd) < 0)
+
128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
+
129 if (unmount(mountpoint, MNT_FORCE) < 0)
+
130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
+
131 mountpoint, strerror(errno));
+
132}
+
133
+
134static int fuse_mount_core(const char *mountpoint, const char *opts)
+
135{
+
136 const char *mountprog = FUSERMOUNT_PROG;
+
137 long fd;
+
138 char *fdnam, *dev;
+
139 pid_t pid, cpid;
+
140 int status;
+
141 int err;
+
142
+
143 fdnam = getenv("FUSE_DEV_FD");
+
144
+
145 if (fdnam) {
+
146 err = libfuse_strtol(fdnam, &fd);
+
147 if (err || fd < 0) {
+
148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
+
149 return -1;
+
150 }
+
151
+
152 goto mount;
+
153 }
+
154
+
155 dev = getenv("FUSE_DEV_NAME");
+
156
+
157 if (! dev)
+
158 dev = (char *)FUSE_DEV_TRUNK;
+
159
+
160 if ((fd = open(dev, O_RDWR)) < 0) {
+
161 perror("fuse: failed to open fuse device");
+
162 return -1;
+
163 }
+
164
+
165mount:
+
166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+
167 goto out;
+
168
+
169 pid = fork();
+
170 cpid = pid;
+
171
+
172 if (pid == -1) {
+
173 perror("fuse: fork() failed");
+
174 close(fd);
+
175 return -1;
+
176 }
+
177
+
178 if (pid == 0) {
+
179 pid = fork();
+
180
+
181 if (pid == -1) {
+
182 perror("fuse: fork() failed");
+
183 close(fd);
+
184 _exit(EXIT_FAILURE);
+
185 }
+
186
+
187 if (pid == 0) {
+
188 const char *argv[32];
+
189 int a = 0;
+
190 int ret = -1;
+
191
+
192 if (! fdnam)
+
193 {
+
194 ret = asprintf(&fdnam, "%ld", fd);
+
195 if(ret == -1)
+
196 {
+
197 perror("fuse: failed to assemble mount arguments");
+
198 close(fd);
+
199 _exit(EXIT_FAILURE);
+
200 }
+
201 }
+
202
+
203 argv[a++] = mountprog;
+
204 if (opts) {
+
205 argv[a++] = "-o";
+
206 argv[a++] = opts;
+
207 }
+
208 argv[a++] = fdnam;
+
209 argv[a++] = mountpoint;
+
210 argv[a++] = NULL;
+
211 execvp(mountprog, (char **) argv);
+
212 perror("fuse: failed to exec mount program");
+
213 free(fdnam);
+
214 _exit(EXIT_FAILURE);
+
215 }
+
216
+
217 _exit(EXIT_SUCCESS);
+
218 }
+
219
+
220 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+
221 perror("fuse: failed to mount file system");
+
222 if (close(fd) < 0)
+
223 perror("fuse: closing FD");
+
224 return -1;
+
225 }
+
226
+
227out:
+
228 return fd;
+
229}
+
230
+
231struct mount_opts *parse_mount_opts(struct fuse_args *args)
+
232{
+
233 struct mount_opts *mo;
+
234
+
235 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+
236 if (mo == NULL)
+
237 return NULL;
+
238
+
239 memset(mo, 0, sizeof(struct mount_opts));
+
240
+
241 if (args &&
+
242 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+
243 goto err_out;
+
244
+
245 return mo;
+
246
+
247err_out:
+
248 destroy_mount_opts(mo);
+
249 return NULL;
+
250}
+
251
+
252void destroy_mount_opts(struct mount_opts *mo)
+
253{
+
254 free(mo->kernel_opts);
+
255 free(mo);
+
256}
+
257
+
258int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+
259{
+
260 /* mount util should not try to spawn the daemon */
+
261 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+
262 /* to notify the mount util it's called from lib */
+
263 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
264
+
265 return fuse_mount_core(mountpoint, mo->kernel_opts);
+
266}
+
void fuse_log(enum fuse_log_level level, const char *fmt,...)
Definition fuse_log.c:77
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
+ + + + diff --git a/doc/html/lib_2mount__util_8c_source.html b/doc/html/lib_2mount__util_8c_source.html new file mode 100644 index 0000000..3e8c7d1 --- /dev/null +++ b/doc/html/lib_2mount__util_8c_source.html @@ -0,0 +1,433 @@ + + + + + + + +libfuse: lib/mount_util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Architecture-independent mounting code.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file COPYING.LIB.
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13
+
14#include <stdio.h>
+
15#include <unistd.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <signal.h>
+
19#include <dirent.h>
+
20#include <errno.h>
+
21#include <fcntl.h>
+
22#include <limits.h>
+
23#include <paths.h>
+
24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
+
25#include <mntent.h>
+
26#else
+
27#define IGNORE_MTAB
+
28#endif
+
29#include <sys/stat.h>
+
30#include <sys/wait.h>
+
31
+
32#include "fuse_mount_compat.h"
+
33
+
34#include <sys/param.h>
+
35
+
36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
+
37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+
38#endif
+
39
+
40#ifdef IGNORE_MTAB
+
41#define mtab_needs_update(mnt) 0
+
42#else
+
43static int mtab_needs_update(const char *mnt)
+
44{
+
45 int res;
+
46 struct stat stbuf;
+
47
+
48 /* If mtab is within new mount, don't touch it */
+
49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+
50 _PATH_MOUNTED[strlen(mnt)] == '/')
+
51 return 0;
+
52
+
53 /*
+
54 * Skip mtab update if /etc/mtab:
+
55 *
+
56 * - doesn't exist,
+
57 * - is on a read-only filesystem.
+
58 */
+
59 res = lstat(_PATH_MOUNTED, &stbuf);
+
60 if (res == -1) {
+
61 if (errno == ENOENT)
+
62 return 0;
+
63 } else {
+
64 uid_t ruid;
+
65 int err;
+
66
+
67 ruid = getuid();
+
68 if (ruid != 0)
+
69 setreuid(0, -1);
+
70
+
71 res = access(_PATH_MOUNTED, W_OK);
+
72 err = (res == -1) ? errno : 0;
+
73 if (ruid != 0)
+
74 setreuid(ruid, -1);
+
75
+
76 if (err == EROFS)
+
77 return 0;
+
78 }
+
79
+
80 return 1;
+
81}
+
82#endif /* IGNORE_MTAB */
+
83
+
84static int add_mount(const char *progname, const char *fsname,
+
85 const char *mnt, const char *type, const char *opts)
+
86{
+
87 int res;
+
88 int status;
+
89 sigset_t blockmask;
+
90 sigset_t oldmask;
+
91
+
92 sigemptyset(&blockmask);
+
93 sigaddset(&blockmask, SIGCHLD);
+
94 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
95 if (res == -1) {
+
96 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
97 return -1;
+
98 }
+
99
+
100 res = fork();
+
101 if (res == -1) {
+
102 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
103 goto out_restore;
+
104 }
+
105 if (res == 0) {
+
106 char *env = NULL;
+
107
+
108 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
109
+
110 if(setuid(geteuid()) == -1) {
+
111 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
112 res = -1;
+
113 goto out_restore;
+
114 }
+
115
+
116 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+
117 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+
118 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+
119 progname, strerror(errno));
+
120 exit(1);
+
121 }
+
122 res = waitpid(res, &status, 0);
+
123 if (res == -1)
+
124 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
125
+
126 if (status != 0)
+
127 res = -1;
+
128
+
129 out_restore:
+
130 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
131
+
132 return res;
+
133}
+
134
+
135int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
136 const char *mnt, const char *type, const char *opts)
+
137{
+
138 if (!mtab_needs_update(mnt))
+
139 return 0;
+
140
+
141 return add_mount(progname, fsname, mnt, type, opts);
+
142}
+
143
+
144static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+
145{
+
146 int res;
+
147 int status;
+
148 sigset_t blockmask;
+
149 sigset_t oldmask;
+
150
+
151 sigemptyset(&blockmask);
+
152 sigaddset(&blockmask, SIGCHLD);
+
153 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
154 if (res == -1) {
+
155 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
156 return -1;
+
157 }
+
158
+
159 res = fork();
+
160 if (res == -1) {
+
161 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
162 goto out_restore;
+
163 }
+
164 if (res == 0) {
+
165 char *env = NULL;
+
166
+
167 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
168
+
169 if(setuid(geteuid()) == -1) {
+
170 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
171 res = -1;
+
172 goto out_restore;
+
173 }
+
174
+
175 if (lazy) {
+
176 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
177 "-l", NULL, &env);
+
178 } else {
+
179 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+
180 NULL, &env);
+
181 }
+
182 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
183 progname, strerror(errno));
+
184 exit(1);
+
185 }
+
186 res = waitpid(res, &status, 0);
+
187 if (res == -1)
+
188 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
189
+
190 if (status != 0) {
+
191 res = -1;
+
192 }
+
193
+
194 out_restore:
+
195 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
196 return res;
+
197
+
198}
+
199
+
200int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
201 const char *rel_mnt, int lazy)
+
202{
+
203 int res;
+
204
+
205 if (!mtab_needs_update(abs_mnt)) {
+
206 res = umount2(rel_mnt, lazy ? 2 : 0);
+
207 if (res == -1)
+
208 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
209 progname, abs_mnt, strerror(errno));
+
210 return res;
+
211 }
+
212
+
213 return exec_umount(progname, rel_mnt, lazy);
+
214}
+
215
+
216static int remove_mount(const char *progname, const char *mnt)
+
217{
+
218 int res;
+
219 int status;
+
220 sigset_t blockmask;
+
221 sigset_t oldmask;
+
222
+
223 sigemptyset(&blockmask);
+
224 sigaddset(&blockmask, SIGCHLD);
+
225 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+
226 if (res == -1) {
+
227 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+
228 return -1;
+
229 }
+
230
+
231 res = fork();
+
232 if (res == -1) {
+
233 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+
234 goto out_restore;
+
235 }
+
236 if (res == 0) {
+
237 char *env = NULL;
+
238
+
239 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
240
+
241 if(setuid(geteuid()) == -1) {
+
242 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+
243 res = -1;
+
244 goto out_restore;
+
245 }
+
246
+
247 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+
248 "--fake", mnt, NULL, &env);
+
249 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+
250 progname, strerror(errno));
+
251 exit(1);
+
252 }
+
253 res = waitpid(res, &status, 0);
+
254 if (res == -1)
+
255 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
256
+
257 if (status != 0)
+
258 res = -1;
+
259
+
260 out_restore:
+
261 sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
262 return res;
+
263}
+
264
+
265int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+
266{
+
267 if (!mtab_needs_update(mnt))
+
268 return 0;
+
269
+
270 return remove_mount(progname, mnt);
+
271}
+
272
+
273char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+
274{
+
275 char buf[PATH_MAX];
+
276 char *copy;
+
277 char *dst;
+
278 char *end;
+
279 char *lastcomp;
+
280 const char *toresolv;
+
281
+
282 if (!orig[0]) {
+
283 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+
284 orig);
+
285 return NULL;
+
286 }
+
287
+
288 copy = strdup(orig);
+
289 if (copy == NULL) {
+
290 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
291 return NULL;
+
292 }
+
293
+
294 toresolv = copy;
+
295 lastcomp = NULL;
+
296 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+
297 if (end[0] != '/') {
+
298 char *tmp;
+
299 end[1] = '\0';
+
300 tmp = strrchr(copy, '/');
+
301 if (tmp == NULL) {
+
302 lastcomp = copy;
+
303 toresolv = ".";
+
304 } else {
+
305 lastcomp = tmp + 1;
+
306 if (tmp == copy)
+
307 toresolv = "/";
+
308 }
+
309 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+
310 lastcomp = NULL;
+
311 toresolv = copy;
+
312 }
+
313 else if (tmp)
+
314 tmp[0] = '\0';
+
315 }
+
316 if (realpath(toresolv, buf) == NULL) {
+
317 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+
318 strerror(errno));
+
319 free(copy);
+
320 return NULL;
+
321 }
+
322 if (lastcomp == NULL)
+
323 dst = strdup(buf);
+
324 else {
+
325 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+
326 if (dst) {
+
327 unsigned buflen = strlen(buf);
+
328 if (buflen && buf[buflen-1] == '/')
+
329 sprintf(dst, "%s%s", buf, lastcomp);
+
330 else
+
331 sprintf(dst, "%s/%s", buf, lastcomp);
+
332 }
+
333 }
+
334 free(copy);
+
335 if (dst == NULL)
+
336 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
337 return dst;
+
338}
+
339
+
340int fuse_mnt_check_fuseblk(void)
+
341{
+
342 char buf[256];
+
343 FILE *f = fopen("/proc/filesystems", "r");
+
344 if (!f)
+
345 return 1;
+
346
+
347 while (fgets(buf, sizeof(buf), f))
+
348 if (strstr(buf, "fuseblk\n")) {
+
349 fclose(f);
+
350 return 1;
+
351 }
+
352
+
353 fclose(f);
+
354 return 0;
+
355}
+
356
+
357int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+
358{
+
359 int fd = -1;
+
360 int len = 0;
+
361
+
362 if (mountpoint == NULL) {
+
363 fprintf(stderr, "Invalid null-ptr mount-point!\n");
+
364 return -1;
+
365 }
+
366
+
367 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+
368 len == strlen(mountpoint)) {
+
369 return fd;
+
370 }
+
371
+
372 return -1;
+
373}
+
+ + + + diff --git a/doc/html/lib_2mount__util_8h_source.html b/doc/html/lib_2mount__util_8h_source.html new file mode 100644 index 0000000..4f965f7 --- /dev/null +++ b/doc/html/lib_2mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: lib/mount_util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
mount_util.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file COPYING.LIB.
+
7*/
+
8
+
9#include <sys/types.h>
+
10
+
11int fuse_mnt_add_mount(const char *progname, const char *fsname,
+
12 const char *mnt, const char *type, const char *opts);
+
13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+
14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+
15 const char *rel_mnt, int lazy);
+
16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+
17int fuse_mnt_check_fuseblk(void);
+
18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
+
+ + + + diff --git a/doc/html/lib_2util_8c_source.html b/doc/html/lib_2util_8c_source.html new file mode 100644 index 0000000..8502090 --- /dev/null +++ b/doc/html/lib_2util_8c_source.html @@ -0,0 +1,87 @@ + + + + + + + +libfuse: lib/util.c Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.c
+
+
+
1#include <stdlib.h>
+
2#include <errno.h>
+
3
+
4#include "util.h"
+
5
+
6int libfuse_strtol(const char *str, long *res)
+
7{
+
8 char *endptr;
+
9 int base = 10;
+
10 long val;
+
11
+
12 errno = 0;
+
13
+
14 if (!str)
+
15 return -EINVAL;
+
16
+
17 val = strtol(str, &endptr, base);
+
18
+
19 if (errno)
+
20 return -errno;
+
21
+
22 if (endptr == str || *endptr != '\0')
+
23 return -EINVAL;
+
24
+
25 *res = val;
+
26 return 0;
+
27}
+
+ + + + diff --git a/doc/html/lib_2util_8h_source.html b/doc/html/lib_2util_8h_source.html new file mode 100644 index 0000000..4acdad4 --- /dev/null +++ b/doc/html/lib_2util_8h_source.html @@ -0,0 +1,87 @@ + + + + + + + +libfuse: lib/util.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
util.h
+
+
+
1#ifndef FUSE_UTIL_H_
+
2#define FUSE_UTIL_H_
+
3
+
4#include <stdint.h>
+
5
+
6#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
+
7
+
8#define likely(x) __builtin_expect(!!(x), 1)
+
9#define unlikely(x) __builtin_expect(!!(x), 0)
+
10
+
11int libfuse_strtol(const char *str, long *res);
+
12
+
16static inline uint32_t fuse_lower_32_bits(uint64_t nr)
+
17{
+
18 return (uint32_t)(nr & 0xffffffff);
+
19}
+
20
+
24static inline uint64_t fuse_higher_32_bits(uint64_t nr)
+
25{
+
26 return nr & ~0xffffffffULL;
+
27}
+
28
+
29#ifndef FUSE_VAR_UNUSED
+
30#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var)
+
31#endif
+
32
+
33#endif
+
+ + + + diff --git a/doc/html/libfuse__config_8h_source.html b/doc/html/libfuse__config_8h_source.html new file mode 100644 index 0000000..dad4663 --- /dev/null +++ b/doc/html/libfuse__config_8h_source.html @@ -0,0 +1,77 @@ + + + + + + + +libfuse: build-ubuntu/libfuse_config.h Source File + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
libfuse_config.h
+
+
+
1/*
+
2 * Autogenerated by the Meson build system.
+
3 * Do not edit, your changes will be lost.
+
4 */
+
5
+
6#pragma once
+
7
+
8#define FUSE_HOTFIX_VERSION 1
+
9
+
10#define FUSE_MAJOR_VERSION 3
+
11
+
12#define FUSE_MINOR_VERSION 17
+
13
+
14#define FUSE_RC_VERSION rc1
+
15
+
16#define LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS 1
+
17
+
+ + + + diff --git a/doc/html/menu.js b/doc/html/menu.js new file mode 100644 index 0000000..b0b2693 --- /dev/null +++ b/doc/html/menu.js @@ -0,0 +1,136 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function initMenu(relPath,searchEnabled,serverSide,searchPage,search) { + function makeTree(data,relPath) { + var result=''; + if ('children' in data) { + result+='
    '; + for (var i in data.children) { + var url; + var link; + link = data.children[i].url; + if (link.substring(0,1)=='^') { + url = link.substring(1); + } else { + url = relPath+link; + } + result+='
  • '+ + data.children[i].text+''+ + makeTree(data.children[i],relPath)+'
  • '; + } + result+='
'; + } + return result; + } + var searchBoxHtml; + if (searchEnabled) { + if (serverSide) { + searchBoxHtml='
'+ + '
'+ + '
 '+ + ''+ + '
'+ + '
'+ + '
'+ + '
'; + } else { + searchBoxHtml='
'+ + ''+ + ' '+ + ''+ + ''+ + ''+ + ''+ + ''+ + '
'; + } + } + + $('#main-nav').before('
'+ + ''+ + ''+ + '
'); + $('#main-nav').append(makeTree(menudata,relPath)); + $('#main-nav').children(':first').addClass('sm sm-dox').attr('id','main-menu'); + if (searchBoxHtml) { + $('#main-menu').append('
  • '); + } + var $mainMenuState = $('#main-menu-state'); + var prevWidth = 0; + if ($mainMenuState.length) { + function initResizableIfExists() { + if (typeof initResizable==='function') initResizable(); + } + // animate mobile menu + $mainMenuState.change(function(e) { + var $menu = $('#main-menu'); + var options = { duration: 250, step: initResizableIfExists }; + if (this.checked) { + options['complete'] = function() { $menu.css('display', 'block') }; + $menu.hide().slideDown(options); + } else { + options['complete'] = function() { $menu.css('display', 'none') }; + $menu.show().slideUp(options); + } + }); + // set default menu visibility + function resetState() { + var $menu = $('#main-menu'); + var $mainMenuState = $('#main-menu-state'); + var newWidth = $(window).outerWidth(); + if (newWidth!=prevWidth) { + if ($(window).outerWidth()<768) { + $mainMenuState.prop('checked',false); $menu.hide(); + $('#searchBoxPos1').html(searchBoxHtml); + $('#searchBoxPos2').hide(); + } else { + $menu.show(); + $('#searchBoxPos1').empty(); + $('#searchBoxPos2').html(searchBoxHtml); + $('#searchBoxPos2').show(); + } + if (typeof searchBox!=='undefined') { + searchBox.CloseResultsWindow(); + } + prevWidth = newWidth; + } + } + $(window).ready(function() { resetState(); initResizableIfExists(); }); + $(window).resize(resetState); + } + $('#main-menu').smartmenus(); +} +/* @license-end */ diff --git a/doc/html/menudata.js b/doc/html/menudata.js new file mode 100644 index 0000000..2341ca8 --- /dev/null +++ b/doc/html/menudata.js @@ -0,0 +1,85 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file +*/ +var menudata={children:[ +{text:"Main Page",url:"index.html"}, +{text:"Data Structures",url:"annotated.html",children:[ +{text:"Data Structures",url:"annotated.html"}, +{text:"Data Fields",url:"functions.html",children:[ +{text:"All",url:"functions.html",children:[ +{text:"a",url:"functions.html#index_a"}, +{text:"b",url:"functions.html#index_b"}, +{text:"c",url:"functions.html#index_c"}, +{text:"d",url:"functions.html#index_d"}, +{text:"e",url:"functions.html#index_e"}, +{text:"f",url:"functions.html#index_f"}, +{text:"g",url:"functions.html#index_g"}, +{text:"h",url:"functions.html#index_h"}, +{text:"i",url:"functions.html#index_i"}, +{text:"k",url:"functions.html#index_k"}, +{text:"l",url:"functions.html#index_l"}, +{text:"m",url:"functions.html#index_m"}, +{text:"n",url:"functions.html#index_n"}, +{text:"o",url:"functions.html#index_o"}, +{text:"p",url:"functions.html#index_p"}, +{text:"r",url:"functions.html#index_r"}, +{text:"s",url:"functions.html#index_s"}, +{text:"t",url:"functions.html#index_t"}, +{text:"u",url:"functions.html#index_u"}, +{text:"v",url:"functions.html#index_v"}, +{text:"w",url:"functions.html#index_w"}]}, +{text:"Variables",url:"functions_vars.html",children:[ +{text:"a",url:"functions_vars.html#index_a"}, +{text:"b",url:"functions_vars.html#index_b"}, +{text:"c",url:"functions_vars.html#index_c"}, +{text:"d",url:"functions_vars.html#index_d"}, +{text:"e",url:"functions_vars.html#index_e"}, +{text:"f",url:"functions_vars.html#index_f"}, +{text:"g",url:"functions_vars.html#index_g"}, +{text:"h",url:"functions_vars.html#index_h"}, +{text:"i",url:"functions_vars.html#index_i"}, +{text:"k",url:"functions_vars.html#index_k"}, +{text:"l",url:"functions_vars.html#index_l"}, +{text:"m",url:"functions_vars.html#index_m"}, +{text:"n",url:"functions_vars.html#index_n"}, +{text:"o",url:"functions_vars.html#index_o"}, +{text:"p",url:"functions_vars.html#index_p"}, +{text:"r",url:"functions_vars.html#index_r"}, +{text:"s",url:"functions_vars.html#index_s"}, +{text:"t",url:"functions_vars.html#index_t"}, +{text:"u",url:"functions_vars.html#index_u"}, +{text:"v",url:"functions_vars.html#index_v"}, +{text:"w",url:"functions_vars.html#index_w"}]}]}]}, +{text:"Files",url:"files.html",children:[ +{text:"File List",url:"files.html"}, +{text:"Globals",url:"globals.html",children:[ +{text:"All",url:"globals.html",children:[ +{text:"f",url:"globals.html#index_f"}]}, +{text:"Functions",url:"globals_func.html",children:[ +{text:"f",url:"globals_func.html#index_f"}]}, +{text:"Typedefs",url:"globals_type.html"}, +{text:"Enumerations",url:"globals_enum.html"}, +{text:"Enumerator",url:"globals_eval.html"}, +{text:"Macros",url:"globals_defs.html",children:[ +{text:"f",url:"globals_defs.html#index_f"}]}]}]}]} diff --git a/doc/html/minus.svg b/doc/html/minus.svg new file mode 100644 index 0000000..f70d0c1 --- /dev/null +++ b/doc/html/minus.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/doc/html/minusd.svg b/doc/html/minusd.svg new file mode 100644 index 0000000..5f8e879 --- /dev/null +++ b/doc/html/minusd.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/doc/html/mount_8c_source.html b/doc/html/mount_8c_source.html new file mode 100644 index 0000000..b0e65a7 --- /dev/null +++ b/doc/html/mount_8c_source.html @@ -0,0 +1,793 @@ + + + + + + + +libfuse: lib/mount.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    mount.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 Architecture specific file system mounting (Linux).
    +
    6
    +
    7 This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 See the file COPYING.LIB.
    +
    9*/
    +
    10
    +
    11/* For environ */
    +
    12#define _GNU_SOURCE
    +
    13
    +
    14#include "fuse_config.h"
    +
    15#include "fuse_i.h"
    +
    16#include "fuse_misc.h"
    +
    17#include "fuse_opt.h"
    +
    18#include "mount_util.h"
    +
    19
    +
    20#include <stdio.h>
    +
    21#include <stdlib.h>
    +
    22#include <unistd.h>
    +
    23#include <stddef.h>
    +
    24#include <string.h>
    +
    25#include <fcntl.h>
    +
    26#include <errno.h>
    +
    27#include <poll.h>
    +
    28#include <spawn.h>
    +
    29#include <sys/socket.h>
    +
    30#include <sys/un.h>
    +
    31#include <sys/wait.h>
    +
    32
    +
    33#include "fuse_mount_compat.h"
    +
    34
    +
    35#ifdef __NetBSD__
    +
    36#include <perfuse.h>
    +
    37
    +
    38#define MS_RDONLY MNT_RDONLY
    +
    39#define MS_NOSUID MNT_NOSUID
    +
    40#define MS_NODEV MNT_NODEV
    +
    41#define MS_NOEXEC MNT_NOEXEC
    +
    42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
    +
    43#define MS_NOATIME MNT_NOATIME
    +
    44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
    +
    45
    +
    46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
    +
    47#endif
    +
    48
    +
    49#define FUSERMOUNT_PROG "fusermount3"
    +
    50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
    +
    51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
    +
    52
    +
    53#ifndef MS_DIRSYNC
    +
    54#define MS_DIRSYNC 128
    +
    55#endif
    +
    56
    +
    57enum {
    +
    58 KEY_KERN_FLAG,
    +
    59 KEY_KERN_OPT,
    +
    60 KEY_FUSERMOUNT_OPT,
    +
    61 KEY_SUBTYPE_OPT,
    +
    62 KEY_MTAB_OPT,
    +
    63 KEY_ALLOW_OTHER,
    +
    64 KEY_RO,
    +
    65};
    +
    66
    +
    67struct mount_opts {
    +
    68 int allow_other;
    +
    69 int flags;
    +
    70 int auto_unmount;
    +
    71 int blkdev;
    +
    72 char *fsname;
    +
    73 char *subtype;
    +
    74 char *subtype_opt;
    +
    75 char *mtab_opts;
    +
    76 char *fusermount_opts;
    +
    77 char *kernel_opts;
    +
    78 unsigned max_read;
    +
    79};
    +
    80
    +
    81#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
    +
    82
    +
    83static const struct fuse_opt fuse_mount_opts[] = {
    +
    84 FUSE_MOUNT_OPT("allow_other", allow_other),
    +
    85 FUSE_MOUNT_OPT("blkdev", blkdev),
    +
    86 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
    +
    87 FUSE_MOUNT_OPT("fsname=%s", fsname),
    +
    88 FUSE_MOUNT_OPT("max_read=%u", max_read),
    +
    89 FUSE_MOUNT_OPT("subtype=%s", subtype),
    +
    90 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
    +
    91 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
    +
    92 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
    +
    93 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
    +
    94 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
    +
    95 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
    +
    96 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
    +
    97 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
    +
    98 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
    +
    99 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
    +
    100 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
    +
    101 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
    +
    102 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
    +
    103 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
    +
    104 FUSE_OPT_KEY("-r", KEY_RO),
    +
    105 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
    +
    106 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
    +
    107 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
    +
    108 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
    +
    109 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
    +
    110 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
    +
    111 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
    +
    112 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
    +
    113 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
    +
    114 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
    +
    115 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
    +
    116 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
    +
    117 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
    +
    118 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
    +
    119 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
    +
    120 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
    + +
    122};
    +
    123
    +
    124/*
    +
    125 * Running fusermount by calling 'posix_spawn'
    +
    126 *
    +
    127 * @param out_pid might be NULL
    +
    128 */
    +
    129static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
    +
    130 char const * const argv[], pid_t *out_pid)
    +
    131{
    +
    132 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
    +
    133 pid_t pid;
    +
    134
    +
    135 /* See man 7 environ for the global environ pointer */
    +
    136
    +
    137 /* first try the install path */
    +
    138 int status = posix_spawn(&pid, full_path, action, NULL,
    +
    139 (char * const *) argv, environ);
    +
    140 if (status != 0) {
    +
    141 /* if that fails, try a system install */
    +
    142 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
    +
    143 (char * const *) argv, environ);
    +
    144 }
    +
    145
    +
    146 if (status != 0) {
    +
    147 fuse_log(FUSE_LOG_ERR,
    +
    148 "On calling fusermount posix_spawn failed: %s\n",
    +
    149 strerror(status));
    +
    150 return -status;
    +
    151 }
    +
    152
    +
    153 if (out_pid)
    +
    154 *out_pid = pid;
    +
    155 else
    +
    156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
    +
    157
    +
    158 return 0;
    +
    159}
    +
    160
    +
    161void fuse_mount_version(void)
    +
    162{
    +
    163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
    +
    164 int status = fusermount_posix_spawn(NULL, argv, NULL);
    +
    165
    +
    166 if(status != 0)
    +
    167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
    +
    168 FUSERMOUNT_PROG);
    +
    169}
    +
    170
    +
    171struct mount_flags {
    +
    172 const char *opt;
    +
    173 unsigned long flag;
    +
    174 int on;
    +
    175};
    +
    176
    +
    177static const struct mount_flags mount_flags[] = {
    +
    178 {"rw", MS_RDONLY, 0},
    +
    179 {"ro", MS_RDONLY, 1},
    +
    180 {"suid", MS_NOSUID, 0},
    +
    181 {"nosuid", MS_NOSUID, 1},
    +
    182 {"dev", MS_NODEV, 0},
    +
    183 {"nodev", MS_NODEV, 1},
    +
    184 {"exec", MS_NOEXEC, 0},
    +
    185 {"noexec", MS_NOEXEC, 1},
    +
    186 {"async", MS_SYNCHRONOUS, 0},
    +
    187 {"sync", MS_SYNCHRONOUS, 1},
    +
    188 {"noatime", MS_NOATIME, 1},
    +
    189 {"nodiratime", MS_NODIRATIME, 1},
    +
    190 {"norelatime", MS_RELATIME, 0},
    +
    191 {"nostrictatime", MS_STRICTATIME, 0},
    +
    192 {"symfollow", MS_NOSYMFOLLOW, 0},
    +
    193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
    +
    194#ifndef __NetBSD__
    +
    195 {"dirsync", MS_DIRSYNC, 1},
    +
    196#endif
    +
    197 {NULL, 0, 0}
    +
    198};
    +
    199
    +
    200unsigned get_max_read(struct mount_opts *o)
    +
    201{
    +
    202 return o->max_read;
    +
    203}
    +
    204
    +
    205static void set_mount_flag(const char *s, int *flags)
    +
    206{
    +
    207 int i;
    +
    208
    +
    209 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    210 const char *opt = mount_flags[i].opt;
    +
    211 if (strcmp(opt, s) == 0) {
    +
    212 if (mount_flags[i].on)
    +
    213 *flags |= mount_flags[i].flag;
    +
    214 else
    +
    215 *flags &= ~mount_flags[i].flag;
    +
    216 return;
    +
    217 }
    +
    218 }
    +
    219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
    +
    220 abort();
    +
    221}
    +
    222
    +
    223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
    +
    224 struct fuse_args *outargs)
    +
    225{
    +
    226 (void) outargs;
    +
    227 struct mount_opts *mo = data;
    +
    228
    +
    229 switch (key) {
    +
    230 case KEY_RO:
    +
    231 arg = "ro";
    +
    232 /* fall through */
    +
    233 case KEY_KERN_FLAG:
    +
    234 set_mount_flag(arg, &mo->flags);
    +
    235 return 0;
    +
    236
    +
    237 case KEY_KERN_OPT:
    +
    238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
    +
    239
    +
    240 case KEY_FUSERMOUNT_OPT:
    +
    241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
    +
    242
    +
    243 case KEY_SUBTYPE_OPT:
    +
    244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
    +
    245
    +
    246 case KEY_MTAB_OPT:
    +
    247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
    +
    248
    +
    249 /* Third party options like 'x-gvfs-notrash' */
    +
    250 case FUSE_OPT_KEY_OPT:
    +
    251 return (strncmp("x-", arg, 2) == 0) ?
    +
    252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
    +
    253 1;
    +
    254 }
    +
    255
    +
    256 /* Pass through unknown options */
    +
    257 return 1;
    +
    258}
    +
    259
    +
    260/* return value:
    +
    261 * >= 0 => fd
    +
    262 * -1 => error
    +
    263 */
    +
    264static int receive_fd(int fd)
    +
    265{
    +
    266 struct msghdr msg;
    +
    267 struct iovec iov;
    +
    268 char buf[1];
    +
    269 int rv;
    +
    270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
    +
    271 struct cmsghdr *cmsg;
    +
    272
    +
    273 iov.iov_base = buf;
    +
    274 iov.iov_len = 1;
    +
    275
    +
    276 memset(&msg, 0, sizeof(msg));
    +
    277 msg.msg_name = 0;
    +
    278 msg.msg_namelen = 0;
    +
    279 msg.msg_iov = &iov;
    +
    280 msg.msg_iovlen = 1;
    +
    281 /* old BSD implementations should use msg_accrights instead of
    +
    282 * msg_control; the interface is different. */
    +
    283 msg.msg_control = ccmsg;
    +
    284 msg.msg_controllen = sizeof(ccmsg);
    +
    285
    +
    286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
    +
    287 if (rv == -1) {
    +
    288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
    +
    289 return -1;
    +
    290 }
    +
    291 if(!rv) {
    +
    292 /* EOF */
    +
    293 return -1;
    +
    294 }
    +
    295
    +
    296 cmsg = CMSG_FIRSTHDR(&msg);
    +
    297 if (cmsg->cmsg_type != SCM_RIGHTS) {
    +
    298 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
    +
    299 cmsg->cmsg_type);
    +
    300 return -1;
    +
    301 }
    +
    302 return *(int*)CMSG_DATA(cmsg);
    +
    303}
    +
    304
    +
    305void fuse_kern_unmount(const char *mountpoint, int fd)
    +
    306{
    +
    307 int res;
    +
    308
    +
    309 if (fd != -1) {
    +
    310 struct pollfd pfd;
    +
    311
    +
    312 pfd.fd = fd;
    +
    313 pfd.events = 0;
    +
    314 res = poll(&pfd, 1, 0);
    +
    315
    +
    316 /* Need to close file descriptor, otherwise synchronous umount
    +
    317 would recurse into filesystem, and deadlock.
    +
    318
    +
    319 Caller expects fuse_kern_unmount to close the fd, so close it
    +
    320 anyway. */
    +
    321 close(fd);
    +
    322
    +
    323 /* If file poll returns POLLERR on the device file descriptor,
    +
    324 then the filesystem is already unmounted or the connection
    +
    325 was severed via /sys/fs/fuse/connections/NNN/abort */
    +
    326 if (res == 1 && (pfd.revents & POLLERR))
    +
    327 return;
    +
    328 }
    +
    329
    +
    330 if (geteuid() == 0) {
    +
    331 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
    +
    332 return;
    +
    333 }
    +
    334
    +
    335 res = umount2(mountpoint, 2);
    +
    336 if (res == 0)
    +
    337 return;
    +
    338
    +
    339 char const * const argv[] =
    +
    340 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
    +
    341 "--", mountpoint, NULL };
    +
    342 int status = fusermount_posix_spawn(NULL, argv, NULL);
    +
    343 if(status != 0) {
    +
    344 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
    +
    345 FUSERMOUNT_PROG, strerror(-status));
    +
    346 return;
    +
    347 }
    +
    348}
    +
    349
    +
    350static int setup_auto_unmount(const char *mountpoint, int quiet)
    +
    351{
    +
    352 int fds[2];
    +
    353 pid_t pid;
    +
    354 int res;
    +
    355
    +
    356 if (!mountpoint) {
    +
    357 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
    +
    358 return -1;
    +
    359 }
    +
    360
    +
    361 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
    +
    362 if(res == -1) {
    +
    363 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed",
    +
    364 strerror(errno));
    +
    365 return -1;
    +
    366 }
    +
    367
    +
    368 char arg_fd_entry[30];
    +
    369 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
    +
    370 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
    +
    371 /*
    +
    372 * This helps to identify the FD hold by parent process.
    +
    373 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
    +
    374 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
    +
    375 * One potential use case is to satisfy FD-Leak checks.
    +
    376 */
    +
    377 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
    +
    378 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
    +
    379
    +
    380 char const *const argv[] = {
    +
    381 FUSERMOUNT_PROG,
    +
    382 "--auto-unmount",
    +
    383 "--",
    +
    384 mountpoint,
    +
    385 NULL,
    +
    386 };
    +
    387
    +
    388 // TODO: add error handling for all manipulations of action.
    +
    389 posix_spawn_file_actions_t action;
    +
    390 posix_spawn_file_actions_init(&action);
    +
    391
    +
    392 if (quiet) {
    +
    393 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
    +
    394 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
    +
    395 }
    +
    396 posix_spawn_file_actions_addclose(&action, fds[1]);
    +
    397
    +
    398 /*
    +
    399 * auto-umount runs in the background - it is not waiting for the
    +
    400 * process
    +
    401 */
    +
    402 int status = fusermount_posix_spawn(&action, argv, &pid);
    +
    403
    +
    404 posix_spawn_file_actions_destroy(&action);
    +
    405
    +
    406 if(status != 0) {
    +
    407 close(fds[0]);
    +
    408 close(fds[1]);
    +
    409 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
    +
    410 strerror(-status));
    +
    411 return -1;
    +
    412 }
    +
    413 // passed to child now, so can close here.
    +
    414 close(fds[0]);
    +
    415
    +
    416 // Now fusermount3 will only exit when fds[1] closes automatically when our
    +
    417 // process exits.
    +
    418 return 0;
    +
    419 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
    +
    420}
    +
    421
    +
    422static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
    +
    423 const char *opts, int quiet)
    +
    424{
    +
    425 int fds[2];
    +
    426 pid_t pid;
    +
    427 int res;
    +
    428
    +
    429 if (!mountpoint) {
    +
    430 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
    +
    431 return -1;
    +
    432 }
    +
    433
    +
    434 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
    +
    435 if(res == -1) {
    +
    436 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
    +
    437 FUSERMOUNT_PROG, strerror(errno));
    +
    438 return -1;
    +
    439 }
    +
    440
    +
    441 char arg_fd_entry[30];
    +
    442 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
    +
    443 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
    +
    444 /*
    +
    445 * This helps to identify the FD hold by parent process.
    +
    446 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
    +
    447 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
    +
    448 * One potential use case is to satisfy FD-Leak checks.
    +
    449 */
    +
    450 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
    +
    451 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
    +
    452
    +
    453 char const *const argv[] = {
    +
    454 FUSERMOUNT_PROG,
    +
    455 "-o", opts ? opts : "",
    +
    456 "--",
    +
    457 mountpoint,
    +
    458 NULL,
    +
    459 };
    +
    460
    +
    461
    +
    462 posix_spawn_file_actions_t action;
    +
    463 posix_spawn_file_actions_init(&action);
    +
    464
    +
    465 if (quiet) {
    +
    466 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
    +
    467 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
    +
    468 }
    +
    469 posix_spawn_file_actions_addclose(&action, fds[1]);
    +
    470
    +
    471 int status = fusermount_posix_spawn(&action, argv, &pid);
    +
    472
    +
    473 posix_spawn_file_actions_destroy(&action);
    +
    474
    +
    475 if(status != 0) {
    +
    476 close(fds[0]);
    +
    477 close(fds[1]);
    +
    478 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
    +
    479 FUSERMOUNT_PROG, strerror(-status));
    +
    480 return -1;
    +
    481 }
    +
    482
    +
    483 // passed to child now, so can close here.
    +
    484 close(fds[0]);
    +
    485
    +
    486 int fd = receive_fd(fds[1]);
    +
    487
    +
    488 if (!mo->auto_unmount) {
    +
    489 /* with auto_unmount option fusermount3 will not exit until
    +
    490 this socket is closed */
    +
    491 close(fds[1]);
    +
    492 waitpid(pid, NULL, 0); /* bury zombie */
    +
    493 }
    +
    494
    +
    495 if (fd >= 0)
    +
    496 fcntl(fd, F_SETFD, FD_CLOEXEC);
    +
    497
    +
    498 return fd;
    +
    499}
    +
    500
    +
    501#ifndef O_CLOEXEC
    +
    502#define O_CLOEXEC 0
    +
    503#endif
    +
    504
    +
    505static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
    +
    506 const char *mnt_opts)
    +
    507{
    +
    508 char tmp[128];
    +
    509 const char *devname = "/dev/fuse";
    +
    510 char *source = NULL;
    +
    511 char *type = NULL;
    +
    512 struct stat stbuf;
    +
    513 int fd;
    +
    514 int res;
    +
    515
    +
    516 if (!mnt) {
    +
    517 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
    +
    518 return -1;
    +
    519 }
    +
    520
    +
    521 res = stat(mnt, &stbuf);
    +
    522 if (res == -1) {
    +
    523 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
    +
    524 mnt, strerror(errno));
    +
    525 return -1;
    +
    526 }
    +
    527
    +
    528 fd = open(devname, O_RDWR | O_CLOEXEC);
    +
    529 if (fd == -1) {
    +
    530 if (errno == ENODEV || errno == ENOENT)
    +
    531 fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n");
    +
    532 else
    +
    533 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
    +
    534 devname, strerror(errno));
    +
    535 return -1;
    +
    536 }
    +
    537 if (!O_CLOEXEC)
    +
    538 fcntl(fd, F_SETFD, FD_CLOEXEC);
    +
    539
    +
    540 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
    +
    541 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
    +
    542
    +
    543 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
    +
    544 if (res == -1)
    +
    545 goto out_close;
    +
    546
    +
    547 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
    +
    548 (mo->subtype ? strlen(mo->subtype) : 0) +
    +
    549 strlen(devname) + 32);
    +
    550
    +
    551 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
    +
    552 if (!type || !source) {
    +
    553 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
    +
    554 goto out_close;
    +
    555 }
    +
    556
    +
    557 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
    +
    558 if (mo->subtype) {
    +
    559 strcat(type, ".");
    +
    560 strcat(type, mo->subtype);
    +
    561 }
    +
    562 strcpy(source,
    +
    563 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
    +
    564
    +
    565 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
    +
    566 if (res == -1 && errno == ENODEV && mo->subtype) {
    +
    567 /* Probably missing subtype support */
    +
    568 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
    +
    569 if (mo->fsname) {
    +
    570 if (!mo->blkdev)
    +
    571 sprintf(source, "%s#%s", mo->subtype,
    +
    572 mo->fsname);
    +
    573 } else {
    +
    574 strcpy(source, type);
    +
    575 }
    +
    576 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
    +
    577 }
    +
    578 if (res == -1) {
    +
    579 /*
    +
    580 * Maybe kernel doesn't support unprivileged mounts, in this
    +
    581 * case try falling back to fusermount3
    +
    582 */
    +
    583 if (errno == EPERM) {
    +
    584 res = -2;
    +
    585 } else {
    +
    586 int errno_save = errno;
    +
    587 if (mo->blkdev && errno == ENODEV &&
    +
    588 !fuse_mnt_check_fuseblk())
    +
    589 fuse_log(FUSE_LOG_ERR,
    +
    590 "fuse: 'fuseblk' support missing\n");
    +
    591 else
    +
    592 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
    +
    593 strerror(errno_save));
    +
    594 }
    +
    595
    +
    596 goto out_close;
    +
    597 }
    +
    598
    +
    599#ifndef IGNORE_MTAB
    +
    600 if (geteuid() == 0) {
    +
    601 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
    +
    602 res = -1;
    +
    603 if (!newmnt)
    +
    604 goto out_umount;
    +
    605
    +
    606 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
    +
    607 mnt_opts);
    +
    608 free(newmnt);
    +
    609 if (res == -1)
    +
    610 goto out_umount;
    +
    611 }
    +
    612#endif /* IGNORE_MTAB */
    +
    613 free(type);
    +
    614 free(source);
    +
    615
    +
    616 return fd;
    +
    617
    +
    618out_umount:
    +
    619 umount2(mnt, 2); /* lazy umount */
    +
    620out_close:
    +
    621 free(type);
    +
    622 free(source);
    +
    623 close(fd);
    +
    624 return res;
    +
    625}
    +
    626
    +
    627static int get_mnt_flag_opts(char **mnt_optsp, int flags)
    +
    628{
    +
    629 int i;
    +
    630
    +
    631 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
    +
    632 return -1;
    +
    633
    +
    634 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    635 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
    +
    636 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
    +
    637 return -1;
    +
    638 }
    +
    639 return 0;
    +
    640}
    +
    641
    +
    642struct mount_opts *parse_mount_opts(struct fuse_args *args)
    +
    643{
    +
    644 struct mount_opts *mo;
    +
    645
    +
    646 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
    +
    647 if (mo == NULL)
    +
    648 return NULL;
    +
    649
    +
    650 memset(mo, 0, sizeof(struct mount_opts));
    +
    651 mo->flags = MS_NOSUID | MS_NODEV;
    +
    652
    +
    653 if (args &&
    +
    654 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
    +
    655 goto err_out;
    +
    656
    +
    657 return mo;
    +
    658
    +
    659err_out:
    +
    660 destroy_mount_opts(mo);
    +
    661 return NULL;
    +
    662}
    +
    663
    +
    664void destroy_mount_opts(struct mount_opts *mo)
    +
    665{
    +
    666 free(mo->fsname);
    +
    667 free(mo->subtype);
    +
    668 free(mo->fusermount_opts);
    +
    669 free(mo->subtype_opt);
    +
    670 free(mo->kernel_opts);
    +
    671 free(mo->mtab_opts);
    +
    672 free(mo);
    +
    673}
    +
    674
    +
    675
    +
    676int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
    +
    677{
    +
    678 int res = -1;
    +
    679 char *mnt_opts = NULL;
    +
    680
    +
    681 res = -1;
    +
    682 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
    +
    683 goto out;
    +
    684 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
    +
    685 goto out;
    +
    686 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
    +
    687 goto out;
    +
    688
    +
    689 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
    +
    690 if (res >= 0 && mo->auto_unmount) {
    +
    691 if(0 > setup_auto_unmount(mountpoint, 0)) {
    +
    692 // Something went wrong, let's umount like in fuse_mount_sys.
    +
    693 umount2(mountpoint, MNT_DETACH); /* lazy umount */
    +
    694 res = -1;
    +
    695 }
    +
    696 } else if (res == -2) {
    +
    697 if (mo->fusermount_opts &&
    +
    698 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
    +
    699 goto out;
    +
    700
    +
    701 if (mo->subtype) {
    +
    702 char *tmp_opts = NULL;
    +
    703
    +
    704 res = -1;
    +
    705 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
    +
    706 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
    +
    707 free(tmp_opts);
    +
    708 goto out;
    +
    709 }
    +
    710
    +
    711 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
    +
    712 free(tmp_opts);
    +
    713 if (res == -1)
    +
    714 res = fuse_mount_fusermount(mountpoint, mo,
    +
    715 mnt_opts, 0);
    +
    716 } else {
    +
    717 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
    +
    718 }
    +
    719 }
    +
    720out:
    +
    721 free(mnt_opts);
    +
    722 return res;
    +
    723}
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...)
    Definition fuse_log.c:77
    + +
    #define FUSE_OPT_KEY(templ, key)
    Definition fuse_opt.h:98
    +
    #define FUSE_OPT_KEY_OPT
    Definition fuse_opt.h:129
    +
    int fuse_opt_add_opt_escaped(char **opts, const char *opt)
    Definition fuse_opt.c:144
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    int fuse_opt_add_opt(char **opts, const char *opt)
    Definition fuse_opt.c:139
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    + + + + diff --git a/doc/html/mount_8fuse_8c_source.html b/doc/html/mount_8fuse_8c_source.html new file mode 100644 index 0000000..61d4c2d --- /dev/null +++ b/doc/html/mount_8fuse_8c_source.html @@ -0,0 +1,516 @@ + + + + + + + +libfuse: util/mount.fuse.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    mount.fuse.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    9#include "fuse_config.h"
    +
    10
    +
    11#include <stdio.h>
    +
    12#include <stdlib.h>
    +
    13#include <string.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16#include <stdint.h>
    +
    17#include <fcntl.h>
    +
    18#include <pwd.h>
    +
    19#include <sys/wait.h>
    +
    20
    +
    21#ifdef linux
    +
    22#include <sys/prctl.h>
    +
    23#include <sys/syscall.h>
    +
    24#include <linux/capability.h>
    +
    25#include <linux/securebits.h>
    +
    26/* for 2.6 kernels */
    +
    27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
    +
    28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
    +
    29#endif
    +
    30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
    +
    31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
    +
    32#endif
    +
    33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
    +
    34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
    +
    35#endif
    +
    36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
    +
    37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
    +
    38#endif
    +
    39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
    +
    40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
    +
    41#endif
    +
    42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
    +
    43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
    +
    44#endif
    +
    45#endif
    +
    46/* linux < 3.5 */
    +
    47#ifndef PR_SET_NO_NEW_PRIVS
    +
    48#define PR_SET_NO_NEW_PRIVS 38
    +
    49#endif
    +
    50
    +
    51#include "fuse.h"
    +
    52
    +
    53static char *progname;
    +
    54
    +
    55static char *xstrdup(const char *s)
    +
    56{
    +
    57 char *t = strdup(s);
    +
    58 if (!t) {
    +
    59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    60 exit(1);
    +
    61 }
    +
    62 return t;
    +
    63}
    +
    64
    +
    65static void *xrealloc(void *oldptr, size_t size)
    +
    66{
    +
    67 void *ptr = realloc(oldptr, size);
    +
    68 if (!ptr) {
    +
    69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    70 exit(1);
    +
    71 }
    +
    72 return ptr;
    +
    73}
    +
    74
    +
    75static void add_arg(char **cmdp, const char *opt)
    +
    76{
    +
    77 size_t optlen = strlen(opt);
    +
    78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
    +
    79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
    +
    80 fprintf(stderr, "%s: argument too long\n", progname);
    +
    81 exit(1);
    +
    82 }
    +
    83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
    +
    84 char *s;
    +
    85 s = cmd + cmdlen;
    +
    86 if (*cmdp)
    +
    87 *s++ = ' ';
    +
    88
    +
    89 *s++ = '\'';
    +
    90 for (; *opt; opt++) {
    +
    91 if (*opt == '\'') {
    +
    92 *s++ = '\'';
    +
    93 *s++ = '\\';
    +
    94 *s++ = '\'';
    +
    95 *s++ = '\'';
    +
    96 } else
    +
    97 *s++ = *opt;
    +
    98 }
    +
    99 *s++ = '\'';
    +
    100 *s = '\0';
    +
    101 *cmdp = cmd;
    +
    102}
    +
    103
    +
    104static char *add_option(const char *opt, char *options)
    +
    105{
    +
    106 int oldlen = options ? strlen(options) : 0;
    +
    107
    +
    108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
    +
    109 if (!oldlen)
    +
    110 strcpy(options, opt);
    +
    111 else {
    +
    112 strcat(options, ",");
    +
    113 strcat(options, opt);
    +
    114 }
    +
    115 return options;
    +
    116}
    +
    117
    +
    118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
    +
    119 const char *options)
    +
    120{
    +
    121 int fuse_fd = -1;
    +
    122 int flags = -1;
    +
    123 int subtype_len = strlen(subtype) + 9;
    +
    124 char* options_copy = xrealloc(NULL, subtype_len);
    +
    125
    +
    126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
    +
    127 options_copy = add_option(options, options_copy);
    +
    128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
    +
    129 if (fuse_fd == -1) {
    +
    130 exit(1);
    +
    131 }
    +
    132
    +
    133 flags = fcntl(fuse_fd, F_GETFD);
    +
    134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
    +
    135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
    +
    136 progname, strerror(errno));
    +
    137 exit(1);
    +
    138 }
    +
    139
    +
    140 return fuse_fd;
    +
    141}
    +
    142
    +
    143#ifdef linux
    +
    144static uint64_t get_capabilities(void)
    +
    145{
    +
    146 /*
    +
    147 * This invokes the capset syscall directly to avoid the libcap
    +
    148 * dependency, which isn't really justified just for this.
    +
    149 */
    +
    150 struct __user_cap_header_struct header = {
    +
    151 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    152 .pid = 0,
    +
    153 };
    +
    154 struct __user_cap_data_struct data[2];
    +
    155 memset(data, 0, sizeof(data));
    +
    156 if (syscall(SYS_capget, &header, data) == -1) {
    +
    157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
    +
    158 progname, strerror(errno));
    +
    159 exit(1);
    +
    160 }
    +
    161
    +
    162 return data[0].effective | ((uint64_t) data[1].effective << 32);
    +
    163}
    +
    164
    +
    165static void set_capabilities(uint64_t caps)
    +
    166{
    +
    167 /*
    +
    168 * This invokes the capset syscall directly to avoid the libcap
    +
    169 * dependency, which isn't really justified just for this.
    +
    170 */
    +
    171 struct __user_cap_header_struct header = {
    +
    172 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    173 .pid = 0,
    +
    174 };
    +
    175 struct __user_cap_data_struct data[2];
    +
    176 memset(data, 0, sizeof(data));
    +
    177 data[0].effective = data[0].permitted = caps;
    +
    178 data[1].effective = data[1].permitted = caps >> 32;
    +
    179 if (syscall(SYS_capset, &header, data) == -1) {
    +
    180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
    +
    181 progname, strerror(errno));
    +
    182 exit(1);
    +
    183 }
    +
    184}
    +
    185
    +
    186static void drop_and_lock_capabilities(void)
    +
    187{
    +
    188 /* Set and lock securebits. */
    +
    189 if (prctl(PR_SET_SECUREBITS,
    +
    190 SECBIT_KEEP_CAPS_LOCKED |
    +
    191 SECBIT_NO_SETUID_FIXUP |
    +
    192 SECBIT_NO_SETUID_FIXUP_LOCKED |
    +
    193 SECBIT_NOROOT |
    +
    194 SECBIT_NOROOT_LOCKED) == -1) {
    +
    195 fprintf(stderr, "%s: Failed to set securebits %s\n",
    +
    196 progname, strerror(errno));
    +
    197 exit(1);
    +
    198 }
    +
    199
    +
    200 /* Clear the capability bounding set. */
    +
    201 int cap;
    +
    202 for (cap = 0; ; cap++) {
    +
    203 int cap_status = prctl(PR_CAPBSET_READ, cap);
    +
    204 if (cap_status == 0) {
    +
    205 continue;
    +
    206 }
    +
    207 if (cap_status == -1 && errno == EINVAL) {
    +
    208 break;
    +
    209 }
    +
    210
    +
    211 if (cap_status != 1) {
    +
    212 fprintf(stderr,
    +
    213 "%s: Failed to get capability %u: %s\n",
    +
    214 progname, cap, strerror(errno));
    +
    215 exit(1);
    +
    216 }
    +
    217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
    +
    218 fprintf(stderr,
    +
    219 "%s: Failed to drop capability %u: %s\n",
    +
    220 progname, cap, strerror(errno));
    +
    221 }
    +
    222 }
    +
    223
    +
    224 /* Drop capabilities. */
    +
    225 set_capabilities(0);
    +
    226
    +
    227 /* Prevent re-acquisition of privileges. */
    +
    228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    +
    229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
    +
    230 progname, strerror(errno));
    +
    231 exit(1);
    +
    232 }
    +
    233}
    +
    234#endif
    +
    235
    +
    236int main(int argc, char *argv[])
    +
    237{
    +
    238 char *type = NULL;
    +
    239 char *source;
    +
    240 char *dup_source = NULL;
    +
    241 const char *mountpoint;
    +
    242 char *basename;
    +
    243 char *options = NULL;
    +
    244 char *command = NULL;
    +
    245 char *setuid_name = NULL;
    +
    246 int i;
    +
    247 int dev = 1;
    +
    248 int suid = 1;
    +
    249 int pass_fuse_fd = 0;
    +
    250 int fuse_fd = 0;
    +
    251 int drop_privileges = 0;
    +
    252 char *dev_fd_mountpoint = NULL;
    +
    253
    +
    254 progname = argv[0];
    +
    255 basename = strrchr(argv[0], '/');
    +
    256 if (basename)
    +
    257 basename++;
    +
    258 else
    +
    259 basename = argv[0];
    +
    260
    +
    261 if (strncmp(basename, "mount.fuse.", 11) == 0)
    +
    262 type = basename + 11;
    +
    263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
    +
    264 type = basename + 14;
    +
    265
    +
    266 if (type && !type[0])
    +
    267 type = NULL;
    +
    268
    +
    269 if (argc < 3) {
    +
    270 fprintf(stderr,
    +
    271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
    +
    272 progname, type ? "source" : "type#[source]");
    +
    273 exit(1);
    +
    274 }
    +
    275
    +
    276 source = argv[1];
    +
    277 if (!source[0])
    +
    278 source = NULL;
    +
    279
    +
    280 mountpoint = argv[2];
    +
    281
    +
    282 for (i = 3; i < argc; i++) {
    +
    283 if (strcmp(argv[i], "-v") == 0) {
    +
    284 continue;
    +
    285 } else if (strcmp(argv[i], "-t") == 0) {
    +
    286 i++;
    +
    287
    +
    288 if (i == argc) {
    +
    289 fprintf(stderr,
    +
    290 "%s: missing argument to option '-t'\n",
    +
    291 progname);
    +
    292 exit(1);
    +
    293 }
    +
    294 type = argv[i];
    +
    295 if (strncmp(type, "fuse.", 5) == 0)
    +
    296 type += 5;
    +
    297 else if (strncmp(type, "fuseblk.", 8) == 0)
    +
    298 type += 8;
    +
    299
    +
    300 if (!type[0]) {
    +
    301 fprintf(stderr,
    +
    302 "%s: empty type given as argument to option '-t'\n",
    +
    303 progname);
    +
    304 exit(1);
    +
    305 }
    +
    306 } else if (strcmp(argv[i], "-o") == 0) {
    +
    307 char *opts;
    +
    308 char *opt;
    +
    309 i++;
    +
    310 if (i == argc)
    +
    311 break;
    +
    312
    +
    313 opts = xstrdup(argv[i]);
    +
    314 opt = strtok(opts, ",");
    +
    315 while (opt) {
    +
    316 int j;
    +
    317 int ignore = 0;
    +
    318 const char *ignore_opts[] = { "",
    +
    319 "user",
    +
    320 "nofail",
    +
    321 "nouser",
    +
    322 "users",
    +
    323 "auto",
    +
    324 "noauto",
    +
    325 "_netdev",
    +
    326 NULL};
    +
    327 if (strncmp(opt, "setuid=", 7) == 0) {
    +
    328 setuid_name = xstrdup(opt + 7);
    +
    329 ignore = 1;
    +
    330 } else if (strcmp(opt,
    +
    331 "drop_privileges") == 0) {
    +
    332 pass_fuse_fd = 1;
    +
    333 drop_privileges = 1;
    +
    334 ignore = 1;
    +
    335 }
    +
    336 for (j = 0; ignore_opts[j]; j++)
    +
    337 if (strcmp(opt, ignore_opts[j]) == 0)
    +
    338 ignore = 1;
    +
    339
    +
    340 if (!ignore) {
    +
    341 if (strcmp(opt, "nodev") == 0)
    +
    342 dev = 0;
    +
    343 else if (strcmp(opt, "nosuid") == 0)
    +
    344 suid = 0;
    +
    345
    +
    346 options = add_option(opt, options);
    +
    347 }
    +
    348 opt = strtok(NULL, ",");
    +
    349 }
    +
    350 free(opts);
    +
    351 }
    +
    352 }
    +
    353
    +
    354 if (drop_privileges) {
    +
    355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
    +
    356 CAP_TO_MASK(CAP_SYS_ADMIN);
    +
    357 if ((get_capabilities() & required_caps) != required_caps) {
    +
    358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
    +
    359 progname, progname);
    +
    360 exit(1);
    +
    361 }
    +
    362 }
    +
    363
    +
    364 if (dev)
    +
    365 options = add_option("dev", options);
    +
    366 if (suid)
    +
    367 options = add_option("suid", options);
    +
    368
    +
    369 if (!type) {
    +
    370 if (source) {
    +
    371 dup_source = xstrdup(source);
    +
    372 type = dup_source;
    +
    373 source = strchr(type, '#');
    +
    374 if (source)
    +
    375 *source++ = '\0';
    +
    376 if (!type[0]) {
    +
    377 fprintf(stderr, "%s: empty filesystem type\n",
    +
    378 progname);
    +
    379 exit(1);
    +
    380 }
    +
    381 } else {
    +
    382 fprintf(stderr, "%s: empty source\n", progname);
    +
    383 exit(1);
    +
    384 }
    +
    385 }
    +
    386
    +
    387 if (setuid_name && setuid_name[0]) {
    +
    388#ifdef linux
    +
    389 if (drop_privileges) {
    +
    390 /*
    +
    391 * Make securebits more permissive before calling
    +
    392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
    +
    393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
    +
    394 * have the side effect of dropping all capabilities,
    +
    395 * and we need to retain CAP_SETPCAP in order to drop
    +
    396 * all privileges before exec().
    +
    397 */
    +
    398 if (prctl(PR_SET_SECUREBITS,
    +
    399 SECBIT_KEEP_CAPS |
    +
    400 SECBIT_NO_SETUID_FIXUP) == -1) {
    +
    401 fprintf(stderr,
    +
    402 "%s: Failed to set securebits %s\n",
    +
    403 progname, strerror(errno));
    +
    404 exit(1);
    +
    405 }
    +
    406 }
    +
    407#endif
    +
    408
    +
    409 struct passwd *pwd = getpwnam(setuid_name);
    +
    410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
    +
    411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
    +
    412 progname, setuid_name, strerror(errno));
    +
    413 exit(1);
    +
    414 }
    +
    415 } else if (!getenv("HOME")) {
    +
    416 /* Hack to make filesystems work in the boot environment */
    +
    417 setenv("HOME", "/root", 0);
    +
    418 }
    +
    419
    +
    420 if (pass_fuse_fd) {
    +
    421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
    +
    422 dev_fd_mountpoint = xrealloc(NULL, 20);
    +
    423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
    +
    424 mountpoint = dev_fd_mountpoint;
    +
    425 }
    +
    426
    +
    427#ifdef linux
    +
    428 if (drop_privileges) {
    +
    429 drop_and_lock_capabilities();
    +
    430 }
    +
    431#endif
    +
    432 add_arg(&command, type);
    +
    433 if (source)
    +
    434 add_arg(&command, source);
    +
    435 add_arg(&command, mountpoint);
    +
    436 if (options) {
    +
    437 add_arg(&command, "-o");
    +
    438 add_arg(&command, options);
    +
    439 }
    +
    440
    +
    441 free(options);
    +
    442 free(dev_fd_mountpoint);
    +
    443 free(dup_source);
    +
    444 free(setuid_name);
    +
    445
    +
    446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
    +
    447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
    +
    448 strerror(errno));
    +
    449
    +
    450 if (pass_fuse_fd)
    +
    451 close(fuse_fd);
    +
    452 free(command);
    +
    453 return 1;
    +
    454}
    + +
    int fuse_open_channel(const char *mountpoint, const char *options)
    Definition helper.c:475
    +
    + + + + diff --git a/doc/html/mount__bsd_8c_source.html b/doc/html/mount__bsd_8c_source.html new file mode 100644 index 0000000..d9e037b --- /dev/null +++ b/doc/html/mount__bsd_8c_source.html @@ -0,0 +1,334 @@ + + + + + + + +libfuse: lib/mount_bsd.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    mount_bsd.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
    +
    4
    +
    5 Architecture specific file system mounting (FreeBSD).
    +
    6
    +
    7 This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 See the file COPYING.LIB.
    +
    9*/
    +
    10
    +
    11#include "fuse_config.h"
    +
    12#include "fuse_i.h"
    +
    13#include "fuse_misc.h"
    +
    14#include "fuse_opt.h"
    +
    15#include "util.h"
    +
    16
    +
    17#include <sys/param.h>
    +
    18#include "fuse_mount_compat.h"
    +
    19
    +
    20#include <sys/wait.h>
    +
    21#include <stdio.h>
    +
    22#include <stdlib.h>
    +
    23#include <unistd.h>
    +
    24#include <stddef.h>
    +
    25#include <fcntl.h>
    +
    26#include <errno.h>
    +
    27#include <string.h>
    +
    28
    +
    29#define FUSERMOUNT_PROG "mount_fusefs"
    +
    30#define FUSE_DEV_TRUNK "/dev/fuse"
    +
    31
    +
    32enum {
    +
    33 KEY_RO,
    +
    34 KEY_KERN
    +
    35};
    +
    36
    +
    37struct mount_opts {
    +
    38 int allow_other;
    +
    39 char *kernel_opts;
    +
    40 unsigned max_read;
    +
    41};
    +
    42
    +
    43#define FUSE_DUAL_OPT_KEY(templ, key) \
    +
    44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
    +
    45
    +
    46static const struct fuse_opt fuse_mount_opts[] = {
    +
    47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
    +
    48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
    +
    49 FUSE_OPT_KEY("-r", KEY_RO),
    +
    50 /* standard FreeBSD mount options */
    +
    51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
    +
    52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
    +
    53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
    +
    54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
    +
    55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
    +
    56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
    +
    57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
    +
    58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
    +
    59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
    +
    60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
    +
    61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
    +
    62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
    +
    63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
    +
    64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
    +
    65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
    +
    66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
    +
    67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
    +
    68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
    +
    69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
    +
    70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
    +
    71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
    +
    72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
    +
    73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
    +
    74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
    +
    75 /* options supported under both Linux and FBSD */
    +
    76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
    +
    77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
    +
    78 FUSE_OPT_KEY("max_read=", KEY_KERN),
    +
    79 FUSE_OPT_KEY("subtype=", KEY_KERN),
    +
    80 /* FBSD FUSE specific mount options */
    +
    81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
    +
    82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
    +
    83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
    +
    84#if __FreeBSD_version >= 1200519
    +
    85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
    +
    86#endif
    +
    87 /* stock FBSD mountopt parsing routine lets anything be negated... */
    +
    88 /*
    +
    89 * Linux specific mount options, but let just the mount util
    +
    90 * handle them
    +
    91 */
    +
    92 FUSE_OPT_KEY("fsname=", KEY_KERN),
    + +
    94};
    +
    95
    +
    96void fuse_mount_version(void)
    +
    97{
    +
    98 system(FUSERMOUNT_PROG " --version");
    +
    99}
    +
    100
    +
    101unsigned get_max_read(struct mount_opts *o)
    +
    102{
    +
    103 return o->max_read;
    +
    104}
    +
    105
    +
    106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
    +
    107 struct fuse_args *outargs)
    +
    108{
    +
    109 (void) outargs;
    +
    110 struct mount_opts *mo = data;
    +
    111
    +
    112 switch (key) {
    +
    113 case KEY_RO:
    +
    114 arg = "ro";
    +
    115 /* fall through */
    +
    116
    +
    117 case KEY_KERN:
    +
    118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
    +
    119 }
    +
    120
    +
    121 /* Pass through unknown options */
    +
    122 return 1;
    +
    123}
    +
    124
    +
    125void fuse_kern_unmount(const char *mountpoint, int fd)
    +
    126{
    +
    127 if (close(fd) < 0)
    +
    128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
    +
    129 if (unmount(mountpoint, MNT_FORCE) < 0)
    +
    130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
    +
    131 mountpoint, strerror(errno));
    +
    132}
    +
    133
    +
    134static int fuse_mount_core(const char *mountpoint, const char *opts)
    +
    135{
    +
    136 const char *mountprog = FUSERMOUNT_PROG;
    +
    137 long fd;
    +
    138 char *fdnam, *dev;
    +
    139 pid_t pid, cpid;
    +
    140 int status;
    +
    141 int err;
    +
    142
    +
    143 fdnam = getenv("FUSE_DEV_FD");
    +
    144
    +
    145 if (fdnam) {
    +
    146 err = libfuse_strtol(fdnam, &fd);
    +
    147 if (err || fd < 0) {
    +
    148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
    +
    149 return -1;
    +
    150 }
    +
    151
    +
    152 goto mount;
    +
    153 }
    +
    154
    +
    155 dev = getenv("FUSE_DEV_NAME");
    +
    156
    +
    157 if (! dev)
    +
    158 dev = (char *)FUSE_DEV_TRUNK;
    +
    159
    +
    160 if ((fd = open(dev, O_RDWR)) < 0) {
    +
    161 perror("fuse: failed to open fuse device");
    +
    162 return -1;
    +
    163 }
    +
    164
    +
    165mount:
    +
    166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
    +
    167 goto out;
    +
    168
    +
    169 pid = fork();
    +
    170 cpid = pid;
    +
    171
    +
    172 if (pid == -1) {
    +
    173 perror("fuse: fork() failed");
    +
    174 close(fd);
    +
    175 return -1;
    +
    176 }
    +
    177
    +
    178 if (pid == 0) {
    +
    179 pid = fork();
    +
    180
    +
    181 if (pid == -1) {
    +
    182 perror("fuse: fork() failed");
    +
    183 close(fd);
    +
    184 _exit(EXIT_FAILURE);
    +
    185 }
    +
    186
    +
    187 if (pid == 0) {
    +
    188 const char *argv[32];
    +
    189 int a = 0;
    +
    190 int ret = -1;
    +
    191
    +
    192 if (! fdnam)
    +
    193 {
    +
    194 ret = asprintf(&fdnam, "%ld", fd);
    +
    195 if(ret == -1)
    +
    196 {
    +
    197 perror("fuse: failed to assemble mount arguments");
    +
    198 close(fd);
    +
    199 _exit(EXIT_FAILURE);
    +
    200 }
    +
    201 }
    +
    202
    +
    203 argv[a++] = mountprog;
    +
    204 if (opts) {
    +
    205 argv[a++] = "-o";
    +
    206 argv[a++] = opts;
    +
    207 }
    +
    208 argv[a++] = fdnam;
    +
    209 argv[a++] = mountpoint;
    +
    210 argv[a++] = NULL;
    +
    211 execvp(mountprog, (char **) argv);
    +
    212 perror("fuse: failed to exec mount program");
    +
    213 free(fdnam);
    +
    214 _exit(EXIT_FAILURE);
    +
    215 }
    +
    216
    +
    217 _exit(EXIT_SUCCESS);
    +
    218 }
    +
    219
    +
    220 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
    +
    221 perror("fuse: failed to mount file system");
    +
    222 if (close(fd) < 0)
    +
    223 perror("fuse: closing FD");
    +
    224 return -1;
    +
    225 }
    +
    226
    +
    227out:
    +
    228 return fd;
    +
    229}
    +
    230
    +
    231struct mount_opts *parse_mount_opts(struct fuse_args *args)
    +
    232{
    +
    233 struct mount_opts *mo;
    +
    234
    +
    235 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
    +
    236 if (mo == NULL)
    +
    237 return NULL;
    +
    238
    +
    239 memset(mo, 0, sizeof(struct mount_opts));
    +
    240
    +
    241 if (args &&
    +
    242 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
    +
    243 goto err_out;
    +
    244
    +
    245 return mo;
    +
    246
    +
    247err_out:
    +
    248 destroy_mount_opts(mo);
    +
    249 return NULL;
    +
    250}
    +
    251
    +
    252void destroy_mount_opts(struct mount_opts *mo)
    +
    253{
    +
    254 free(mo->kernel_opts);
    +
    255 free(mo);
    +
    256}
    +
    257
    +
    258int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
    +
    259{
    +
    260 /* mount util should not try to spawn the daemon */
    +
    261 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
    +
    262 /* to notify the mount util it's called from lib */
    +
    263 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
    +
    264
    +
    265 return fuse_mount_core(mountpoint, mo->kernel_opts);
    +
    266}
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...)
    Definition fuse_log.c:77
    + +
    #define FUSE_OPT_KEY(templ, key)
    Definition fuse_opt.h:98
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    int fuse_opt_add_opt(char **opts, const char *opt)
    Definition fuse_opt.c:139
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    + + + + diff --git a/doc/html/mount__util_8c_source.html b/doc/html/mount__util_8c_source.html new file mode 100644 index 0000000..3e8c7d1 --- /dev/null +++ b/doc/html/mount__util_8c_source.html @@ -0,0 +1,433 @@ + + + + + + + +libfuse: lib/mount_util.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    mount_util.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 Architecture-independent mounting code.
    +
    6
    +
    7 This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 See the file COPYING.LIB.
    +
    9*/
    +
    10
    +
    11#include "fuse_config.h"
    +
    12#include "mount_util.h"
    +
    13
    +
    14#include <stdio.h>
    +
    15#include <unistd.h>
    +
    16#include <stdlib.h>
    +
    17#include <string.h>
    +
    18#include <signal.h>
    +
    19#include <dirent.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <limits.h>
    +
    23#include <paths.h>
    +
    24#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
    +
    25#include <mntent.h>
    +
    26#else
    +
    27#define IGNORE_MTAB
    +
    28#endif
    +
    29#include <sys/stat.h>
    +
    30#include <sys/wait.h>
    +
    31
    +
    32#include "fuse_mount_compat.h"
    +
    33
    +
    34#include <sys/param.h>
    +
    35
    +
    36#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
    +
    37#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
    +
    38#endif
    +
    39
    +
    40#ifdef IGNORE_MTAB
    +
    41#define mtab_needs_update(mnt) 0
    +
    42#else
    +
    43static int mtab_needs_update(const char *mnt)
    +
    44{
    +
    45 int res;
    +
    46 struct stat stbuf;
    +
    47
    +
    48 /* If mtab is within new mount, don't touch it */
    +
    49 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
    +
    50 _PATH_MOUNTED[strlen(mnt)] == '/')
    +
    51 return 0;
    +
    52
    +
    53 /*
    +
    54 * Skip mtab update if /etc/mtab:
    +
    55 *
    +
    56 * - doesn't exist,
    +
    57 * - is on a read-only filesystem.
    +
    58 */
    +
    59 res = lstat(_PATH_MOUNTED, &stbuf);
    +
    60 if (res == -1) {
    +
    61 if (errno == ENOENT)
    +
    62 return 0;
    +
    63 } else {
    +
    64 uid_t ruid;
    +
    65 int err;
    +
    66
    +
    67 ruid = getuid();
    +
    68 if (ruid != 0)
    +
    69 setreuid(0, -1);
    +
    70
    +
    71 res = access(_PATH_MOUNTED, W_OK);
    +
    72 err = (res == -1) ? errno : 0;
    +
    73 if (ruid != 0)
    +
    74 setreuid(ruid, -1);
    +
    75
    +
    76 if (err == EROFS)
    +
    77 return 0;
    +
    78 }
    +
    79
    +
    80 return 1;
    +
    81}
    +
    82#endif /* IGNORE_MTAB */
    +
    83
    +
    84static int add_mount(const char *progname, const char *fsname,
    +
    85 const char *mnt, const char *type, const char *opts)
    +
    86{
    +
    87 int res;
    +
    88 int status;
    +
    89 sigset_t blockmask;
    +
    90 sigset_t oldmask;
    +
    91
    +
    92 sigemptyset(&blockmask);
    +
    93 sigaddset(&blockmask, SIGCHLD);
    +
    94 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
    +
    95 if (res == -1) {
    +
    96 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
    +
    97 return -1;
    +
    98 }
    +
    99
    +
    100 res = fork();
    +
    101 if (res == -1) {
    +
    102 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
    +
    103 goto out_restore;
    +
    104 }
    +
    105 if (res == 0) {
    +
    106 char *env = NULL;
    +
    107
    +
    108 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    109
    +
    110 if(setuid(geteuid()) == -1) {
    +
    111 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
    +
    112 res = -1;
    +
    113 goto out_restore;
    +
    114 }
    +
    115
    +
    116 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
    +
    117 "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
    +
    118 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
    +
    119 progname, strerror(errno));
    +
    120 exit(1);
    +
    121 }
    +
    122 res = waitpid(res, &status, 0);
    +
    123 if (res == -1)
    +
    124 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
    +
    125
    +
    126 if (status != 0)
    +
    127 res = -1;
    +
    128
    +
    129 out_restore:
    +
    130 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    131
    +
    132 return res;
    +
    133}
    +
    134
    +
    135int fuse_mnt_add_mount(const char *progname, const char *fsname,
    +
    136 const char *mnt, const char *type, const char *opts)
    +
    137{
    +
    138 if (!mtab_needs_update(mnt))
    +
    139 return 0;
    +
    140
    +
    141 return add_mount(progname, fsname, mnt, type, opts);
    +
    142}
    +
    143
    +
    144static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
    +
    145{
    +
    146 int res;
    +
    147 int status;
    +
    148 sigset_t blockmask;
    +
    149 sigset_t oldmask;
    +
    150
    +
    151 sigemptyset(&blockmask);
    +
    152 sigaddset(&blockmask, SIGCHLD);
    +
    153 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
    +
    154 if (res == -1) {
    +
    155 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
    +
    156 return -1;
    +
    157 }
    +
    158
    +
    159 res = fork();
    +
    160 if (res == -1) {
    +
    161 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
    +
    162 goto out_restore;
    +
    163 }
    +
    164 if (res == 0) {
    +
    165 char *env = NULL;
    +
    166
    +
    167 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    168
    +
    169 if(setuid(geteuid()) == -1) {
    +
    170 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
    +
    171 res = -1;
    +
    172 goto out_restore;
    +
    173 }
    +
    174
    +
    175 if (lazy) {
    +
    176 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
    +
    177 "-l", NULL, &env);
    +
    178 } else {
    +
    179 execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
    +
    180 NULL, &env);
    +
    181 }
    +
    182 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
    +
    183 progname, strerror(errno));
    +
    184 exit(1);
    +
    185 }
    +
    186 res = waitpid(res, &status, 0);
    +
    187 if (res == -1)
    +
    188 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
    +
    189
    +
    190 if (status != 0) {
    +
    191 res = -1;
    +
    192 }
    +
    193
    +
    194 out_restore:
    +
    195 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    196 return res;
    +
    197
    +
    198}
    +
    199
    +
    200int fuse_mnt_umount(const char *progname, const char *abs_mnt,
    +
    201 const char *rel_mnt, int lazy)
    +
    202{
    +
    203 int res;
    +
    204
    +
    205 if (!mtab_needs_update(abs_mnt)) {
    +
    206 res = umount2(rel_mnt, lazy ? 2 : 0);
    +
    207 if (res == -1)
    +
    208 fprintf(stderr, "%s: failed to unmount %s: %s\n",
    +
    209 progname, abs_mnt, strerror(errno));
    +
    210 return res;
    +
    211 }
    +
    212
    +
    213 return exec_umount(progname, rel_mnt, lazy);
    +
    214}
    +
    215
    +
    216static int remove_mount(const char *progname, const char *mnt)
    +
    217{
    +
    218 int res;
    +
    219 int status;
    +
    220 sigset_t blockmask;
    +
    221 sigset_t oldmask;
    +
    222
    +
    223 sigemptyset(&blockmask);
    +
    224 sigaddset(&blockmask, SIGCHLD);
    +
    225 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
    +
    226 if (res == -1) {
    +
    227 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
    +
    228 return -1;
    +
    229 }
    +
    230
    +
    231 res = fork();
    +
    232 if (res == -1) {
    +
    233 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
    +
    234 goto out_restore;
    +
    235 }
    +
    236 if (res == 0) {
    +
    237 char *env = NULL;
    +
    238
    +
    239 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    240
    +
    241 if(setuid(geteuid()) == -1) {
    +
    242 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
    +
    243 res = -1;
    +
    244 goto out_restore;
    +
    245 }
    +
    246
    +
    247 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
    +
    248 "--fake", mnt, NULL, &env);
    +
    249 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
    +
    250 progname, strerror(errno));
    +
    251 exit(1);
    +
    252 }
    +
    253 res = waitpid(res, &status, 0);
    +
    254 if (res == -1)
    +
    255 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
    +
    256
    +
    257 if (status != 0)
    +
    258 res = -1;
    +
    259
    +
    260 out_restore:
    +
    261 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    262 return res;
    +
    263}
    +
    264
    +
    265int fuse_mnt_remove_mount(const char *progname, const char *mnt)
    +
    266{
    +
    267 if (!mtab_needs_update(mnt))
    +
    268 return 0;
    +
    269
    +
    270 return remove_mount(progname, mnt);
    +
    271}
    +
    272
    +
    273char *fuse_mnt_resolve_path(const char *progname, const char *orig)
    +
    274{
    +
    275 char buf[PATH_MAX];
    +
    276 char *copy;
    +
    277 char *dst;
    +
    278 char *end;
    +
    279 char *lastcomp;
    +
    280 const char *toresolv;
    +
    281
    +
    282 if (!orig[0]) {
    +
    283 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
    +
    284 orig);
    +
    285 return NULL;
    +
    286 }
    +
    287
    +
    288 copy = strdup(orig);
    +
    289 if (copy == NULL) {
    +
    290 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    291 return NULL;
    +
    292 }
    +
    293
    +
    294 toresolv = copy;
    +
    295 lastcomp = NULL;
    +
    296 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
    +
    297 if (end[0] != '/') {
    +
    298 char *tmp;
    +
    299 end[1] = '\0';
    +
    300 tmp = strrchr(copy, '/');
    +
    301 if (tmp == NULL) {
    +
    302 lastcomp = copy;
    +
    303 toresolv = ".";
    +
    304 } else {
    +
    305 lastcomp = tmp + 1;
    +
    306 if (tmp == copy)
    +
    307 toresolv = "/";
    +
    308 }
    +
    309 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
    +
    310 lastcomp = NULL;
    +
    311 toresolv = copy;
    +
    312 }
    +
    313 else if (tmp)
    +
    314 tmp[0] = '\0';
    +
    315 }
    +
    316 if (realpath(toresolv, buf) == NULL) {
    +
    317 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
    +
    318 strerror(errno));
    +
    319 free(copy);
    +
    320 return NULL;
    +
    321 }
    +
    322 if (lastcomp == NULL)
    +
    323 dst = strdup(buf);
    +
    324 else {
    +
    325 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
    +
    326 if (dst) {
    +
    327 unsigned buflen = strlen(buf);
    +
    328 if (buflen && buf[buflen-1] == '/')
    +
    329 sprintf(dst, "%s%s", buf, lastcomp);
    +
    330 else
    +
    331 sprintf(dst, "%s/%s", buf, lastcomp);
    +
    332 }
    +
    333 }
    +
    334 free(copy);
    +
    335 if (dst == NULL)
    +
    336 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    337 return dst;
    +
    338}
    +
    339
    +
    340int fuse_mnt_check_fuseblk(void)
    +
    341{
    +
    342 char buf[256];
    +
    343 FILE *f = fopen("/proc/filesystems", "r");
    +
    344 if (!f)
    +
    345 return 1;
    +
    346
    +
    347 while (fgets(buf, sizeof(buf), f))
    +
    348 if (strstr(buf, "fuseblk\n")) {
    +
    349 fclose(f);
    +
    350 return 1;
    +
    351 }
    +
    352
    +
    353 fclose(f);
    +
    354 return 0;
    +
    355}
    +
    356
    +
    357int fuse_mnt_parse_fuse_fd(const char *mountpoint)
    +
    358{
    +
    359 int fd = -1;
    +
    360 int len = 0;
    +
    361
    +
    362 if (mountpoint == NULL) {
    +
    363 fprintf(stderr, "Invalid null-ptr mount-point!\n");
    +
    364 return -1;
    +
    365 }
    +
    366
    +
    367 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
    +
    368 len == strlen(mountpoint)) {
    +
    369 return fd;
    +
    370 }
    +
    371
    +
    372 return -1;
    +
    373}
    +
    + + + + diff --git a/doc/html/mount__util_8h_source.html b/doc/html/mount__util_8h_source.html new file mode 100644 index 0000000..4f965f7 --- /dev/null +++ b/doc/html/mount__util_8h_source.html @@ -0,0 +1,78 @@ + + + + + + + +libfuse: lib/mount_util.h Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    mount_util.h
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU LGPLv2.
    +
    6 See the file COPYING.LIB.
    +
    7*/
    +
    8
    +
    9#include <sys/types.h>
    +
    10
    +
    11int fuse_mnt_add_mount(const char *progname, const char *fsname,
    +
    12 const char *mnt, const char *type, const char *opts);
    +
    13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
    +
    14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
    +
    15 const char *rel_mnt, int lazy);
    +
    16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
    +
    17int fuse_mnt_check_fuseblk(void);
    +
    18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
    +
    + + + + diff --git a/doc/html/nav_f.png b/doc/html/nav_f.png new file mode 100644 index 0000000000000000000000000000000000000000..72a58a529ed3a9ed6aa0c51a79cf207e026deee2 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^j6iI`!2~2XGqLUlQVE_ejv*C{Z|{2ZH7M}7UYxc) zn!W8uqtnIQ>_z8U literal 0 HcmV?d00001 diff --git a/doc/html/nav_fd.png b/doc/html/nav_fd.png new file mode 100644 index 0000000000000000000000000000000000000000..032fbdd4c54f54fa9a2e6423b94ef4b2ebdfaceb GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^j6iI`!2~2XGqLUlQU#tajv*C{Z|C~*H7f|XvG1G8 zt7aS*L7xwMeS}!z6R#{C5tIw-s~AJ==F^i}x3XyJseHR@yF& zerFf(Zf;Dd{+(0lDIROL@Sj-Ju2JQ8&-n%4%q?>|^bShc&lR?}7HeMo@BDl5N(aHY Uj$gdr1MOz;boFyt=akR{0D!zeaR2}S literal 0 HcmV?d00001 diff --git a/doc/html/nav_g.png b/doc/html/nav_g.png new file mode 100644 index 0000000000000000000000000000000000000000..2093a237a94f6c83e19ec6e5fd42f7ddabdafa81 GIT binary patch literal 95 zcmeAS@N?(olHy`uVBq!ia0vp^j6lrB!3HFm1ilyoDK$?Q$B+ufw|5PB85lU25BhtE tr?otc=hd~V+ws&_A@j8Fiv!KF$B+ufw|5=67#uj90@pIL wZ=Q8~_Ju`#59=RjDrmm`tMD@M=!-l18IR?&vFVdQ&MBb@0HFXL6W-eg#Jd_@e6*DPn)w;=|1H}Zvm9l6xXXB%>yL=NQU;mg M>FVdQ&MBb@0Bdt1Qvd(} literal 0 HcmV?d00001 diff --git a/doc/html/notify__inval__entry_8c.html b/doc/html/notify__inval__entry_8c.html new file mode 100644 index 0000000..b0457e4 --- /dev/null +++ b/doc/html/notify__inval__entry_8c.html @@ -0,0 +1,475 @@ + + + + + + + +libfuse: example/notify_inval_entry.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    notify_inval_entry.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <assert.h>
    +#include <signal.h>
    +#include <stddef.h>
    +#include <sys/stat.h>
    +#include <unistd.h>
    +#include <pthread.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

    +

    It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

    +

    To see the effect, first start the file system with the --no-notify

    $ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
    +

    Observe that ls always prints the correct directory contents (since readdir output is not cached)::

    $ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
    +Time_is_15h_48m_33s  current_time
    +Time_is_15h_48m_34s  current_time
    +Time_is_15h_48m_35s  current_time
    +

    However, if you try to access a file by name the kernel will report that it still exists:

    $ file=$(ls mnt/); echo $file
    +Time_is_15h_50m_09s
    +$ sleep 5; stat mnt/$file
    +  File: ‘mnt/Time_is_15h_50m_09s’
    +  Size: 32                Blocks: 0          IO Block: 4096   regular file
    +Device: 2ah/42d     Inode: 3           Links: 1
    +Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
    +Access: 1969-12-31 16:00:00.000000000 -0800
    +Modify: 1969-12-31 16:00:00.000000000 -0800
    +Change: 1969-12-31 16:00:00.000000000 -0800
    + Birth: -
    +

    Only once the kernel cache timeout has been reached will the stat call fail:

    $ sleep 30; stat mnt/$file
    +stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
    +

    In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

    $ notify_inval_entry --update-interval=1 --timeout=30 mnt/
    +$ file=$(ls mnt/); stat mnt/$file
    +  File: ‘mnt/Time_is_20h_42m_11s’
    +  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
    +Device: 2ah/42d     Inode: 2           Links: 1
    +Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
    +Access: 1969-12-31 16:00:00.000000000 -0800
    +Modify: 1969-12-31 16:00:00.000000000 -0800
    +Change: 1969-12-31 16:00:00.000000000 -0800
    + Birth: -
    +$ sleep 1; stat mnt/$file
    +stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
    +

    To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

    +

    +Compilation

    +
    gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <assert.h>
    +
    #include <signal.h>
    +
    #include <stddef.h>
    +
    #include <sys/stat.h>
    +
    #include <unistd.h>
    +
    #include <pthread.h>
    +
    +
    #define MAX_STR_LEN 128
    +
    static char file_name[MAX_STR_LEN];
    +
    static fuse_ino_t file_ino = 2;
    +
    static int lookup_cnt = 0;
    +
    static pthread_t main_thread;
    +
    +
    /* Command line parsing */
    +
    struct options {
    +
    int no_notify;
    +
    float timeout;
    +
    int update_interval;
    +
    int only_expire;
    +
    };
    +
    static struct options options = {
    +
    .timeout = 5,
    +
    .no_notify = 0,
    +
    .update_interval = 1,
    +
    .only_expire = 0,
    +
    };
    +
    +
    #define OPTION(t, p) \
    +
    { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--no-notify", no_notify),
    +
    OPTION("--update-interval=%d", update_interval),
    +
    OPTION("--timeout=%f", timeout),
    +
    OPTION("--only-expire", only_expire),
    + +
    };
    +
    +
    static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    stbuf->st_ino = ino;
    +
    if (ino == FUSE_ROOT_ID) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 1;
    +
    }
    +
    +
    else if (ino == file_ino) {
    +
    stbuf->st_mode = S_IFREG | 0000;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = 0;
    +
    }
    +
    +
    else
    +
    return -1;
    +
    +
    return 0;
    +
    }
    +
    +
    static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
    +
    (void)userdata;
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    const char *name) {
    +
    struct fuse_entry_param e;
    +
    memset(&e, 0, sizeof(e));
    +
    +
    if (parent != FUSE_ROOT_ID)
    +
    goto err_out;
    +
    else if (strcmp(name, file_name) == 0) {
    +
    e.ino = file_ino;
    +
    lookup_cnt++;
    +
    } else
    +
    goto err_out;
    +
    +
    e.attr_timeout = options.timeout;
    +
    e.entry_timeout = options.timeout;
    +
    if (tfs_stat(e.ino, &e.attr) != 0)
    +
    goto err_out;
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    err_out:
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    +
    static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
    +
    uint64_t nlookup) {
    +
    (void) req;
    +
    if(ino == file_ino)
    +
    lookup_cnt -= nlookup;
    +
    else
    +
    assert(ino == FUSE_ROOT_ID);
    + +
    }
    +
    +
    static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi) {
    +
    struct stat stbuf;
    +
    +
    (void) fi;
    +
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    if (tfs_stat(ino, &stbuf) != 0)
    +
    fuse_reply_err(req, ENOENT);
    +
    else
    +
    fuse_reply_attr(req, &stbuf, options.timeout);
    +
    }
    +
    +
    struct dirbuf {
    +
    char *p;
    +
    size_t size;
    +
    };
    +
    +
    static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    fuse_ino_t ino) {
    +
    struct stat stbuf;
    +
    size_t oldsize = b->size;
    +
    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    b->p = (char *) realloc(b->p, b->size);
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    stbuf.st_ino = ino;
    +
    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    b->size);
    +
    }
    +
    +
    #define min(x, y) ((x) < (y) ? (x) : (y))
    +
    +
    static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    off_t off, size_t maxsize) {
    +
    if (off < bufsize)
    +
    return fuse_reply_buf(req, buf + off,
    +
    min(bufsize - off, maxsize));
    +
    else
    +
    return fuse_reply_buf(req, NULL, 0);
    +
    }
    +
    +
    static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t off, struct fuse_file_info *fi) {
    +
    (void) fi;
    +
    +
    if (ino != FUSE_ROOT_ID)
    +
    fuse_reply_err(req, ENOTDIR);
    +
    else {
    +
    struct dirbuf b;
    +
    +
    memset(&b, 0, sizeof(b));
    +
    dirbuf_add(req, &b, file_name, file_ino);
    +
    reply_buf_limited(req, b.p, b.size, off, size);
    +
    free(b.p);
    +
    }
    +
    }
    +
    +
    static const struct fuse_lowlevel_ops tfs_oper = {
    +
    .init = tfs_init,
    +
    .lookup = tfs_lookup,
    +
    .getattr = tfs_getattr,
    +
    .readdir = tfs_readdir,
    +
    .forget = tfs_forget,
    +
    };
    +
    +
    static void update_fs(void) {
    +
    time_t t;
    +
    struct tm *now;
    +
    ssize_t ret;
    +
    +
    t = time(NULL);
    +
    now = localtime(&t);
    +
    assert(now != NULL);
    +
    +
    ret = strftime(file_name, MAX_STR_LEN,
    +
    "Time_is_%Hh_%Mm_%Ss", now);
    +
    assert(ret != 0);
    +
    }
    +
    +
    static void* update_fs_loop(void *data) {
    +
    struct fuse_session *se = (struct fuse_session*) data;
    +
    char *old_name;
    +
    +
    +
    while(!fuse_session_exited(se)) {
    +
    old_name = strdup(file_name);
    +
    update_fs();
    +
    +
    if (!options.no_notify && lookup_cnt) {
    +
    if(options.only_expire) { // expire entry
    + +
    (se, FUSE_ROOT_ID, old_name, strlen(old_name));
    +
    +
    // no kernel support
    +
    if (ret == -ENOSYS) {
    +
    printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
    +
    printf("Exiting...\n");
    +
    + +
    // Make sure to exit now, rather than on next request from userspace
    +
    pthread_kill(main_thread, SIGPIPE);
    +
    +
    break;
    +
    }
    +
    // 1) ret == 0: successful expire of an existing entry
    +
    // 2) ret == -ENOENT: kernel has already expired the entry /
    +
    // entry does not exist anymore in the kernel
    +
    assert(ret == 0 || ret == -ENOENT);
    +
    } else { // invalidate entry
    + +
    (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
    +
    }
    +
    }
    +
    free(old_name);
    +
    sleep(options.update_interval);
    +
    }
    +
    return NULL;
    +
    }
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --timeout=<secs> Timeout for kernel caches\n"
    +
    " --update-interval=<secs> Update-rate of file system contents\n"
    +
    " --no-notify Disable kernel notifications\n"
    +
    " --only-expire Expire entries instead of invalidating them\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[]) {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    pthread_t updater;
    +
    int ret = -1;
    +
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    return 1;
    +
    if (opts.show_help) {
    +
    show_help(argv[0]);
    + + +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    /* Initial contents */
    +
    update_fs();
    +
    +
    se = fuse_session_new(&args, &tfs_oper,
    +
    sizeof(tfs_oper), &se);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    // Needed to ensure that the main thread continues/restarts processing as soon
    +
    // as the fuse session ends (immediately after calling fuse_session_exit() )
    +
    // and not only on the next request from userspace
    +
    main_thread = pthread_self();
    +
    +
    /* Start thread to update file contents */
    +
    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    if (ret != 0) {
    +
    fprintf(stderr, "pthread_create failed with %s\n",
    +
    strerror(ret));
    +
    goto err_out3;
    +
    }
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread) {
    +
    ret = fuse_session_loop(se);
    +
    } else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    free(opts.mountpoint);
    + +
    +
    return ret ? 1 : 0;
    +
    }
    +
    +
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_exited(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    +

    Definition in file notify_inval_entry.c.

    +
    + + + + diff --git a/doc/html/notify__inval__entry_8c_source.html b/doc/html/notify__inval__entry_8c_source.html new file mode 100644 index 0000000..a444442 --- /dev/null +++ b/doc/html/notify__inval__entry_8c_source.html @@ -0,0 +1,425 @@ + + + + + + + +libfuse: example/notify_inval_entry.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    notify_inval_entry.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    79#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    80
    +
    81#include <fuse_lowlevel.h>
    +
    82#include <stdio.h>
    +
    83#include <stdlib.h>
    +
    84#include <string.h>
    +
    85#include <errno.h>
    +
    86#include <fcntl.h>
    +
    87#include <assert.h>
    +
    88#include <signal.h>
    +
    89#include <stddef.h>
    +
    90#include <sys/stat.h>
    +
    91#include <unistd.h>
    +
    92#include <pthread.h>
    +
    93
    +
    94#define MAX_STR_LEN 128
    +
    95static char file_name[MAX_STR_LEN];
    +
    96static fuse_ino_t file_ino = 2;
    +
    97static int lookup_cnt = 0;
    +
    98static pthread_t main_thread;
    +
    99
    +
    100/* Command line parsing */
    +
    101struct options {
    +
    102 int no_notify;
    +
    103 float timeout;
    +
    104 int update_interval;
    +
    105 int only_expire;
    +
    106};
    +
    107static struct options options = {
    +
    108 .timeout = 5,
    +
    109 .no_notify = 0,
    +
    110 .update_interval = 1,
    +
    111 .only_expire = 0,
    +
    112};
    +
    113
    +
    114#define OPTION(t, p) \
    +
    115 { t, offsetof(struct options, p), 1 }
    +
    116static const struct fuse_opt option_spec[] = {
    +
    117 OPTION("--no-notify", no_notify),
    +
    118 OPTION("--update-interval=%d", update_interval),
    +
    119 OPTION("--timeout=%f", timeout),
    +
    120 OPTION("--only-expire", only_expire),
    + +
    122};
    +
    123
    +
    124static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    125 stbuf->st_ino = ino;
    +
    126 if (ino == FUSE_ROOT_ID) {
    +
    127 stbuf->st_mode = S_IFDIR | 0755;
    +
    128 stbuf->st_nlink = 1;
    +
    129 }
    +
    130
    +
    131 else if (ino == file_ino) {
    +
    132 stbuf->st_mode = S_IFREG | 0000;
    +
    133 stbuf->st_nlink = 1;
    +
    134 stbuf->st_size = 0;
    +
    135 }
    +
    136
    +
    137 else
    +
    138 return -1;
    +
    139
    +
    140 return 0;
    +
    141}
    +
    142
    +
    143static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
    +
    144 (void)userdata;
    +
    145
    +
    146 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    147 conn->no_interrupt = 1;
    +
    148}
    +
    149
    +
    150static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    151 const char *name) {
    +
    152 struct fuse_entry_param e;
    +
    153 memset(&e, 0, sizeof(e));
    +
    154
    +
    155 if (parent != FUSE_ROOT_ID)
    +
    156 goto err_out;
    +
    157 else if (strcmp(name, file_name) == 0) {
    +
    158 e.ino = file_ino;
    +
    159 lookup_cnt++;
    +
    160 } else
    +
    161 goto err_out;
    +
    162
    +
    163 e.attr_timeout = options.timeout;
    +
    164 e.entry_timeout = options.timeout;
    +
    165 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    166 goto err_out;
    +
    167 fuse_reply_entry(req, &e);
    +
    168 return;
    +
    169
    +
    170err_out:
    +
    171 fuse_reply_err(req, ENOENT);
    +
    172}
    +
    173
    +
    174static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
    +
    175 uint64_t nlookup) {
    +
    176 (void) req;
    +
    177 if(ino == file_ino)
    +
    178 lookup_cnt -= nlookup;
    +
    179 else
    +
    180 assert(ino == FUSE_ROOT_ID);
    +
    181 fuse_reply_none(req);
    +
    182}
    +
    183
    +
    184static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    185 struct fuse_file_info *fi) {
    +
    186 struct stat stbuf;
    +
    187
    +
    188 (void) fi;
    +
    189
    +
    190 memset(&stbuf, 0, sizeof(stbuf));
    +
    191 if (tfs_stat(ino, &stbuf) != 0)
    +
    192 fuse_reply_err(req, ENOENT);
    +
    193 else
    +
    194 fuse_reply_attr(req, &stbuf, options.timeout);
    +
    195}
    +
    196
    +
    197struct dirbuf {
    +
    198 char *p;
    +
    199 size_t size;
    +
    200};
    +
    201
    +
    202static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    203 fuse_ino_t ino) {
    +
    204 struct stat stbuf;
    +
    205 size_t oldsize = b->size;
    +
    206 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    207 b->p = (char *) realloc(b->p, b->size);
    +
    208 memset(&stbuf, 0, sizeof(stbuf));
    +
    209 stbuf.st_ino = ino;
    +
    210 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    211 b->size);
    +
    212}
    +
    213
    +
    214#define min(x, y) ((x) < (y) ? (x) : (y))
    +
    215
    +
    216static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    217 off_t off, size_t maxsize) {
    +
    218 if (off < bufsize)
    +
    219 return fuse_reply_buf(req, buf + off,
    +
    220 min(bufsize - off, maxsize));
    +
    221 else
    +
    222 return fuse_reply_buf(req, NULL, 0);
    +
    223}
    +
    224
    +
    225static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    226 off_t off, struct fuse_file_info *fi) {
    +
    227 (void) fi;
    +
    228
    +
    229 if (ino != FUSE_ROOT_ID)
    +
    230 fuse_reply_err(req, ENOTDIR);
    +
    231 else {
    +
    232 struct dirbuf b;
    +
    233
    +
    234 memset(&b, 0, sizeof(b));
    +
    235 dirbuf_add(req, &b, file_name, file_ino);
    +
    236 reply_buf_limited(req, b.p, b.size, off, size);
    +
    237 free(b.p);
    +
    238 }
    +
    239}
    +
    240
    +
    241static const struct fuse_lowlevel_ops tfs_oper = {
    +
    242 .init = tfs_init,
    +
    243 .lookup = tfs_lookup,
    +
    244 .getattr = tfs_getattr,
    +
    245 .readdir = tfs_readdir,
    +
    246 .forget = tfs_forget,
    +
    247};
    +
    248
    +
    249static void update_fs(void) {
    +
    250 time_t t;
    +
    251 struct tm *now;
    +
    252 ssize_t ret;
    +
    253
    +
    254 t = time(NULL);
    +
    255 now = localtime(&t);
    +
    256 assert(now != NULL);
    +
    257
    +
    258 ret = strftime(file_name, MAX_STR_LEN,
    +
    259 "Time_is_%Hh_%Mm_%Ss", now);
    +
    260 assert(ret != 0);
    +
    261}
    +
    262
    +
    263static void* update_fs_loop(void *data) {
    +
    264 struct fuse_session *se = (struct fuse_session*) data;
    +
    265 char *old_name;
    +
    266
    +
    267
    +
    268 while(!fuse_session_exited(se)) {
    +
    269 old_name = strdup(file_name);
    +
    270 update_fs();
    +
    271
    +
    272 if (!options.no_notify && lookup_cnt) {
    +
    273 if(options.only_expire) { // expire entry
    + +
    275 (se, FUSE_ROOT_ID, old_name, strlen(old_name));
    +
    276
    +
    277 // no kernel support
    +
    278 if (ret == -ENOSYS) {
    +
    279 printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
    +
    280 printf("Exiting...\n");
    +
    281
    + +
    283 // Make sure to exit now, rather than on next request from userspace
    +
    284 pthread_kill(main_thread, SIGPIPE);
    +
    285
    +
    286 break;
    +
    287 }
    +
    288 // 1) ret == 0: successful expire of an existing entry
    +
    289 // 2) ret == -ENOENT: kernel has already expired the entry /
    +
    290 // entry does not exist anymore in the kernel
    +
    291 assert(ret == 0 || ret == -ENOENT);
    +
    292 } else { // invalidate entry
    + +
    294 (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
    +
    295 }
    +
    296 }
    +
    297 free(old_name);
    +
    298 sleep(options.update_interval);
    +
    299 }
    +
    300 return NULL;
    +
    301}
    +
    302
    +
    303static void show_help(const char *progname)
    +
    304{
    +
    305 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    306 printf("File-system specific options:\n"
    +
    307 " --timeout=<secs> Timeout for kernel caches\n"
    +
    308 " --update-interval=<secs> Update-rate of file system contents\n"
    +
    309 " --no-notify Disable kernel notifications\n"
    +
    310 " --only-expire Expire entries instead of invalidating them\n"
    +
    311 "\n");
    +
    312}
    +
    313
    +
    314int main(int argc, char *argv[]) {
    +
    315 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    316 struct fuse_session *se;
    +
    317 struct fuse_cmdline_opts opts;
    +
    318 struct fuse_loop_config *config;
    +
    319 pthread_t updater;
    +
    320 int ret = -1;
    +
    321
    +
    322 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    323 return 1;
    +
    324
    +
    325 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    326 return 1;
    +
    327 if (opts.show_help) {
    +
    328 show_help(argv[0]);
    + + +
    331 ret = 0;
    +
    332 goto err_out1;
    +
    333 } else if (opts.show_version) {
    +
    334 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    336 ret = 0;
    +
    337 goto err_out1;
    +
    338 }
    +
    339
    +
    340 /* Initial contents */
    +
    341 update_fs();
    +
    342
    +
    343 se = fuse_session_new(&args, &tfs_oper,
    +
    344 sizeof(tfs_oper), &se);
    +
    345 if (se == NULL)
    +
    346 goto err_out1;
    +
    347
    +
    348 if (fuse_set_signal_handlers(se) != 0)
    +
    349 goto err_out2;
    +
    350
    +
    351 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    352 goto err_out3;
    +
    353
    +
    354 fuse_daemonize(opts.foreground);
    +
    355
    +
    356 // Needed to ensure that the main thread continues/restarts processing as soon
    +
    357 // as the fuse session ends (immediately after calling fuse_session_exit() )
    +
    358 // and not only on the next request from userspace
    +
    359 main_thread = pthread_self();
    +
    360
    +
    361 /* Start thread to update file contents */
    +
    362 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    363 if (ret != 0) {
    +
    364 fprintf(stderr, "pthread_create failed with %s\n",
    +
    365 strerror(ret));
    +
    366 goto err_out3;
    +
    367 }
    +
    368
    +
    369 /* Block until ctrl+c or fusermount -u */
    +
    370 if (opts.singlethread) {
    +
    371 ret = fuse_session_loop(se);
    +
    372 } else {
    +
    373 config = fuse_loop_cfg_create();
    +
    374 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    375 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    376 ret = fuse_session_loop_mt(se, config);
    +
    377 fuse_loop_cfg_destroy(config);
    +
    378 config = NULL;
    +
    379 }
    +
    380
    + +
    382err_out3:
    + +
    384err_out2:
    + +
    386err_out1:
    +
    387 free(opts.mountpoint);
    +
    388 fuse_opt_free_args(&args);
    +
    389
    +
    390 return ret ? 1 : 0;
    +
    391}
    +
    392
    +
    393
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_exited(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    + + + + diff --git a/doc/html/notify__inval__inode_8c.html b/doc/html/notify__inval__inode_8c.html new file mode 100644 index 0000000..826bdb3 --- /dev/null +++ b/doc/html/notify__inval__inode_8c.html @@ -0,0 +1,484 @@ + + + + + + + +libfuse: example/notify_inval_inode.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    notify_inval_inode.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <assert.h>
    +#include <stddef.h>
    +#include <unistd.h>
    +#include <pthread.h>
    +#include <stdbool.h>
    +#include <stdatomic.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

    +

    While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

    +

    To see the effect, first start the file system with the --no-notify option:

    +

    $ notify_inval_inode –update-interval=1 –no-notify mnt/

    +

    Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

    $ for i in 1 2 3 4 5; do
    +>     cat mnt/current_time
    +>     sleep 1
    +> done
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +

    If you instead enable the notification functions, the changes become visible:

     $ notify_inval_inode --update-interval=1 mnt/
    + $ for i in 1 2 3 4 5; do
    + >     cat mnt/current_time
    + >     sleep 1
    + > done
    + The current time is 15:58:40
    + The current time is 15:58:41
    + The current time is 15:58:42
    + The current time is 15:58:43
    + The current time is 15:58:44
    +

    +Compilation

    +
    gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <assert.h>
    +
    #include <stddef.h>
    +
    #include <unistd.h>
    +
    #include <pthread.h>
    +
    #include <stdbool.h>
    +
    #include <stdatomic.h>
    +
    +
    /* We can't actually tell the kernel that there is no
    +
    timeout, so we just send a big value */
    +
    #define NO_TIMEOUT 500000
    +
    +
    #define MAX_STR_LEN 128
    +
    #define FILE_INO 2
    +
    #define FILE_NAME "current_time"
    +
    static char file_contents[MAX_STR_LEN];
    +
    static int lookup_cnt = 0;
    +
    static size_t file_size;
    +
    static _Atomic bool is_stop = false;
    +
    +
    /* Command line parsing */
    +
    struct options {
    +
    int no_notify;
    +
    int update_interval;
    +
    };
    +
    static struct options options = {
    +
    .no_notify = 0,
    +
    .update_interval = 1,
    +
    };
    +
    +
    #define OPTION(t, p) \
    +
    { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--no-notify", no_notify),
    +
    OPTION("--update-interval=%d", update_interval),
    + +
    };
    +
    +
    static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    stbuf->st_ino = ino;
    +
    if (ino == FUSE_ROOT_ID) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 1;
    +
    }
    +
    +
    else if (ino == FILE_INO) {
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = file_size;
    +
    }
    +
    +
    else
    +
    return -1;
    +
    +
    return 0;
    +
    }
    +
    +
    static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
    +
    (void)userdata;
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void tfs_destroy(void *userarg)
    +
    {
    +
    (void)userarg;
    +
    +
    is_stop = true;
    +
    }
    +
    +
    static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    const char *name) {
    +
    struct fuse_entry_param e;
    +
    memset(&e, 0, sizeof(e));
    +
    +
    if (parent != FUSE_ROOT_ID)
    +
    goto err_out;
    +
    else if (strcmp(name, FILE_NAME) == 0) {
    +
    e.ino = FILE_INO;
    +
    lookup_cnt++;
    +
    } else
    +
    goto err_out;
    +
    +
    e.attr_timeout = NO_TIMEOUT;
    +
    e.entry_timeout = NO_TIMEOUT;
    +
    if (tfs_stat(e.ino, &e.attr) != 0)
    +
    goto err_out;
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    err_out:
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    +
    static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
    +
    uint64_t nlookup) {
    +
    (void) req;
    +
    if(ino == FILE_INO)
    +
    lookup_cnt -= nlookup;
    +
    else
    +
    assert(ino == FUSE_ROOT_ID);
    + +
    }
    +
    +
    static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi) {
    +
    struct stat stbuf;
    +
    +
    (void) fi;
    +
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    if (tfs_stat(ino, &stbuf) != 0)
    +
    fuse_reply_err(req, ENOENT);
    +
    else
    +
    fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    }
    +
    +
    struct dirbuf {
    +
    char *p;
    +
    size_t size;
    +
    };
    +
    +
    static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    fuse_ino_t ino) {
    +
    struct stat stbuf;
    +
    size_t oldsize = b->size;
    +
    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    b->p = (char *) realloc(b->p, b->size);
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    stbuf.st_ino = ino;
    +
    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    b->size);
    +
    }
    +
    +
    #define min(x, y) ((x) < (y) ? (x) : (y))
    +
    +
    static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    off_t off, size_t maxsize) {
    +
    if (off < bufsize)
    +
    return fuse_reply_buf(req, buf + off,
    +
    min(bufsize - off, maxsize));
    +
    else
    +
    return fuse_reply_buf(req, NULL, 0);
    +
    }
    +
    +
    static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t off, struct fuse_file_info *fi) {
    +
    (void) fi;
    +
    +
    if (ino != FUSE_ROOT_ID)
    +
    fuse_reply_err(req, ENOTDIR);
    +
    else {
    +
    struct dirbuf b;
    +
    +
    memset(&b, 0, sizeof(b));
    +
    dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    reply_buf_limited(req, b.p, b.size, off, size);
    +
    free(b.p);
    +
    }
    +
    }
    +
    +
    static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi) {
    +
    +
    /* Make cache persistent even if file is closed,
    +
    this makes it easier to see the effects */
    +
    fi->keep_cache = 1;
    +
    +
    if (ino == FUSE_ROOT_ID)
    +
    fuse_reply_err(req, EISDIR);
    +
    else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    fuse_reply_err(req, EACCES);
    +
    else if (ino == FILE_INO)
    +
    fuse_reply_open(req, fi);
    +
    else {
    +
    // This should not happen
    +
    fprintf(stderr, "Got open for non-existing inode!\n");
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    }
    +
    +
    static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t off, struct fuse_file_info *fi) {
    +
    (void) fi;
    +
    +
    assert(ino == FILE_INO);
    +
    reply_buf_limited(req, file_contents, file_size, off, size);
    +
    }
    +
    +
    static const struct fuse_lowlevel_ops tfs_oper = {
    +
    .init = tfs_init,
    +
    .destroy = tfs_destroy,
    +
    .lookup = tfs_lookup,
    +
    .getattr = tfs_getattr,
    +
    .readdir = tfs_readdir,
    +
    .open = tfs_open,
    +
    .read = tfs_read,
    +
    .forget = tfs_forget,
    +
    };
    +
    +
    static void update_fs(void) {
    +
    struct tm *now;
    +
    time_t t;
    +
    t = time(NULL);
    +
    now = localtime(&t);
    +
    assert(now != NULL);
    +
    +
    file_size = strftime(file_contents, MAX_STR_LEN,
    +
    "The current time is %H:%M:%S\n", now);
    +
    assert(file_size != 0);
    +
    }
    +
    +
    static void* update_fs_loop(void *data) {
    +
    struct fuse_session *se = (struct fuse_session*) data;
    +
    +
    while(!is_stop) {
    +
    update_fs();
    +
    if (!options.no_notify && lookup_cnt) {
    +
    /* Only send notification if the kernel is aware of the inode */
    +
    +
    /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    * might come up during umount, when kernel side already releases
    +
    * all inodes, but does not send FUSE_DESTROY yet.
    +
    */
    +
    int ret =
    +
    fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
    +
    if ((ret != 0 && !is_stop) &&
    +
    ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
    +
    fprintf(stderr,
    +
    "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    strerror(-ret), -ret);
    +
    abort();
    +
    }
    +
    }
    +
    sleep(options.update_interval);
    +
    }
    +
    return NULL;
    +
    }
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --update-interval=<secs> Update-rate of file system contents\n"
    +
    " --no-notify Disable kernel notifications\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[]) {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    pthread_t updater;
    +
    int ret = -1;
    +
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0) {
    +
    ret = 1;
    +
    goto err_out1;
    +
    }
    +
    +
    if (opts.show_help) {
    +
    show_help(argv[0]);
    + + +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    /* Initial contents */
    +
    update_fs();
    +
    +
    se = fuse_session_new(&args, &tfs_oper,
    +
    sizeof(tfs_oper), NULL);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    /* Start thread to update file contents */
    +
    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    if (ret != 0) {
    +
    fprintf(stderr, "pthread_create failed with %s\n",
    +
    strerror(ret));
    +
    goto err_out3;
    +
    }
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread)
    +
    ret = fuse_session_loop(se);
    +
    else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    + +
    free(opts.mountpoint);
    +
    +
    return ret ? 1 : 0;
    +
    }
    +
    +
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:75
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    +

    Definition in file notify_inval_inode.c.

    +
    + + + + diff --git a/doc/html/notify__inval__inode_8c_source.html b/doc/html/notify__inval__inode_8c_source.html new file mode 100644 index 0000000..3790c57 --- /dev/null +++ b/doc/html/notify__inval__inode_8c_source.html @@ -0,0 +1,444 @@ + + + + + + + +libfuse: example/notify_inval_inode.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    notify_inval_inode.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    62#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    63
    +
    64#include <fuse_lowlevel.h>
    +
    65#include <stdio.h>
    +
    66#include <stdlib.h>
    +
    67#include <string.h>
    +
    68#include <errno.h>
    +
    69#include <fcntl.h>
    +
    70#include <assert.h>
    +
    71#include <stddef.h>
    +
    72#include <unistd.h>
    +
    73#include <pthread.h>
    +
    74#include <stdbool.h>
    +
    75#include <stdatomic.h>
    +
    76
    +
    77/* We can't actually tell the kernel that there is no
    +
    78 timeout, so we just send a big value */
    +
    79#define NO_TIMEOUT 500000
    +
    80
    +
    81#define MAX_STR_LEN 128
    +
    82#define FILE_INO 2
    +
    83#define FILE_NAME "current_time"
    +
    84static char file_contents[MAX_STR_LEN];
    +
    85static int lookup_cnt = 0;
    +
    86static size_t file_size;
    +
    87static _Atomic bool is_stop = false;
    +
    88
    +
    89/* Command line parsing */
    +
    90struct options {
    +
    91 int no_notify;
    +
    92 int update_interval;
    +
    93};
    +
    94static struct options options = {
    +
    95 .no_notify = 0,
    +
    96 .update_interval = 1,
    +
    97};
    +
    98
    +
    99#define OPTION(t, p) \
    +
    100 { t, offsetof(struct options, p), 1 }
    +
    101static const struct fuse_opt option_spec[] = {
    +
    102 OPTION("--no-notify", no_notify),
    +
    103 OPTION("--update-interval=%d", update_interval),
    + +
    105};
    +
    106
    +
    107static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    108 stbuf->st_ino = ino;
    +
    109 if (ino == FUSE_ROOT_ID) {
    +
    110 stbuf->st_mode = S_IFDIR | 0755;
    +
    111 stbuf->st_nlink = 1;
    +
    112 }
    +
    113
    +
    114 else if (ino == FILE_INO) {
    +
    115 stbuf->st_mode = S_IFREG | 0444;
    +
    116 stbuf->st_nlink = 1;
    +
    117 stbuf->st_size = file_size;
    +
    118 }
    +
    119
    +
    120 else
    +
    121 return -1;
    +
    122
    +
    123 return 0;
    +
    124}
    +
    125
    +
    126static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
    +
    127 (void)userdata;
    +
    128
    +
    129 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    130 conn->no_interrupt = 1;
    +
    131}
    +
    132
    +
    133static void tfs_destroy(void *userarg)
    +
    134{
    +
    135 (void)userarg;
    +
    136
    +
    137 is_stop = true;
    +
    138}
    +
    139
    +
    140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    141 const char *name) {
    +
    142 struct fuse_entry_param e;
    +
    143 memset(&e, 0, sizeof(e));
    +
    144
    +
    145 if (parent != FUSE_ROOT_ID)
    +
    146 goto err_out;
    +
    147 else if (strcmp(name, FILE_NAME) == 0) {
    +
    148 e.ino = FILE_INO;
    +
    149 lookup_cnt++;
    +
    150 } else
    +
    151 goto err_out;
    +
    152
    +
    153 e.attr_timeout = NO_TIMEOUT;
    +
    154 e.entry_timeout = NO_TIMEOUT;
    +
    155 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    156 goto err_out;
    +
    157 fuse_reply_entry(req, &e);
    +
    158 return;
    +
    159
    +
    160err_out:
    +
    161 fuse_reply_err(req, ENOENT);
    +
    162}
    +
    163
    +
    164static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
    +
    165 uint64_t nlookup) {
    +
    166 (void) req;
    +
    167 if(ino == FILE_INO)
    +
    168 lookup_cnt -= nlookup;
    +
    169 else
    +
    170 assert(ino == FUSE_ROOT_ID);
    +
    171 fuse_reply_none(req);
    +
    172}
    +
    173
    +
    174static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    175 struct fuse_file_info *fi) {
    +
    176 struct stat stbuf;
    +
    177
    +
    178 (void) fi;
    +
    179
    +
    180 memset(&stbuf, 0, sizeof(stbuf));
    +
    181 if (tfs_stat(ino, &stbuf) != 0)
    +
    182 fuse_reply_err(req, ENOENT);
    +
    183 else
    +
    184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    185}
    +
    186
    +
    187struct dirbuf {
    +
    188 char *p;
    +
    189 size_t size;
    +
    190};
    +
    191
    +
    192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    193 fuse_ino_t ino) {
    +
    194 struct stat stbuf;
    +
    195 size_t oldsize = b->size;
    +
    196 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    197 b->p = (char *) realloc(b->p, b->size);
    +
    198 memset(&stbuf, 0, sizeof(stbuf));
    +
    199 stbuf.st_ino = ino;
    +
    200 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    201 b->size);
    +
    202}
    +
    203
    +
    204#define min(x, y) ((x) < (y) ? (x) : (y))
    +
    205
    +
    206static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    207 off_t off, size_t maxsize) {
    +
    208 if (off < bufsize)
    +
    209 return fuse_reply_buf(req, buf + off,
    +
    210 min(bufsize - off, maxsize));
    +
    211 else
    +
    212 return fuse_reply_buf(req, NULL, 0);
    +
    213}
    +
    214
    +
    215static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    216 off_t off, struct fuse_file_info *fi) {
    +
    217 (void) fi;
    +
    218
    +
    219 if (ino != FUSE_ROOT_ID)
    +
    220 fuse_reply_err(req, ENOTDIR);
    +
    221 else {
    +
    222 struct dirbuf b;
    +
    223
    +
    224 memset(&b, 0, sizeof(b));
    +
    225 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    226 reply_buf_limited(req, b.p, b.size, off, size);
    +
    227 free(b.p);
    +
    228 }
    +
    229}
    +
    230
    +
    231static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    232 struct fuse_file_info *fi) {
    +
    233
    +
    234 /* Make cache persistent even if file is closed,
    +
    235 this makes it easier to see the effects */
    +
    236 fi->keep_cache = 1;
    +
    237
    +
    238 if (ino == FUSE_ROOT_ID)
    +
    239 fuse_reply_err(req, EISDIR);
    +
    240 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    241 fuse_reply_err(req, EACCES);
    +
    242 else if (ino == FILE_INO)
    +
    243 fuse_reply_open(req, fi);
    +
    244 else {
    +
    245 // This should not happen
    +
    246 fprintf(stderr, "Got open for non-existing inode!\n");
    +
    247 fuse_reply_err(req, ENOENT);
    +
    248 }
    +
    249}
    +
    250
    +
    251static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    252 off_t off, struct fuse_file_info *fi) {
    +
    253 (void) fi;
    +
    254
    +
    255 assert(ino == FILE_INO);
    +
    256 reply_buf_limited(req, file_contents, file_size, off, size);
    +
    257}
    +
    258
    +
    259static const struct fuse_lowlevel_ops tfs_oper = {
    +
    260 .init = tfs_init,
    +
    261 .destroy = tfs_destroy,
    +
    262 .lookup = tfs_lookup,
    +
    263 .getattr = tfs_getattr,
    +
    264 .readdir = tfs_readdir,
    +
    265 .open = tfs_open,
    +
    266 .read = tfs_read,
    +
    267 .forget = tfs_forget,
    +
    268};
    +
    269
    +
    270static void update_fs(void) {
    +
    271 struct tm *now;
    +
    272 time_t t;
    +
    273 t = time(NULL);
    +
    274 now = localtime(&t);
    +
    275 assert(now != NULL);
    +
    276
    +
    277 file_size = strftime(file_contents, MAX_STR_LEN,
    +
    278 "The current time is %H:%M:%S\n", now);
    +
    279 assert(file_size != 0);
    +
    280}
    +
    281
    +
    282static void* update_fs_loop(void *data) {
    +
    283 struct fuse_session *se = (struct fuse_session*) data;
    +
    284
    +
    285 while(!is_stop) {
    +
    286 update_fs();
    +
    287 if (!options.no_notify && lookup_cnt) {
    +
    288 /* Only send notification if the kernel is aware of the inode */
    +
    289
    +
    290 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    291 * might come up during umount, when kernel side already releases
    +
    292 * all inodes, but does not send FUSE_DESTROY yet.
    +
    293 */
    +
    294 int ret =
    +
    295 fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0);
    +
    296 if ((ret != 0 && !is_stop) &&
    +
    297 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
    +
    298 fprintf(stderr,
    +
    299 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    300 strerror(-ret), -ret);
    +
    301 abort();
    +
    302 }
    +
    303 }
    +
    304 sleep(options.update_interval);
    +
    305 }
    +
    306 return NULL;
    +
    307}
    +
    308
    +
    309static void show_help(const char *progname)
    +
    310{
    +
    311 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    312 printf("File-system specific options:\n"
    +
    313 " --update-interval=<secs> Update-rate of file system contents\n"
    +
    314 " --no-notify Disable kernel notifications\n"
    +
    315 "\n");
    +
    316}
    +
    317
    +
    318int main(int argc, char *argv[]) {
    +
    319 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    320 struct fuse_session *se;
    +
    321 struct fuse_cmdline_opts opts;
    +
    322 struct fuse_loop_config *config;
    +
    323 pthread_t updater;
    +
    324 int ret = -1;
    +
    325
    +
    326 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    327 return 1;
    +
    328
    +
    329 if (fuse_parse_cmdline(&args, &opts) != 0) {
    +
    330 ret = 1;
    +
    331 goto err_out1;
    +
    332 }
    +
    333
    +
    334 if (opts.show_help) {
    +
    335 show_help(argv[0]);
    + + +
    338 ret = 0;
    +
    339 goto err_out1;
    +
    340 } else if (opts.show_version) {
    +
    341 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    343 ret = 0;
    +
    344 goto err_out1;
    +
    345 }
    +
    346
    +
    347 /* Initial contents */
    +
    348 update_fs();
    +
    349
    +
    350 se = fuse_session_new(&args, &tfs_oper,
    +
    351 sizeof(tfs_oper), NULL);
    +
    352 if (se == NULL)
    +
    353 goto err_out1;
    +
    354
    +
    355 if (fuse_set_signal_handlers(se) != 0)
    +
    356 goto err_out2;
    +
    357
    +
    358 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    359 goto err_out3;
    +
    360
    +
    361 fuse_daemonize(opts.foreground);
    +
    362
    +
    363 /* Start thread to update file contents */
    +
    364 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    365 if (ret != 0) {
    +
    366 fprintf(stderr, "pthread_create failed with %s\n",
    +
    367 strerror(ret));
    +
    368 goto err_out3;
    +
    369 }
    +
    370
    +
    371 /* Block until ctrl+c or fusermount -u */
    +
    372 if (opts.singlethread)
    +
    373 ret = fuse_session_loop(se);
    +
    374 else {
    +
    375 config = fuse_loop_cfg_create();
    +
    376 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    377 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    378 ret = fuse_session_loop_mt(se, config);
    +
    379 fuse_loop_cfg_destroy(config);
    +
    380 config = NULL;
    +
    381 }
    +
    382
    + +
    384err_out3:
    + +
    386err_out2:
    + +
    388err_out1:
    +
    389 fuse_opt_free_args(&args);
    +
    390 free(opts.mountpoint);
    +
    391
    +
    392 return ret ? 1 : 0;
    +
    393}
    +
    394
    +
    395
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:75
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    + + + + diff --git a/doc/html/notify__store__retrieve_8c.html b/doc/html/notify__store__retrieve_8c.html new file mode 100644 index 0000000..18e6cd4 --- /dev/null +++ b/doc/html/notify__store__retrieve_8c.html @@ -0,0 +1,561 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    notify_store_retrieve.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <assert.h>
    +#include <stddef.h>
    +#include <unistd.h>
    +#include <pthread.h>
    +#include <stdbool.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

    +

    While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

    +

    To see the effect, first start the file system with the --no-notify option:

    $ notify_store_retrieve --update-interval=1 --no-notify mnt/
    +

    Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

    $ for i in 1 2 3 4 5; do
    +>     cat mnt/current_time
    +>     sleep 1
    +> done
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +

    If you instead enable the notification functions, the changes become visible:

    $ notify_store_retrieve --update-interval=1 mnt/
    +$ for i in 1 2 3 4 5; do
    +>     cat mnt/current_time
    +>     sleep 1
    +> done
    +The current time is 15:58:40
    +The current time is 15:58:41
    +The current time is 15:58:42
    +The current time is 15:58:43
    +The current time is 15:58:44
    +

    +Compilation

    +
    gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <assert.h>
    +
    #include <stddef.h>
    +
    #include <unistd.h>
    +
    #include <pthread.h>
    +
    #include <stdbool.h>
    +
    +
    /* We can't actually tell the kernel that there is no
    +
    timeout, so we just send a big value */
    +
    #define NO_TIMEOUT 500000
    +
    +
    #define MAX_STR_LEN 128
    +
    #define FILE_INO 2
    +
    #define FILE_NAME "current_time"
    +
    static char file_contents[MAX_STR_LEN];
    +
    static int lookup_cnt = 0;
    +
    static int open_cnt = 0;
    +
    static size_t file_size;
    +
    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    +
    /* Keep track if we ever stored data (==1), and
    +
    received it back correctly (==2) */
    +
    static int retrieve_status = 0;
    +
    +
    static bool is_umount = false;
    +
    +
    /* updater thread tid */
    +
    static pthread_t updater;
    +
    +
    +
    /* Command line parsing */
    +
    struct options {
    +
    int no_notify;
    +
    int update_interval;
    +
    };
    +
    static struct options options = {
    +
    .no_notify = 0,
    +
    .update_interval = 1,
    +
    };
    +
    +
    #define OPTION(t, p) \
    +
    { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--no-notify", no_notify),
    +
    OPTION("--update-interval=%d", update_interval),
    + +
    };
    +
    +
    static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    stbuf->st_ino = ino;
    +
    if (ino == FUSE_ROOT_ID) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 1;
    +
    }
    +
    +
    else if (ino == FILE_INO) {
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = file_size;
    +
    }
    +
    +
    else
    +
    return -1;
    +
    +
    return 0;
    +
    }
    +
    +
    static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
    +
    (void)userdata;
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    const char *name) {
    +
    struct fuse_entry_param e;
    +
    memset(&e, 0, sizeof(e));
    +
    +
    if (parent != FUSE_ROOT_ID)
    +
    goto err_out;
    +
    else if (strcmp(name, FILE_NAME) == 0) {
    +
    e.ino = FILE_INO;
    +
    } else
    +
    goto err_out;
    +
    +
    e.attr_timeout = NO_TIMEOUT;
    +
    e.entry_timeout = NO_TIMEOUT;
    +
    if (tfs_stat(e.ino, &e.attr) != 0)
    +
    goto err_out;
    +
    fuse_reply_entry(req, &e);
    +
    +
    /*
    +
    * must only be set when the kernel knows about the entry,
    +
    * otherwise update_fs_loop() might see a positive count, but kernel
    +
    * would not have the entry yet
    +
    */
    +
    if (e.ino == FILE_INO) {
    +
    pthread_mutex_lock(&lock);
    +
    lookup_cnt++;
    +
    pthread_mutex_unlock(&lock);
    +
    }
    +
    +
    return;
    +
    +
    err_out:
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    +
    static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
    +
    uint64_t nlookup) {
    +
    (void) req;
    +
    if(ino == FILE_INO) {
    +
    pthread_mutex_lock(&lock);
    +
    lookup_cnt -= nlookup;
    +
    pthread_mutex_unlock(&lock);
    +
    } else
    +
    assert(ino == FUSE_ROOT_ID);
    + +
    }
    +
    +
    static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi) {
    +
    struct stat stbuf;
    +
    +
    (void) fi;
    +
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    if (tfs_stat(ino, &stbuf) != 0)
    +
    fuse_reply_err(req, ENOENT);
    +
    else
    +
    fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    }
    +
    +
    struct dirbuf {
    +
    char *p;
    +
    size_t size;
    +
    };
    +
    +
    static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    fuse_ino_t ino) {
    +
    struct stat stbuf;
    +
    size_t oldsize = b->size;
    +
    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    b->p = (char *) realloc(b->p, b->size);
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    stbuf.st_ino = ino;
    +
    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    b->size);
    +
    }
    +
    +
    #define min(x, y) ((x) < (y) ? (x) : (y))
    +
    +
    static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    off_t off, size_t maxsize) {
    +
    if (off < bufsize)
    +
    return fuse_reply_buf(req, buf + off,
    +
    min(bufsize - off, maxsize));
    +
    else
    +
    return fuse_reply_buf(req, NULL, 0);
    +
    }
    +
    +
    static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t off, struct fuse_file_info *fi) {
    +
    (void) fi;
    +
    +
    if (ino != FUSE_ROOT_ID)
    +
    fuse_reply_err(req, ENOTDIR);
    +
    else {
    +
    struct dirbuf b;
    +
    +
    memset(&b, 0, sizeof(b));
    +
    dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    reply_buf_limited(req, b.p, b.size, off, size);
    +
    free(b.p);
    +
    }
    +
    }
    +
    +
    static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi) {
    +
    +
    /* Make cache persistent even if file is closed,
    +
    this makes it easier to see the effects */
    +
    fi->keep_cache = 1;
    +
    +
    if (ino == FUSE_ROOT_ID)
    +
    fuse_reply_err(req, EISDIR);
    +
    else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    fuse_reply_err(req, EACCES);
    +
    else if (ino == FILE_INO) {
    +
    fuse_reply_open(req, fi);
    +
    pthread_mutex_lock(&lock);
    +
    open_cnt++;
    +
    pthread_mutex_unlock(&lock);
    +
    } else {
    +
    // This should not happen
    +
    fprintf(stderr, "Got open for non-existing inode!\n");
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    }
    +
    +
    static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t off, struct fuse_file_info *fi) {
    +
    (void) fi;
    +
    +
    assert(ino == FILE_INO);
    +
    reply_buf_limited(req, file_contents, file_size, off, size);
    +
    }
    +
    +
    static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
    +
    off_t offset, struct fuse_bufvec *data) {
    +
    struct fuse_bufvec bufv;
    +
    char buf[MAX_STR_LEN];
    +
    char *expected;
    +
    ssize_t ret;
    +
    +
    assert(ino == FILE_INO);
    +
    assert(offset == 0);
    +
    expected = (char*) cookie;
    +
    +
    bufv.count = 1;
    +
    bufv.idx = 0;
    +
    bufv.off = 0;
    +
    bufv.buf[0].size = MAX_STR_LEN;
    +
    bufv.buf[0].mem = buf;
    +
    bufv.buf[0].flags = 0;
    +
    +
    ret = fuse_buf_copy(&bufv, data, 0);
    +
    assert(ret > 0);
    +
    assert(strncmp(buf, expected, ret) == 0);
    +
    free(expected);
    +
    retrieve_status = 2;
    + +
    }
    +
    +
    static void tfs_destroy(void *userdata)
    +
    {
    +
    (void)userdata;
    +
    +
    is_umount = true;
    +
    +
    pthread_join(updater, NULL);
    +
    }
    +
    +
    +
    static const struct fuse_lowlevel_ops tfs_oper = {
    +
    .init = tfs_init,
    +
    .lookup = tfs_lookup,
    +
    .getattr = tfs_getattr,
    +
    .readdir = tfs_readdir,
    +
    .open = tfs_open,
    +
    .read = tfs_read,
    +
    .forget = tfs_forget,
    +
    .retrieve_reply = tfs_retrieve_reply,
    +
    .destroy = tfs_destroy,
    +
    };
    +
    +
    static void update_fs(void) {
    +
    struct tm *now;
    +
    time_t t;
    +
    t = time(NULL);
    +
    now = localtime(&t);
    +
    assert(now != NULL);
    +
    +
    file_size = strftime(file_contents, MAX_STR_LEN,
    +
    "The current time is %H:%M:%S\n", now);
    +
    assert(file_size != 0);
    +
    }
    +
    +
    static void* update_fs_loop(void *data) {
    +
    struct fuse_session *se = (struct fuse_session*) data;
    +
    struct fuse_bufvec bufv;
    +
    int ret;
    +
    +
    while(!is_umount) {
    +
    update_fs();
    +
    pthread_mutex_lock(&lock);
    +
    if (!options.no_notify && open_cnt && lookup_cnt) {
    +
    /* Only send notification if the kernel
    +
    is aware of the inode */
    +
    bufv.count = 1;
    +
    bufv.idx = 0;
    +
    bufv.off = 0;
    +
    bufv.buf[0].size = file_size;
    +
    bufv.buf[0].mem = file_contents;
    +
    bufv.buf[0].flags = 0;
    +
    +
    /*
    +
    * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    * might come up during umount, when kernel side already releases
    +
    * all inodes, but does not send FUSE_DESTROY yet.
    +
    */
    +
    +
    ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
    +
    if ((ret != 0 && !is_umount) &&
    +
    ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
    +
    fprintf(stderr,
    +
    "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    strerror(-ret), -ret);
    +
    abort();
    +
    }
    +
    +
    /* To make sure that everything worked correctly, ask the
    +
    kernel to send us back the stored data */
    +
    ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
    +
    0, (void*) strdup(file_contents));
    +
    assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
    +
    ret != -ENODEV);
    +
    if(retrieve_status == 0)
    +
    retrieve_status = 1;
    +
    }
    +
    pthread_mutex_unlock(&lock);
    +
    sleep(options.update_interval);
    +
    }
    +
    return NULL;
    +
    }
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --update-interval=<secs> Update-rate of file system contents\n"
    +
    " --no-notify Disable kernel notifications\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[]) {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    int ret = -1;
    +
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    return 1;
    +
    if (opts.show_help) {
    +
    show_help(argv[0]);
    + + +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    /* Initial contents */
    +
    update_fs();
    +
    +
    se = fuse_session_new(&args, &tfs_oper,
    +
    sizeof(tfs_oper), NULL);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    /* Start thread to update file contents */
    +
    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    if (ret != 0) {
    +
    fprintf(stderr, "pthread_create failed with %s\n",
    +
    strerror(ret));
    +
    goto err_out3;
    +
    }
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread)
    +
    ret = fuse_session_loop(se);
    +
    else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    +
    assert(retrieve_status != 1);
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    free(opts.mountpoint);
    + +
    +
    return ret ? 1 : 0;
    +
    }
    +
    +
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:75
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    +

    Definition in file notify_store_retrieve.c.

    +
    + + + + diff --git a/doc/html/notify__store__retrieve_8c_source.html b/doc/html/notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..d7a2276 --- /dev/null +++ b/doc/html/notify__store__retrieve_8c_source.html @@ -0,0 +1,523 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    notify_store_retrieve.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    62
    +
    63#include <fuse_lowlevel.h>
    +
    64#include <stdio.h>
    +
    65#include <stdlib.h>
    +
    66#include <string.h>
    +
    67#include <errno.h>
    +
    68#include <fcntl.h>
    +
    69#include <assert.h>
    +
    70#include <stddef.h>
    +
    71#include <unistd.h>
    +
    72#include <pthread.h>
    +
    73#include <stdbool.h>
    +
    74
    +
    75/* We can't actually tell the kernel that there is no
    +
    76 timeout, so we just send a big value */
    +
    77#define NO_TIMEOUT 500000
    +
    78
    +
    79#define MAX_STR_LEN 128
    +
    80#define FILE_INO 2
    +
    81#define FILE_NAME "current_time"
    +
    82static char file_contents[MAX_STR_LEN];
    +
    83static int lookup_cnt = 0;
    +
    84static int open_cnt = 0;
    +
    85static size_t file_size;
    +
    86static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    87
    +
    88/* Keep track if we ever stored data (==1), and
    +
    89 received it back correctly (==2) */
    +
    90static int retrieve_status = 0;
    +
    91
    +
    92static bool is_umount = false;
    +
    93
    +
    94/* updater thread tid */
    +
    95static pthread_t updater;
    +
    96
    +
    97
    +
    98/* Command line parsing */
    +
    99struct options {
    +
    100 int no_notify;
    +
    101 int update_interval;
    +
    102};
    +
    103static struct options options = {
    +
    104 .no_notify = 0,
    +
    105 .update_interval = 1,
    +
    106};
    +
    107
    +
    108#define OPTION(t, p) \
    +
    109 { t, offsetof(struct options, p), 1 }
    +
    110static const struct fuse_opt option_spec[] = {
    +
    111 OPTION("--no-notify", no_notify),
    +
    112 OPTION("--update-interval=%d", update_interval),
    + +
    114};
    +
    115
    +
    116static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    117 stbuf->st_ino = ino;
    +
    118 if (ino == FUSE_ROOT_ID) {
    +
    119 stbuf->st_mode = S_IFDIR | 0755;
    +
    120 stbuf->st_nlink = 1;
    +
    121 }
    +
    122
    +
    123 else if (ino == FILE_INO) {
    +
    124 stbuf->st_mode = S_IFREG | 0444;
    +
    125 stbuf->st_nlink = 1;
    +
    126 stbuf->st_size = file_size;
    +
    127 }
    +
    128
    +
    129 else
    +
    130 return -1;
    +
    131
    +
    132 return 0;
    +
    133}
    +
    134
    +
    135static void tfs_init(void *userdata, struct fuse_conn_info *conn) {
    +
    136 (void)userdata;
    +
    137
    +
    138 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    139 conn->no_interrupt = 1;
    +
    140}
    +
    141
    +
    142static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    143 const char *name) {
    +
    144 struct fuse_entry_param e;
    +
    145 memset(&e, 0, sizeof(e));
    +
    146
    +
    147 if (parent != FUSE_ROOT_ID)
    +
    148 goto err_out;
    +
    149 else if (strcmp(name, FILE_NAME) == 0) {
    +
    150 e.ino = FILE_INO;
    +
    151 } else
    +
    152 goto err_out;
    +
    153
    +
    154 e.attr_timeout = NO_TIMEOUT;
    +
    155 e.entry_timeout = NO_TIMEOUT;
    +
    156 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    157 goto err_out;
    +
    158 fuse_reply_entry(req, &e);
    +
    159
    +
    160 /*
    +
    161 * must only be set when the kernel knows about the entry,
    +
    162 * otherwise update_fs_loop() might see a positive count, but kernel
    +
    163 * would not have the entry yet
    +
    164 */
    +
    165 if (e.ino == FILE_INO) {
    +
    166 pthread_mutex_lock(&lock);
    +
    167 lookup_cnt++;
    +
    168 pthread_mutex_unlock(&lock);
    +
    169 }
    +
    170
    +
    171 return;
    +
    172
    +
    173err_out:
    +
    174 fuse_reply_err(req, ENOENT);
    +
    175}
    +
    176
    +
    177static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
    +
    178 uint64_t nlookup) {
    +
    179 (void) req;
    +
    180 if(ino == FILE_INO) {
    +
    181 pthread_mutex_lock(&lock);
    +
    182 lookup_cnt -= nlookup;
    +
    183 pthread_mutex_unlock(&lock);
    +
    184 } else
    +
    185 assert(ino == FUSE_ROOT_ID);
    +
    186 fuse_reply_none(req);
    +
    187}
    +
    188
    +
    189static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    190 struct fuse_file_info *fi) {
    +
    191 struct stat stbuf;
    +
    192
    +
    193 (void) fi;
    +
    194
    +
    195 memset(&stbuf, 0, sizeof(stbuf));
    +
    196 if (tfs_stat(ino, &stbuf) != 0)
    +
    197 fuse_reply_err(req, ENOENT);
    +
    198 else
    +
    199 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    200}
    +
    201
    +
    202struct dirbuf {
    +
    203 char *p;
    +
    204 size_t size;
    +
    205};
    +
    206
    +
    207static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    208 fuse_ino_t ino) {
    +
    209 struct stat stbuf;
    +
    210 size_t oldsize = b->size;
    +
    211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    212 b->p = (char *) realloc(b->p, b->size);
    +
    213 memset(&stbuf, 0, sizeof(stbuf));
    +
    214 stbuf.st_ino = ino;
    +
    215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    216 b->size);
    +
    217}
    +
    218
    +
    219#define min(x, y) ((x) < (y) ? (x) : (y))
    +
    220
    +
    221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    222 off_t off, size_t maxsize) {
    +
    223 if (off < bufsize)
    +
    224 return fuse_reply_buf(req, buf + off,
    +
    225 min(bufsize - off, maxsize));
    +
    226 else
    +
    227 return fuse_reply_buf(req, NULL, 0);
    +
    228}
    +
    229
    +
    230static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    231 off_t off, struct fuse_file_info *fi) {
    +
    232 (void) fi;
    +
    233
    +
    234 if (ino != FUSE_ROOT_ID)
    +
    235 fuse_reply_err(req, ENOTDIR);
    +
    236 else {
    +
    237 struct dirbuf b;
    +
    238
    +
    239 memset(&b, 0, sizeof(b));
    +
    240 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    241 reply_buf_limited(req, b.p, b.size, off, size);
    +
    242 free(b.p);
    +
    243 }
    +
    244}
    +
    245
    +
    246static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    247 struct fuse_file_info *fi) {
    +
    248
    +
    249 /* Make cache persistent even if file is closed,
    +
    250 this makes it easier to see the effects */
    +
    251 fi->keep_cache = 1;
    +
    252
    +
    253 if (ino == FUSE_ROOT_ID)
    +
    254 fuse_reply_err(req, EISDIR);
    +
    255 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    256 fuse_reply_err(req, EACCES);
    +
    257 else if (ino == FILE_INO) {
    +
    258 fuse_reply_open(req, fi);
    +
    259 pthread_mutex_lock(&lock);
    +
    260 open_cnt++;
    +
    261 pthread_mutex_unlock(&lock);
    +
    262 } else {
    +
    263 // This should not happen
    +
    264 fprintf(stderr, "Got open for non-existing inode!\n");
    +
    265 fuse_reply_err(req, ENOENT);
    +
    266 }
    +
    267}
    +
    268
    +
    269static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    270 off_t off, struct fuse_file_info *fi) {
    +
    271 (void) fi;
    +
    272
    +
    273 assert(ino == FILE_INO);
    +
    274 reply_buf_limited(req, file_contents, file_size, off, size);
    +
    275}
    +
    276
    +
    277static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
    +
    278 off_t offset, struct fuse_bufvec *data) {
    +
    279 struct fuse_bufvec bufv;
    +
    280 char buf[MAX_STR_LEN];
    +
    281 char *expected;
    +
    282 ssize_t ret;
    +
    283
    +
    284 assert(ino == FILE_INO);
    +
    285 assert(offset == 0);
    +
    286 expected = (char*) cookie;
    +
    287
    +
    288 bufv.count = 1;
    +
    289 bufv.idx = 0;
    +
    290 bufv.off = 0;
    +
    291 bufv.buf[0].size = MAX_STR_LEN;
    +
    292 bufv.buf[0].mem = buf;
    +
    293 bufv.buf[0].flags = 0;
    +
    294
    +
    295 ret = fuse_buf_copy(&bufv, data, 0);
    +
    296 assert(ret > 0);
    +
    297 assert(strncmp(buf, expected, ret) == 0);
    +
    298 free(expected);
    +
    299 retrieve_status = 2;
    +
    300 fuse_reply_none(req);
    +
    301}
    +
    302
    +
    303static void tfs_destroy(void *userdata)
    +
    304{
    +
    305 (void)userdata;
    +
    306
    +
    307 is_umount = true;
    +
    308
    +
    309 pthread_join(updater, NULL);
    +
    310}
    +
    311
    +
    312
    +
    313static const struct fuse_lowlevel_ops tfs_oper = {
    +
    314 .init = tfs_init,
    +
    315 .lookup = tfs_lookup,
    +
    316 .getattr = tfs_getattr,
    +
    317 .readdir = tfs_readdir,
    +
    318 .open = tfs_open,
    +
    319 .read = tfs_read,
    +
    320 .forget = tfs_forget,
    +
    321 .retrieve_reply = tfs_retrieve_reply,
    +
    322 .destroy = tfs_destroy,
    +
    323};
    +
    324
    +
    325static void update_fs(void) {
    +
    326 struct tm *now;
    +
    327 time_t t;
    +
    328 t = time(NULL);
    +
    329 now = localtime(&t);
    +
    330 assert(now != NULL);
    +
    331
    +
    332 file_size = strftime(file_contents, MAX_STR_LEN,
    +
    333 "The current time is %H:%M:%S\n", now);
    +
    334 assert(file_size != 0);
    +
    335}
    +
    336
    +
    337static void* update_fs_loop(void *data) {
    +
    338 struct fuse_session *se = (struct fuse_session*) data;
    +
    339 struct fuse_bufvec bufv;
    +
    340 int ret;
    +
    341
    +
    342 while(!is_umount) {
    +
    343 update_fs();
    +
    344 pthread_mutex_lock(&lock);
    +
    345 if (!options.no_notify && open_cnt && lookup_cnt) {
    +
    346 /* Only send notification if the kernel
    +
    347 is aware of the inode */
    +
    348 bufv.count = 1;
    +
    349 bufv.idx = 0;
    +
    350 bufv.off = 0;
    +
    351 bufv.buf[0].size = file_size;
    +
    352 bufv.buf[0].mem = file_contents;
    +
    353 bufv.buf[0].flags = 0;
    +
    354
    +
    355 /*
    +
    356 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    357 * might come up during umount, when kernel side already releases
    +
    358 * all inodes, but does not send FUSE_DESTROY yet.
    +
    359 */
    +
    360
    +
    361 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
    +
    362 if ((ret != 0 && !is_umount) &&
    +
    363 ret != -ENOENT && ret != -EBADF && ret != -ENODEV) {
    +
    364 fprintf(stderr,
    +
    365 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    366 strerror(-ret), -ret);
    +
    367 abort();
    +
    368 }
    +
    369
    +
    370 /* To make sure that everything worked correctly, ask the
    +
    371 kernel to send us back the stored data */
    +
    372 ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
    +
    373 0, (void*) strdup(file_contents));
    +
    374 assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF ||
    +
    375 ret != -ENODEV);
    +
    376 if(retrieve_status == 0)
    +
    377 retrieve_status = 1;
    +
    378 }
    +
    379 pthread_mutex_unlock(&lock);
    +
    380 sleep(options.update_interval);
    +
    381 }
    +
    382 return NULL;
    +
    383}
    +
    384
    +
    385static void show_help(const char *progname)
    +
    386{
    +
    387 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    388 printf("File-system specific options:\n"
    +
    389 " --update-interval=<secs> Update-rate of file system contents\n"
    +
    390 " --no-notify Disable kernel notifications\n"
    +
    391 "\n");
    +
    392}
    +
    393
    +
    394int main(int argc, char *argv[]) {
    +
    395 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    396 struct fuse_session *se;
    +
    397 struct fuse_cmdline_opts opts;
    +
    398 struct fuse_loop_config *config;
    +
    399 int ret = -1;
    +
    400
    +
    401 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    402 return 1;
    +
    403
    +
    404 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    405 return 1;
    +
    406 if (opts.show_help) {
    +
    407 show_help(argv[0]);
    + + +
    410 ret = 0;
    +
    411 goto err_out1;
    +
    412 } else if (opts.show_version) {
    +
    413 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    415 ret = 0;
    +
    416 goto err_out1;
    +
    417 }
    +
    418
    +
    419 /* Initial contents */
    +
    420 update_fs();
    +
    421
    +
    422 se = fuse_session_new(&args, &tfs_oper,
    +
    423 sizeof(tfs_oper), NULL);
    +
    424 if (se == NULL)
    +
    425 goto err_out1;
    +
    426
    +
    427 if (fuse_set_signal_handlers(se) != 0)
    +
    428 goto err_out2;
    +
    429
    +
    430 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    431 goto err_out3;
    +
    432
    +
    433 fuse_daemonize(opts.foreground);
    +
    434
    +
    435 /* Start thread to update file contents */
    +
    436 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    437 if (ret != 0) {
    +
    438 fprintf(stderr, "pthread_create failed with %s\n",
    +
    439 strerror(ret));
    +
    440 goto err_out3;
    +
    441 }
    +
    442
    +
    443 /* Block until ctrl+c or fusermount -u */
    +
    444 if (opts.singlethread)
    +
    445 ret = fuse_session_loop(se);
    +
    446 else {
    +
    447 config = fuse_loop_cfg_create();
    +
    448 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    449 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    450 ret = fuse_session_loop_mt(se, config);
    +
    451 fuse_loop_cfg_destroy(config);
    +
    452 config = NULL;
    +
    453 }
    +
    454
    +
    455 assert(retrieve_status != 1);
    + +
    457err_out3:
    + +
    459err_out2:
    + +
    461err_out1:
    +
    462 free(opts.mountpoint);
    +
    463 fuse_opt_free_args(&args);
    +
    464
    +
    465 return ret ? 1 : 0;
    +
    466}
    +
    467
    +
    468
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:75
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    + + + + diff --git a/doc/html/null_8c.html b/doc/html/null_8c.html new file mode 100644 index 0000000..638f917 --- /dev/null +++ b/doc/html/null_8c.html @@ -0,0 +1,764 @@ + + + + + + + +libfuse: example/null.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    null.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <unistd.h>
    +#include <time.h>
    +#include <errno.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

    +

    Compile with:

    gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #define _GNU_SOURCE
    +
    +
    #include <fuse.h>
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    #include <ulockmgr.h>
    +
    #endif
    +
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <unistd.h>
    +
    #include <fcntl.h>
    +
    #include <sys/stat.h>
    +
    #include <dirent.h>
    +
    #include <errno.h>
    +
    #include <sys/time.h>
    +
    #ifdef HAVE_SETXATTR
    +
    #include <sys/xattr.h>
    +
    #endif
    +
    #include <sys/file.h> /* flock(2) */
    +
    +
    static void *xmp_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->use_ino = 1;
    +
    cfg->nullpath_ok = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    in current function (recommended in high level API) or set fi->direct_io
    +
    in xmp_create() or xmp_open(). */
    +
    // cfg->direct_io = 1;
    + +
    +
    /* Pick up changes from lower filesystem right away. This is
    +
    also necessary for better hardlink support. When the kernel
    +
    calls the unlink() handler, it does not know the inode of
    +
    the to-be-removed entry and can therefore not invalidate
    +
    the cache of the associated inode - resulting in an
    +
    incorrect st_nlink value being reported for any remaining
    +
    hardlinks to this inode. */
    +
    cfg->entry_timeout = 0;
    +
    cfg->attr_timeout = 0;
    +
    cfg->negative_timeout = 0;
    +
    +
    return NULL;
    +
    }
    +
    +
    static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    +
    if(fi)
    +
    res = fstat(fi->fh, stbuf);
    +
    else
    +
    res = lstat(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_access(const char *path, int mask)
    +
    {
    +
    int res;
    +
    +
    res = access(path, mask);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    {
    +
    int res;
    +
    +
    res = readlink(path, buf, size - 1);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    buf[res] = '\0';
    +
    return 0;
    +
    }
    +
    +
    struct xmp_dirp {
    +
    DIR *dp;
    +
    struct dirent *entry;
    +
    off_t offset;
    +
    };
    +
    +
    static int xmp_opendir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
    +
    if (d == NULL)
    +
    return -ENOMEM;
    +
    +
    d->dp = opendir(path);
    +
    if (d->dp == NULL) {
    +
    res = -errno;
    +
    free(d);
    +
    return res;
    +
    }
    +
    d->offset = 0;
    +
    d->entry = NULL;
    +
    +
    fi->fh = (unsigned long) d;
    +
    return 0;
    +
    }
    +
    +
    static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
    +
    {
    +
    return (struct xmp_dirp *) (uintptr_t) fi->fh;
    +
    }
    +
    +
    static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    +
    (void) path;
    +
    if (offset != d->offset) {
    +
    #ifndef __FreeBSD__
    +
    seekdir(d->dp, offset);
    +
    #else
    +
    /* Subtract the one that we add when calling
    +
    telldir() below */
    +
    seekdir(d->dp, offset-1);
    +
    #endif
    +
    d->entry = NULL;
    +
    d->offset = offset;
    +
    }
    +
    while (1) {
    +
    struct stat st;
    +
    off_t nextoff;
    + +
    +
    if (!d->entry) {
    +
    d->entry = readdir(d->dp);
    +
    if (!d->entry)
    +
    break;
    +
    }
    +
    #ifdef HAVE_FSTATAT
    +
    if (flags & FUSE_READDIR_PLUS) {
    +
    int res;
    +
    +
    res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
    +
    AT_SYMLINK_NOFOLLOW);
    +
    if (res != -1)
    +
    fill_flags |= FUSE_FILL_DIR_PLUS;
    +
    }
    +
    #endif
    +
    if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
    +
    memset(&st, 0, sizeof(st));
    +
    st.st_ino = d->entry->d_ino;
    +
    st.st_mode = d->entry->d_type << 12;
    +
    }
    +
    nextoff = telldir(d->dp);
    +
    #ifdef __FreeBSD__
    +
    /* Under FreeBSD, telldir() may return 0 the first time
    +
    it is called. But for libfuse, an offset of zero
    +
    means that offsets are not supported, so we shift
    +
    everything by one. */
    +
    nextoff++;
    +
    #endif
    +
    if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
    +
    break;
    +
    +
    d->entry = NULL;
    +
    d->offset = nextoff;
    +
    }
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    (void) path;
    +
    closedir(d->dp);
    +
    free(d);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    {
    +
    int res;
    +
    +
    if (S_ISFIFO(mode))
    +
    res = mkfifo(path, mode);
    +
    else
    +
    res = mknod(path, mode, rdev);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mkdir(const char *path, mode_t mode)
    +
    {
    +
    int res;
    +
    +
    res = mkdir(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_unlink(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = unlink(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rmdir(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = rmdir(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_symlink(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = symlink(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    /* When we have renameat2() in libc, then we can implement flags */
    +
    if (flags)
    +
    return -EINVAL;
    +
    +
    res = rename(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_link(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = link(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chmod(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = fchmod(fi->fh, mode);
    +
    else
    +
    res = chmod(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if (fi)
    +
    res = fchown(fi->fh, uid, gid);
    +
    else
    +
    res = lchown(path, uid, gid);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_truncate(const char *path, off_t size,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = ftruncate(fi->fh, size);
    +
    else
    +
    res = truncate(path, size);
    +
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_UTIMENSAT
    +
    static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    /* don't use utime/utimes since they follow symlinks */
    +
    if (fi)
    +
    res = futimens(fi->fh, ts);
    +
    else
    +
    res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags, mode);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file). */
    +
    if (fi->flags & O_DIRECT) {
    +
    fi->direct_io = 1;
    + +
    }
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pread(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    size_t size, off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec *src;
    +
    +
    (void) path;
    +
    +
    src = malloc(sizeof(struct fuse_bufvec));
    +
    if (src == NULL)
    +
    return -ENOMEM;
    +
    +
    *src = FUSE_BUFVEC_INIT(size);
    +
    + +
    src->buf[0].fd = fi->fh;
    +
    src->buf[0].pos = offset;
    +
    +
    *bufp = src;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_write(const char *path, const char *buf, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pwrite(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
    +
    +
    (void) path;
    +
    + +
    dst.buf[0].fd = fi->fh;
    +
    dst.buf[0].pos = offset;
    +
    + +
    }
    +
    +
    static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    {
    +
    int res;
    +
    +
    res = statvfs(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_flush(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    /* This is called from every close on an open file, so call the
    +
    close on the underlying filesystem. But since flush may be
    +
    called multiple times for an open file, this must not really
    +
    close the file. This is important if used on a network
    +
    filesystem like NFS which flush the data/metadata on close() */
    +
    res = close(dup(fi->fh));
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    close(fi->fh);
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fsync(const char *path, int isdatasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    #ifndef HAVE_FDATASYNC
    +
    (void) isdatasync;
    +
    #else
    +
    if (isdatasync)
    +
    res = fdatasync(fi->fh);
    +
    else
    +
    #endif
    +
    res = fsync(fi->fh);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_POSIX_FALLOCATE
    +
    static int xmp_fallocate(const char *path, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    +
    if (mode)
    +
    return -EOPNOTSUPP;
    +
    +
    return -posix_fallocate(fi->fh, offset, length);
    +
    }
    +
    #endif
    +
    +
    #ifdef HAVE_SETXATTR
    +
    /* xattr operations are optional and can safely be left unimplemented */
    +
    static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    size_t size, int flags)
    +
    {
    +
    int res = lsetxattr(path, name, value, size, flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    size_t size)
    +
    {
    +
    int res = lgetxattr(path, name, value, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    {
    +
    int res = llistxattr(path, list, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_removexattr(const char *path, const char *name)
    +
    {
    +
    int res = lremovexattr(path, name);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    #endif /* HAVE_SETXATTR */
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    struct flock *lock)
    +
    {
    +
    (void) path;
    +
    +
    return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
    +
    sizeof(fi->lock_owner));
    +
    }
    +
    #endif
    +
    +
    static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    res = flock(fi->fh, op);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static ssize_t xmp_copy_file_range(const char *path_in,
    +
    struct fuse_file_info *fi_in,
    +
    off_t off_in, const char *path_out,
    +
    struct fuse_file_info *fi_out,
    +
    off_t off_out, size_t len, int flags)
    +
    {
    +
    ssize_t res;
    +
    (void) path_in;
    +
    (void) path_out;
    +
    +
    res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    #endif
    +
    +
    static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    {
    +
    off_t res;
    +
    (void) path;
    +
    +
    res = lseek(fi->fh, off, whence);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static const struct fuse_operations xmp_oper = {
    +
    .init = xmp_init,
    +
    .getattr = xmp_getattr,
    +
    .access = xmp_access,
    +
    .readlink = xmp_readlink,
    +
    .opendir = xmp_opendir,
    +
    .readdir = xmp_readdir,
    +
    .releasedir = xmp_releasedir,
    +
    .mknod = xmp_mknod,
    +
    .mkdir = xmp_mkdir,
    +
    .symlink = xmp_symlink,
    +
    .unlink = xmp_unlink,
    +
    .rmdir = xmp_rmdir,
    +
    .rename = xmp_rename,
    +
    .link = xmp_link,
    +
    .chmod = xmp_chmod,
    +
    .chown = xmp_chown,
    +
    .truncate = xmp_truncate,
    +
    #ifdef HAVE_UTIMENSAT
    +
    .utimens = xmp_utimens,
    +
    #endif
    +
    .create = xmp_create,
    +
    .open = xmp_open,
    +
    .read = xmp_read,
    +
    .read_buf = xmp_read_buf,
    +
    .write = xmp_write,
    +
    .write_buf = xmp_write_buf,
    +
    .statfs = xmp_statfs,
    +
    .flush = xmp_flush,
    +
    .release = xmp_release,
    +
    .fsync = xmp_fsync,
    +
    #ifdef HAVE_POSIX_FALLOCATE
    +
    .fallocate = xmp_fallocate,
    +
    #endif
    +
    #ifdef HAVE_SETXATTR
    +
    .setxattr = xmp_setxattr,
    +
    .getxattr = xmp_getxattr,
    +
    .listxattr = xmp_listxattr,
    +
    .removexattr = xmp_removexattr,
    +
    #endif
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    .lock = xmp_lock,
    +
    #endif
    +
    .flock = xmp_flock,
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = xmp_copy_file_range,
    +
    #endif
    +
    .lseek = xmp_lseek,
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    umask(0);
    +
    return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    fuse_fill_dir_flags
    Definition fuse.h:58
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    @ FUSE_BUF_SPLICE_NONBLOCK
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    + + +
    struct fuse_buf buf[1]
    + +
    int32_t nullpath_ok
    Definition fuse.h:273
    +
    int32_t parallel_direct_writes
    Definition fuse.h:312
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + +
    uint64_t lock_owner
    + +
    uint32_t parallel_direct_writes
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    +

    Definition in file null.c.

    +
    + + + + diff --git a/doc/html/null_8c_source.html b/doc/html/null_8c_source.html new file mode 100644 index 0000000..0020701 --- /dev/null +++ b/doc/html/null_8c_source.html @@ -0,0 +1,198 @@ + + + + + + + +libfuse: example/null.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    null.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    25#define FUSE_USE_VERSION 31
    +
    26
    +
    27#include <fuse.h>
    +
    28#include <fuse_lowlevel.h>
    +
    29#include <stdio.h>
    +
    30#include <stdlib.h>
    +
    31#include <string.h>
    +
    32#include <unistd.h>
    +
    33#include <time.h>
    +
    34#include <errno.h>
    +
    35
    +
    36static int null_getattr(const char *path, struct stat *stbuf,
    +
    37 struct fuse_file_info *fi)
    +
    38{
    +
    39 (void) fi;
    +
    40
    +
    41 if(strcmp(path, "/") != 0)
    +
    42 return -ENOENT;
    +
    43
    +
    44 stbuf->st_mode = S_IFREG | 0644;
    +
    45 stbuf->st_nlink = 1;
    +
    46 stbuf->st_uid = getuid();
    +
    47 stbuf->st_gid = getgid();
    +
    48 stbuf->st_size = (1ULL << 32); /* 4G */
    +
    49 stbuf->st_blocks = 0;
    +
    50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
    +
    51
    +
    52 return 0;
    +
    53}
    +
    54
    +
    55static int null_truncate(const char *path, off_t size,
    +
    56 struct fuse_file_info *fi)
    +
    57{
    +
    58 (void) size;
    +
    59 (void) fi;
    +
    60
    +
    61 if(strcmp(path, "/") != 0)
    +
    62 return -ENOENT;
    +
    63
    +
    64 return 0;
    +
    65}
    +
    66
    +
    67static int null_open(const char *path, struct fuse_file_info *fi)
    +
    68{
    +
    69 (void) fi;
    +
    70
    +
    71 if(strcmp(path, "/") != 0)
    +
    72 return -ENOENT;
    +
    73
    +
    74 return 0;
    +
    75}
    +
    76
    +
    77static int null_read(const char *path, char *buf, size_t size,
    +
    78 off_t offset, struct fuse_file_info *fi)
    +
    79{
    +
    80 (void) buf;
    +
    81 (void) offset;
    +
    82 (void) fi;
    +
    83
    +
    84 if(strcmp(path, "/") != 0)
    +
    85 return -ENOENT;
    +
    86
    +
    87 if (offset >= (1ULL << 32))
    +
    88 return 0;
    +
    89
    +
    90 memset(buf, 0, size);
    +
    91 return size;
    +
    92}
    +
    93
    +
    94static int null_write(const char *path, const char *buf, size_t size,
    +
    95 off_t offset, struct fuse_file_info *fi)
    +
    96{
    +
    97 (void) buf;
    +
    98 (void) offset;
    +
    99 (void) fi;
    +
    100
    +
    101 if(strcmp(path, "/") != 0)
    +
    102 return -ENOENT;
    +
    103
    +
    104 return size;
    +
    105}
    +
    106
    +
    107static const struct fuse_operations null_oper = {
    +
    108 .getattr = null_getattr,
    +
    109 .truncate = null_truncate,
    +
    110 .open = null_open,
    +
    111 .read = null_read,
    +
    112 .write = null_write,
    +
    113};
    +
    114
    +
    115int main(int argc, char *argv[])
    +
    116{
    +
    117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    118 struct fuse_cmdline_opts opts;
    +
    119 struct stat stbuf;
    +
    120
    +
    121 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    122 return 1;
    +
    123 fuse_opt_free_args(&args);
    +
    124
    +
    125 if (!opts.mountpoint) {
    +
    126 fprintf(stderr, "missing mountpoint parameter\n");
    +
    127 return 1;
    +
    128 }
    +
    129
    +
    130 if (stat(opts.mountpoint, &stbuf) == -1) {
    +
    131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
    +
    132 opts.mountpoint, strerror(errno));
    +
    133 free(opts.mountpoint);
    +
    134 return 1;
    +
    135 }
    +
    136 free(opts.mountpoint);
    +
    137 if (!S_ISREG(stbuf.st_mode)) {
    +
    138 fprintf(stderr, "mountpoint is not a regular file\n");
    +
    139 return 1;
    +
    140 }
    +
    141
    +
    142 return fuse_main(argc, argv, &null_oper, NULL);
    +
    143}
    + + +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + + +
    int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
    Definition fuse.h:361
    +
    + + + + diff --git a/doc/html/open.png b/doc/html/open.png new file mode 100644 index 0000000000000000000000000000000000000000..30f75c7efe2dd0c9e956e35b69777a02751f048b GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VPM$7~Ar*{o?;hlAFyLXmaDC0y znK1_#cQqJWPES%4Uujug^TE?jMft$}Eq^WaR~)%f)vSNs&gek&x%A9X9sM + + + + + + +libfuse: example/passthrough.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    passthrough.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <stdio.h>
    +#include <string.h>
    +#include <unistd.h>
    +#include <fcntl.h>
    +#include <sys/stat.h>
    +#include <dirent.h>
    +#include <errno.h>
    +#include <sys/time.h>
    +#include "passthrough_helpers.h"
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

    +

    Compile with

    gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #define _GNU_SOURCE
    +
    +
    #ifdef linux
    +
    /* For pread()/pwrite()/utimensat() */
    +
    #define _XOPEN_SOURCE 700
    +
    #endif
    +
    +
    #include <fuse.h>
    +
    #include <stdio.h>
    +
    #include <string.h>
    +
    #include <unistd.h>
    +
    #include <fcntl.h>
    +
    #include <sys/stat.h>
    +
    #include <dirent.h>
    +
    #include <errno.h>
    +
    #ifdef __FreeBSD__
    +
    #include <sys/socket.h>
    +
    #include <sys/un.h>
    +
    #endif
    +
    #include <sys/time.h>
    +
    #ifdef HAVE_SETXATTR
    +
    #include <sys/xattr.h>
    +
    #endif
    +
    +
    #include "passthrough_helpers.h"
    +
    +
    static int fill_dir_plus = 0;
    +
    +
    static void *xmp_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->use_ino = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    in current function (recommended in high level API) or set fi->direct_io
    +
    in xmp_create() or xmp_open(). */
    +
    // cfg->direct_io = 1;
    + +
    +
    /* Pick up changes from lower filesystem right away. This is
    +
    also necessary for better hardlink support. When the kernel
    +
    calls the unlink() handler, it does not know the inode of
    +
    the to-be-removed entry and can therefore not invalidate
    +
    the cache of the associated inode - resulting in an
    +
    incorrect st_nlink value being reported for any remaining
    +
    hardlinks to this inode. */
    +
    if (!cfg->auto_cache) {
    +
    cfg->entry_timeout = 0;
    +
    cfg->attr_timeout = 0;
    +
    cfg->negative_timeout = 0;
    +
    }
    +
    +
    return NULL;
    +
    }
    +
    +
    static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    res = lstat(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_access(const char *path, int mask)
    +
    {
    +
    int res;
    +
    +
    res = access(path, mask);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    {
    +
    int res;
    +
    +
    res = readlink(path, buf, size - 1);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    buf[res] = '\0';
    +
    return 0;
    +
    }
    +
    +
    +
    static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    DIR *dp;
    +
    struct dirent *de;
    +
    +
    (void) offset;
    +
    (void) fi;
    +
    (void) flags;
    +
    +
    dp = opendir(path);
    +
    if (dp == NULL)
    +
    return -errno;
    +
    +
    while ((de = readdir(dp)) != NULL) {
    +
    struct stat st;
    +
    if (fill_dir_plus) {
    +
    fstatat(dirfd(dp), de->d_name, &st,
    +
    AT_SYMLINK_NOFOLLOW);
    +
    } else {
    +
    memset(&st, 0, sizeof(st));
    +
    st.st_ino = de->d_ino;
    +
    st.st_mode = de->d_type << 12;
    +
    }
    +
    if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
    +
    break;
    +
    }
    +
    +
    closedir(dp);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    {
    +
    int res;
    +
    +
    res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mkdir(const char *path, mode_t mode)
    +
    {
    +
    int res;
    +
    +
    res = mkdir(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_unlink(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = unlink(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rmdir(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = rmdir(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_symlink(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = symlink(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    if (flags)
    +
    return -EINVAL;
    +
    +
    res = rename(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_link(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = link(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chmod(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    res = chmod(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    res = lchown(path, uid, gid);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_truncate(const char *path, off_t size,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if (fi != NULL)
    +
    res = ftruncate(fi->fh, size);
    +
    else
    +
    res = truncate(path, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_UTIMENSAT
    +
    static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    /* don't use utime/utimes since they follow symlinks */
    +
    res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static int xmp_create(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    res = open(path, fi->flags, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    fi->fh = res;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    res = open(path, fi->flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file). */
    +
    if (fi->flags & O_DIRECT) {
    +
    fi->direct_io = 1;
    + +
    }
    +
    +
    fi->fh = res;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    int res;
    +
    +
    if(fi == NULL)
    +
    fd = open(path, O_RDONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = pread(fd, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if(fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    +
    static int xmp_write(const char *path, const char *buf, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    int res;
    +
    +
    (void) fi;
    +
    if(fi == NULL)
    +
    fd = open(path, O_WRONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = pwrite(fd, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if(fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    +
    static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    {
    +
    int res;
    +
    +
    res = statvfs(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    close(fi->fh);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fsync(const char *path, int isdatasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    /* Just a stub. This method is optional and can safely be left
    +
    unimplemented */
    +
    +
    (void) path;
    +
    (void) isdatasync;
    +
    (void) fi;
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_POSIX_FALLOCATE
    +
    static int xmp_fallocate(const char *path, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    int res;
    +
    +
    (void) fi;
    +
    +
    if (mode)
    +
    return -EOPNOTSUPP;
    +
    +
    if(fi == NULL)
    +
    fd = open(path, O_WRONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = -posix_fallocate(fd, offset, length);
    +
    +
    if(fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    #endif
    +
    +
    #ifdef HAVE_SETXATTR
    +
    /* xattr operations are optional and can safely be left unimplemented */
    +
    static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    size_t size, int flags)
    +
    {
    +
    int res = lsetxattr(path, name, value, size, flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    size_t size)
    +
    {
    +
    int res = lgetxattr(path, name, value, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    {
    +
    int res = llistxattr(path, list, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_removexattr(const char *path, const char *name)
    +
    {
    +
    int res = lremovexattr(path, name);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    #endif /* HAVE_SETXATTR */
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static ssize_t xmp_copy_file_range(const char *path_in,
    +
    struct fuse_file_info *fi_in,
    +
    off_t offset_in, const char *path_out,
    +
    struct fuse_file_info *fi_out,
    +
    off_t offset_out, size_t len, int flags)
    +
    {
    +
    int fd_in, fd_out;
    +
    ssize_t res;
    +
    +
    if(fi_in == NULL)
    +
    fd_in = open(path_in, O_RDONLY);
    +
    else
    +
    fd_in = fi_in->fh;
    +
    +
    if (fd_in == -1)
    +
    return -errno;
    +
    +
    if(fi_out == NULL)
    +
    fd_out = open(path_out, O_WRONLY);
    +
    else
    +
    fd_out = fi_out->fh;
    +
    +
    if (fd_out == -1) {
    +
    close(fd_in);
    +
    return -errno;
    +
    }
    +
    +
    res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
    +
    flags);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if (fi_out == NULL)
    +
    close(fd_out);
    +
    if (fi_in == NULL)
    +
    close(fd_in);
    +
    +
    return res;
    +
    }
    +
    #endif
    +
    +
    static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    off_t res;
    +
    +
    if (fi == NULL)
    +
    fd = open(path, O_RDONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = lseek(fd, off, whence);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if (fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    +
    static const struct fuse_operations xmp_oper = {
    +
    .init = xmp_init,
    +
    .getattr = xmp_getattr,
    +
    .access = xmp_access,
    +
    .readlink = xmp_readlink,
    +
    .readdir = xmp_readdir,
    +
    .mknod = xmp_mknod,
    +
    .mkdir = xmp_mkdir,
    +
    .symlink = xmp_symlink,
    +
    .unlink = xmp_unlink,
    +
    .rmdir = xmp_rmdir,
    +
    .rename = xmp_rename,
    +
    .link = xmp_link,
    +
    .chmod = xmp_chmod,
    +
    .chown = xmp_chown,
    +
    .truncate = xmp_truncate,
    +
    #ifdef HAVE_UTIMENSAT
    +
    .utimens = xmp_utimens,
    +
    #endif
    +
    .open = xmp_open,
    +
    .create = xmp_create,
    +
    .read = xmp_read,
    +
    .write = xmp_write,
    +
    .statfs = xmp_statfs,
    +
    .release = xmp_release,
    +
    .fsync = xmp_fsync,
    +
    #ifdef HAVE_POSIX_FALLOCATE
    +
    .fallocate = xmp_fallocate,
    +
    #endif
    +
    #ifdef HAVE_SETXATTR
    +
    .setxattr = xmp_setxattr,
    +
    .getxattr = xmp_getxattr,
    +
    .listxattr = xmp_listxattr,
    +
    .removexattr = xmp_removexattr,
    +
    #endif
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = xmp_copy_file_range,
    +
    #endif
    +
    .lseek = xmp_lseek,
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    enum { MAX_ARGS = 10 };
    +
    int i,new_argc;
    +
    char *new_argv[MAX_ARGS];
    +
    +
    umask(0);
    +
    /* Process the "--plus" option apart */
    +
    for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
    +
    if (!strcmp(argv[i], "--plus")) {
    +
    fill_dir_plus = FUSE_FILL_DIR_PLUS;
    +
    } else {
    +
    new_argv[new_argc++] = argv[i];
    +
    }
    +
    }
    +
    return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    fuse_readdir_flags
    Definition fuse.h:42
    + +
    int32_t parallel_direct_writes
    Definition fuse.h:312
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    int32_t auto_cache
    Definition fuse.h:253
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + + +
    uint32_t parallel_direct_writes
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    +

    Definition in file passthrough.c.

    +
    + + + + diff --git a/doc/html/passthrough_8c_source.html b/doc/html/passthrough_8c_source.html new file mode 100644 index 0000000..a940199 --- /dev/null +++ b/doc/html/passthrough_8c_source.html @@ -0,0 +1,650 @@ + + + + + + + +libfuse: example/passthrough.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    passthrough.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file COPYING.
    +
    8*/
    +
    9
    +
    26#define FUSE_USE_VERSION 31
    +
    27
    +
    28#define _GNU_SOURCE
    +
    29
    +
    30#ifdef linux
    +
    31/* For pread()/pwrite()/utimensat() */
    +
    32#define _XOPEN_SOURCE 700
    +
    33#endif
    +
    34
    +
    35#include <fuse.h>
    +
    36#include <stdio.h>
    +
    37#include <string.h>
    +
    38#include <unistd.h>
    +
    39#include <fcntl.h>
    +
    40#include <sys/stat.h>
    +
    41#include <dirent.h>
    +
    42#include <errno.h>
    +
    43#ifdef __FreeBSD__
    +
    44#include <sys/socket.h>
    +
    45#include <sys/un.h>
    +
    46#endif
    +
    47#include <sys/time.h>
    +
    48#ifdef HAVE_SETXATTR
    +
    49#include <sys/xattr.h>
    +
    50#endif
    +
    51
    +
    52#include "passthrough_helpers.h"
    +
    53
    +
    54static int fill_dir_plus = 0;
    +
    55
    +
    56static void *xmp_init(struct fuse_conn_info *conn,
    +
    57 struct fuse_config *cfg)
    +
    58{
    +
    59 (void) conn;
    +
    60 cfg->use_ino = 1;
    +
    61
    +
    62 /* parallel_direct_writes feature depends on direct_io features.
    +
    63 To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    64 in current function (recommended in high level API) or set fi->direct_io
    +
    65 in xmp_create() or xmp_open(). */
    +
    66 // cfg->direct_io = 1;
    + +
    68
    +
    69 /* Pick up changes from lower filesystem right away. This is
    +
    70 also necessary for better hardlink support. When the kernel
    +
    71 calls the unlink() handler, it does not know the inode of
    +
    72 the to-be-removed entry and can therefore not invalidate
    +
    73 the cache of the associated inode - resulting in an
    +
    74 incorrect st_nlink value being reported for any remaining
    +
    75 hardlinks to this inode. */
    +
    76 if (!cfg->auto_cache) {
    +
    77 cfg->entry_timeout = 0;
    +
    78 cfg->attr_timeout = 0;
    +
    79 cfg->negative_timeout = 0;
    +
    80 }
    +
    81
    +
    82 return NULL;
    +
    83}
    +
    84
    +
    85static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    86 struct fuse_file_info *fi)
    +
    87{
    +
    88 (void) fi;
    +
    89 int res;
    +
    90
    +
    91 res = lstat(path, stbuf);
    +
    92 if (res == -1)
    +
    93 return -errno;
    +
    94
    +
    95 return 0;
    +
    96}
    +
    97
    +
    98static int xmp_access(const char *path, int mask)
    +
    99{
    +
    100 int res;
    +
    101
    +
    102 res = access(path, mask);
    +
    103 if (res == -1)
    +
    104 return -errno;
    +
    105
    +
    106 return 0;
    +
    107}
    +
    108
    +
    109static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    110{
    +
    111 int res;
    +
    112
    +
    113 res = readlink(path, buf, size - 1);
    +
    114 if (res == -1)
    +
    115 return -errno;
    +
    116
    +
    117 buf[res] = '\0';
    +
    118 return 0;
    +
    119}
    +
    120
    +
    121
    +
    122static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    123 off_t offset, struct fuse_file_info *fi,
    +
    124 enum fuse_readdir_flags flags)
    +
    125{
    +
    126 DIR *dp;
    +
    127 struct dirent *de;
    +
    128
    +
    129 (void) offset;
    +
    130 (void) fi;
    +
    131 (void) flags;
    +
    132
    +
    133 dp = opendir(path);
    +
    134 if (dp == NULL)
    +
    135 return -errno;
    +
    136
    +
    137 while ((de = readdir(dp)) != NULL) {
    +
    138 struct stat st;
    +
    139 if (fill_dir_plus) {
    +
    140 fstatat(dirfd(dp), de->d_name, &st,
    +
    141 AT_SYMLINK_NOFOLLOW);
    +
    142 } else {
    +
    143 memset(&st, 0, sizeof(st));
    +
    144 st.st_ino = de->d_ino;
    +
    145 st.st_mode = de->d_type << 12;
    +
    146 }
    +
    147 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
    +
    148 break;
    +
    149 }
    +
    150
    +
    151 closedir(dp);
    +
    152 return 0;
    +
    153}
    +
    154
    +
    155static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    156{
    +
    157 int res;
    +
    158
    +
    159 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
    +
    160 if (res == -1)
    +
    161 return -errno;
    +
    162
    +
    163 return 0;
    +
    164}
    +
    165
    +
    166static int xmp_mkdir(const char *path, mode_t mode)
    +
    167{
    +
    168 int res;
    +
    169
    +
    170 res = mkdir(path, mode);
    +
    171 if (res == -1)
    +
    172 return -errno;
    +
    173
    +
    174 return 0;
    +
    175}
    +
    176
    +
    177static int xmp_unlink(const char *path)
    +
    178{
    +
    179 int res;
    +
    180
    +
    181 res = unlink(path);
    +
    182 if (res == -1)
    +
    183 return -errno;
    +
    184
    +
    185 return 0;
    +
    186}
    +
    187
    +
    188static int xmp_rmdir(const char *path)
    +
    189{
    +
    190 int res;
    +
    191
    +
    192 res = rmdir(path);
    +
    193 if (res == -1)
    +
    194 return -errno;
    +
    195
    +
    196 return 0;
    +
    197}
    +
    198
    +
    199static int xmp_symlink(const char *from, const char *to)
    +
    200{
    +
    201 int res;
    +
    202
    +
    203 res = symlink(from, to);
    +
    204 if (res == -1)
    +
    205 return -errno;
    +
    206
    +
    207 return 0;
    +
    208}
    +
    209
    +
    210static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    211{
    +
    212 int res;
    +
    213
    +
    214 if (flags)
    +
    215 return -EINVAL;
    +
    216
    +
    217 res = rename(from, to);
    +
    218 if (res == -1)
    +
    219 return -errno;
    +
    220
    +
    221 return 0;
    +
    222}
    +
    223
    +
    224static int xmp_link(const char *from, const char *to)
    +
    225{
    +
    226 int res;
    +
    227
    +
    228 res = link(from, to);
    +
    229 if (res == -1)
    +
    230 return -errno;
    +
    231
    +
    232 return 0;
    +
    233}
    +
    234
    +
    235static int xmp_chmod(const char *path, mode_t mode,
    +
    236 struct fuse_file_info *fi)
    +
    237{
    +
    238 (void) fi;
    +
    239 int res;
    +
    240
    +
    241 res = chmod(path, mode);
    +
    242 if (res == -1)
    +
    243 return -errno;
    +
    244
    +
    245 return 0;
    +
    246}
    +
    247
    +
    248static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    249 struct fuse_file_info *fi)
    +
    250{
    +
    251 (void) fi;
    +
    252 int res;
    +
    253
    +
    254 res = lchown(path, uid, gid);
    +
    255 if (res == -1)
    +
    256 return -errno;
    +
    257
    +
    258 return 0;
    +
    259}
    +
    260
    +
    261static int xmp_truncate(const char *path, off_t size,
    +
    262 struct fuse_file_info *fi)
    +
    263{
    +
    264 int res;
    +
    265
    +
    266 if (fi != NULL)
    +
    267 res = ftruncate(fi->fh, size);
    +
    268 else
    +
    269 res = truncate(path, size);
    +
    270 if (res == -1)
    +
    271 return -errno;
    +
    272
    +
    273 return 0;
    +
    274}
    +
    275
    +
    276#ifdef HAVE_UTIMENSAT
    +
    277static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    278 struct fuse_file_info *fi)
    +
    279{
    +
    280 (void) fi;
    +
    281 int res;
    +
    282
    +
    283 /* don't use utime/utimes since they follow symlinks */
    +
    284 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    285 if (res == -1)
    +
    286 return -errno;
    +
    287
    +
    288 return 0;
    +
    289}
    +
    290#endif
    +
    291
    +
    292static int xmp_create(const char *path, mode_t mode,
    +
    293 struct fuse_file_info *fi)
    +
    294{
    +
    295 int res;
    +
    296
    +
    297 res = open(path, fi->flags, mode);
    +
    298 if (res == -1)
    +
    299 return -errno;
    +
    300
    +
    301 fi->fh = res;
    +
    302 return 0;
    +
    303}
    +
    304
    +
    305static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    306{
    +
    307 int res;
    +
    308
    +
    309 res = open(path, fi->flags);
    +
    310 if (res == -1)
    +
    311 return -errno;
    +
    312
    +
    313 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    314 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    315 for writes to the same file). */
    +
    316 if (fi->flags & O_DIRECT) {
    +
    317 fi->direct_io = 1;
    + +
    319 }
    +
    320
    +
    321 fi->fh = res;
    +
    322 return 0;
    +
    323}
    +
    324
    +
    325static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    326 struct fuse_file_info *fi)
    +
    327{
    +
    328 int fd;
    +
    329 int res;
    +
    330
    +
    331 if(fi == NULL)
    +
    332 fd = open(path, O_RDONLY);
    +
    333 else
    +
    334 fd = fi->fh;
    +
    335
    +
    336 if (fd == -1)
    +
    337 return -errno;
    +
    338
    +
    339 res = pread(fd, buf, size, offset);
    +
    340 if (res == -1)
    +
    341 res = -errno;
    +
    342
    +
    343 if(fi == NULL)
    +
    344 close(fd);
    +
    345 return res;
    +
    346}
    +
    347
    +
    348static int xmp_write(const char *path, const char *buf, size_t size,
    +
    349 off_t offset, struct fuse_file_info *fi)
    +
    350{
    +
    351 int fd;
    +
    352 int res;
    +
    353
    +
    354 (void) fi;
    +
    355 if(fi == NULL)
    +
    356 fd = open(path, O_WRONLY);
    +
    357 else
    +
    358 fd = fi->fh;
    +
    359
    +
    360 if (fd == -1)
    +
    361 return -errno;
    +
    362
    +
    363 res = pwrite(fd, buf, size, offset);
    +
    364 if (res == -1)
    +
    365 res = -errno;
    +
    366
    +
    367 if(fi == NULL)
    +
    368 close(fd);
    +
    369 return res;
    +
    370}
    +
    371
    +
    372static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    373{
    +
    374 int res;
    +
    375
    +
    376 res = statvfs(path, stbuf);
    +
    377 if (res == -1)
    +
    378 return -errno;
    +
    379
    +
    380 return 0;
    +
    381}
    +
    382
    +
    383static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    384{
    +
    385 (void) path;
    +
    386 close(fi->fh);
    +
    387 return 0;
    +
    388}
    +
    389
    +
    390static int xmp_fsync(const char *path, int isdatasync,
    +
    391 struct fuse_file_info *fi)
    +
    392{
    +
    393 /* Just a stub. This method is optional and can safely be left
    +
    394 unimplemented */
    +
    395
    +
    396 (void) path;
    +
    397 (void) isdatasync;
    +
    398 (void) fi;
    +
    399 return 0;
    +
    400}
    +
    401
    +
    402#ifdef HAVE_POSIX_FALLOCATE
    +
    403static int xmp_fallocate(const char *path, int mode,
    +
    404 off_t offset, off_t length, struct fuse_file_info *fi)
    +
    405{
    +
    406 int fd;
    +
    407 int res;
    +
    408
    +
    409 (void) fi;
    +
    410
    +
    411 if (mode)
    +
    412 return -EOPNOTSUPP;
    +
    413
    +
    414 if(fi == NULL)
    +
    415 fd = open(path, O_WRONLY);
    +
    416 else
    +
    417 fd = fi->fh;
    +
    418
    +
    419 if (fd == -1)
    +
    420 return -errno;
    +
    421
    +
    422 res = -posix_fallocate(fd, offset, length);
    +
    423
    +
    424 if(fi == NULL)
    +
    425 close(fd);
    +
    426 return res;
    +
    427}
    +
    428#endif
    +
    429
    +
    430#ifdef HAVE_SETXATTR
    +
    431/* xattr operations are optional and can safely be left unimplemented */
    +
    432static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    433 size_t size, int flags)
    +
    434{
    +
    435 int res = lsetxattr(path, name, value, size, flags);
    +
    436 if (res == -1)
    +
    437 return -errno;
    +
    438 return 0;
    +
    439}
    +
    440
    +
    441static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    442 size_t size)
    +
    443{
    +
    444 int res = lgetxattr(path, name, value, size);
    +
    445 if (res == -1)
    +
    446 return -errno;
    +
    447 return res;
    +
    448}
    +
    449
    +
    450static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    451{
    +
    452 int res = llistxattr(path, list, size);
    +
    453 if (res == -1)
    +
    454 return -errno;
    +
    455 return res;
    +
    456}
    +
    457
    +
    458static int xmp_removexattr(const char *path, const char *name)
    +
    459{
    +
    460 int res = lremovexattr(path, name);
    +
    461 if (res == -1)
    +
    462 return -errno;
    +
    463 return 0;
    +
    464}
    +
    465#endif /* HAVE_SETXATTR */
    +
    466
    +
    467#ifdef HAVE_COPY_FILE_RANGE
    +
    468static ssize_t xmp_copy_file_range(const char *path_in,
    +
    469 struct fuse_file_info *fi_in,
    +
    470 off_t offset_in, const char *path_out,
    +
    471 struct fuse_file_info *fi_out,
    +
    472 off_t offset_out, size_t len, int flags)
    +
    473{
    +
    474 int fd_in, fd_out;
    +
    475 ssize_t res;
    +
    476
    +
    477 if(fi_in == NULL)
    +
    478 fd_in = open(path_in, O_RDONLY);
    +
    479 else
    +
    480 fd_in = fi_in->fh;
    +
    481
    +
    482 if (fd_in == -1)
    +
    483 return -errno;
    +
    484
    +
    485 if(fi_out == NULL)
    +
    486 fd_out = open(path_out, O_WRONLY);
    +
    487 else
    +
    488 fd_out = fi_out->fh;
    +
    489
    +
    490 if (fd_out == -1) {
    +
    491 close(fd_in);
    +
    492 return -errno;
    +
    493 }
    +
    494
    +
    495 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
    +
    496 flags);
    +
    497 if (res == -1)
    +
    498 res = -errno;
    +
    499
    +
    500 if (fi_out == NULL)
    +
    501 close(fd_out);
    +
    502 if (fi_in == NULL)
    +
    503 close(fd_in);
    +
    504
    +
    505 return res;
    +
    506}
    +
    507#endif
    +
    508
    +
    509static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    510{
    +
    511 int fd;
    +
    512 off_t res;
    +
    513
    +
    514 if (fi == NULL)
    +
    515 fd = open(path, O_RDONLY);
    +
    516 else
    +
    517 fd = fi->fh;
    +
    518
    +
    519 if (fd == -1)
    +
    520 return -errno;
    +
    521
    +
    522 res = lseek(fd, off, whence);
    +
    523 if (res == -1)
    +
    524 res = -errno;
    +
    525
    +
    526 if (fi == NULL)
    +
    527 close(fd);
    +
    528 return res;
    +
    529}
    +
    530
    +
    531static const struct fuse_operations xmp_oper = {
    +
    532 .init = xmp_init,
    +
    533 .getattr = xmp_getattr,
    +
    534 .access = xmp_access,
    +
    535 .readlink = xmp_readlink,
    +
    536 .readdir = xmp_readdir,
    +
    537 .mknod = xmp_mknod,
    +
    538 .mkdir = xmp_mkdir,
    +
    539 .symlink = xmp_symlink,
    +
    540 .unlink = xmp_unlink,
    +
    541 .rmdir = xmp_rmdir,
    +
    542 .rename = xmp_rename,
    +
    543 .link = xmp_link,
    +
    544 .chmod = xmp_chmod,
    +
    545 .chown = xmp_chown,
    +
    546 .truncate = xmp_truncate,
    +
    547#ifdef HAVE_UTIMENSAT
    +
    548 .utimens = xmp_utimens,
    +
    549#endif
    +
    550 .open = xmp_open,
    +
    551 .create = xmp_create,
    +
    552 .read = xmp_read,
    +
    553 .write = xmp_write,
    +
    554 .statfs = xmp_statfs,
    +
    555 .release = xmp_release,
    +
    556 .fsync = xmp_fsync,
    +
    557#ifdef HAVE_POSIX_FALLOCATE
    +
    558 .fallocate = xmp_fallocate,
    +
    559#endif
    +
    560#ifdef HAVE_SETXATTR
    +
    561 .setxattr = xmp_setxattr,
    +
    562 .getxattr = xmp_getxattr,
    +
    563 .listxattr = xmp_listxattr,
    +
    564 .removexattr = xmp_removexattr,
    +
    565#endif
    +
    566#ifdef HAVE_COPY_FILE_RANGE
    +
    567 .copy_file_range = xmp_copy_file_range,
    +
    568#endif
    +
    569 .lseek = xmp_lseek,
    +
    570};
    +
    571
    +
    572int main(int argc, char *argv[])
    +
    573{
    +
    574 enum { MAX_ARGS = 10 };
    +
    575 int i,new_argc;
    +
    576 char *new_argv[MAX_ARGS];
    +
    577
    +
    578 umask(0);
    +
    579 /* Process the "--plus" option apart */
    +
    580 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
    +
    581 if (!strcmp(argv[i], "--plus")) {
    +
    582 fill_dir_plus = FUSE_FILL_DIR_PLUS;
    +
    583 } else {
    +
    584 new_argv[new_argc++] = argv[i];
    +
    585 }
    +
    586 }
    +
    587 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
    +
    588}
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    fuse_readdir_flags
    Definition fuse.h:42
    + +
    int32_t parallel_direct_writes
    Definition fuse.h:312
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    int32_t auto_cache
    Definition fuse.h:253
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + + +
    uint32_t parallel_direct_writes
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    + + + + diff --git a/doc/html/passthrough__fh_8c.html b/doc/html/passthrough__fh_8c.html new file mode 100644 index 0000000..1e1e239 --- /dev/null +++ b/doc/html/passthrough__fh_8c.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: example/passthrough_fh.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    passthrough_fh.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <unistd.h>
    +#include <fcntl.h>
    +#include <sys/stat.h>
    +#include <dirent.h>
    +#include <errno.h>
    +#include <sys/time.h>
    +#include <sys/file.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

    +

    Compile with:

    gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #define _GNU_SOURCE
    +
    +
    #include <fuse.h>
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    #include <ulockmgr.h>
    +
    #endif
    +
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <unistd.h>
    +
    #include <fcntl.h>
    +
    #include <sys/stat.h>
    +
    #include <dirent.h>
    +
    #include <errno.h>
    +
    #include <sys/time.h>
    +
    #ifdef HAVE_SETXATTR
    +
    #include <sys/xattr.h>
    +
    #endif
    +
    #include <sys/file.h> /* flock(2) */
    +
    +
    static void *xmp_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->use_ino = 1;
    +
    cfg->nullpath_ok = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    in current function (recommended in high level API) or set fi->direct_io
    +
    in xmp_create() or xmp_open(). */
    +
    // cfg->direct_io = 1;
    + +
    +
    /* Pick up changes from lower filesystem right away. This is
    +
    also necessary for better hardlink support. When the kernel
    +
    calls the unlink() handler, it does not know the inode of
    +
    the to-be-removed entry and can therefore not invalidate
    +
    the cache of the associated inode - resulting in an
    +
    incorrect st_nlink value being reported for any remaining
    +
    hardlinks to this inode. */
    +
    cfg->entry_timeout = 0;
    +
    cfg->attr_timeout = 0;
    +
    cfg->negative_timeout = 0;
    +
    +
    return NULL;
    +
    }
    +
    +
    static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    +
    if(fi)
    +
    res = fstat(fi->fh, stbuf);
    +
    else
    +
    res = lstat(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_access(const char *path, int mask)
    +
    {
    +
    int res;
    +
    +
    res = access(path, mask);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    {
    +
    int res;
    +
    +
    res = readlink(path, buf, size - 1);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    buf[res] = '\0';
    +
    return 0;
    +
    }
    +
    +
    struct xmp_dirp {
    +
    DIR *dp;
    +
    struct dirent *entry;
    +
    off_t offset;
    +
    };
    +
    +
    static int xmp_opendir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
    +
    if (d == NULL)
    +
    return -ENOMEM;
    +
    +
    d->dp = opendir(path);
    +
    if (d->dp == NULL) {
    +
    res = -errno;
    +
    free(d);
    +
    return res;
    +
    }
    +
    d->offset = 0;
    +
    d->entry = NULL;
    +
    +
    fi->fh = (unsigned long) d;
    +
    return 0;
    +
    }
    +
    +
    static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
    +
    {
    +
    return (struct xmp_dirp *) (uintptr_t) fi->fh;
    +
    }
    +
    +
    static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    +
    (void) path;
    +
    if (offset != d->offset) {
    +
    #ifndef __FreeBSD__
    +
    seekdir(d->dp, offset);
    +
    #else
    +
    /* Subtract the one that we add when calling
    +
    telldir() below */
    +
    seekdir(d->dp, offset-1);
    +
    #endif
    +
    d->entry = NULL;
    +
    d->offset = offset;
    +
    }
    +
    while (1) {
    +
    struct stat st;
    +
    off_t nextoff;
    + +
    +
    if (!d->entry) {
    +
    d->entry = readdir(d->dp);
    +
    if (!d->entry)
    +
    break;
    +
    }
    +
    #ifdef HAVE_FSTATAT
    +
    if (flags & FUSE_READDIR_PLUS) {
    +
    int res;
    +
    +
    res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
    +
    AT_SYMLINK_NOFOLLOW);
    +
    if (res != -1)
    +
    fill_flags |= FUSE_FILL_DIR_PLUS;
    +
    }
    +
    #endif
    +
    if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
    +
    memset(&st, 0, sizeof(st));
    +
    st.st_ino = d->entry->d_ino;
    +
    st.st_mode = d->entry->d_type << 12;
    +
    }
    +
    nextoff = telldir(d->dp);
    +
    #ifdef __FreeBSD__
    +
    /* Under FreeBSD, telldir() may return 0 the first time
    +
    it is called. But for libfuse, an offset of zero
    +
    means that offsets are not supported, so we shift
    +
    everything by one. */
    +
    nextoff++;
    +
    #endif
    +
    if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
    +
    break;
    +
    +
    d->entry = NULL;
    +
    d->offset = nextoff;
    +
    }
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    (void) path;
    +
    closedir(d->dp);
    +
    free(d);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    {
    +
    int res;
    +
    +
    if (S_ISFIFO(mode))
    +
    res = mkfifo(path, mode);
    +
    else
    +
    res = mknod(path, mode, rdev);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mkdir(const char *path, mode_t mode)
    +
    {
    +
    int res;
    +
    +
    res = mkdir(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_unlink(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = unlink(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rmdir(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = rmdir(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_symlink(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = symlink(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    /* When we have renameat2() in libc, then we can implement flags */
    +
    if (flags)
    +
    return -EINVAL;
    +
    +
    res = rename(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_link(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = link(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chmod(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = fchmod(fi->fh, mode);
    +
    else
    +
    res = chmod(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if (fi)
    +
    res = fchown(fi->fh, uid, gid);
    +
    else
    +
    res = lchown(path, uid, gid);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_truncate(const char *path, off_t size,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = ftruncate(fi->fh, size);
    +
    else
    +
    res = truncate(path, size);
    +
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_UTIMENSAT
    +
    static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    /* don't use utime/utimes since they follow symlinks */
    +
    if (fi)
    +
    res = futimens(fi->fh, ts);
    +
    else
    +
    res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags, mode);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file). */
    +
    if (fi->flags & O_DIRECT) {
    +
    fi->direct_io = 1;
    + +
    }
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pread(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    size_t size, off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec *src;
    +
    +
    (void) path;
    +
    +
    src = malloc(sizeof(struct fuse_bufvec));
    +
    if (src == NULL)
    +
    return -ENOMEM;
    +
    +
    *src = FUSE_BUFVEC_INIT(size);
    +
    + +
    src->buf[0].fd = fi->fh;
    +
    src->buf[0].pos = offset;
    +
    +
    *bufp = src;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_write(const char *path, const char *buf, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pwrite(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
    +
    +
    (void) path;
    +
    + +
    dst.buf[0].fd = fi->fh;
    +
    dst.buf[0].pos = offset;
    +
    + +
    }
    +
    +
    static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    {
    +
    int res;
    +
    +
    res = statvfs(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_flush(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    /* This is called from every close on an open file, so call the
    +
    close on the underlying filesystem. But since flush may be
    +
    called multiple times for an open file, this must not really
    +
    close the file. This is important if used on a network
    +
    filesystem like NFS which flush the data/metadata on close() */
    +
    res = close(dup(fi->fh));
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    close(fi->fh);
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fsync(const char *path, int isdatasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    #ifndef HAVE_FDATASYNC
    +
    (void) isdatasync;
    +
    #else
    +
    if (isdatasync)
    +
    res = fdatasync(fi->fh);
    +
    else
    +
    #endif
    +
    res = fsync(fi->fh);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_POSIX_FALLOCATE
    +
    static int xmp_fallocate(const char *path, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    +
    if (mode)
    +
    return -EOPNOTSUPP;
    +
    +
    return -posix_fallocate(fi->fh, offset, length);
    +
    }
    +
    #endif
    +
    +
    #ifdef HAVE_SETXATTR
    +
    /* xattr operations are optional and can safely be left unimplemented */
    +
    static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    size_t size, int flags)
    +
    {
    +
    int res = lsetxattr(path, name, value, size, flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    size_t size)
    +
    {
    +
    int res = lgetxattr(path, name, value, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    {
    +
    int res = llistxattr(path, list, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_removexattr(const char *path, const char *name)
    +
    {
    +
    int res = lremovexattr(path, name);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    #endif /* HAVE_SETXATTR */
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    struct flock *lock)
    +
    {
    +
    (void) path;
    +
    +
    return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
    +
    sizeof(fi->lock_owner));
    +
    }
    +
    #endif
    +
    +
    static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    res = flock(fi->fh, op);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static ssize_t xmp_copy_file_range(const char *path_in,
    +
    struct fuse_file_info *fi_in,
    +
    off_t off_in, const char *path_out,
    +
    struct fuse_file_info *fi_out,
    +
    off_t off_out, size_t len, int flags)
    +
    {
    +
    ssize_t res;
    +
    (void) path_in;
    +
    (void) path_out;
    +
    +
    res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    #endif
    +
    +
    static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    {
    +
    off_t res;
    +
    (void) path;
    +
    +
    res = lseek(fi->fh, off, whence);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static const struct fuse_operations xmp_oper = {
    +
    .init = xmp_init,
    +
    .getattr = xmp_getattr,
    +
    .access = xmp_access,
    +
    .readlink = xmp_readlink,
    +
    .opendir = xmp_opendir,
    +
    .readdir = xmp_readdir,
    +
    .releasedir = xmp_releasedir,
    +
    .mknod = xmp_mknod,
    +
    .mkdir = xmp_mkdir,
    +
    .symlink = xmp_symlink,
    +
    .unlink = xmp_unlink,
    +
    .rmdir = xmp_rmdir,
    +
    .rename = xmp_rename,
    +
    .link = xmp_link,
    +
    .chmod = xmp_chmod,
    +
    .chown = xmp_chown,
    +
    .truncate = xmp_truncate,
    +
    #ifdef HAVE_UTIMENSAT
    +
    .utimens = xmp_utimens,
    +
    #endif
    +
    .create = xmp_create,
    +
    .open = xmp_open,
    +
    .read = xmp_read,
    +
    .read_buf = xmp_read_buf,
    +
    .write = xmp_write,
    +
    .write_buf = xmp_write_buf,
    +
    .statfs = xmp_statfs,
    +
    .flush = xmp_flush,
    +
    .release = xmp_release,
    +
    .fsync = xmp_fsync,
    +
    #ifdef HAVE_POSIX_FALLOCATE
    +
    .fallocate = xmp_fallocate,
    +
    #endif
    +
    #ifdef HAVE_SETXATTR
    +
    .setxattr = xmp_setxattr,
    +
    .getxattr = xmp_getxattr,
    +
    .listxattr = xmp_listxattr,
    +
    .removexattr = xmp_removexattr,
    +
    #endif
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    .lock = xmp_lock,
    +
    #endif
    +
    .flock = xmp_flock,
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = xmp_copy_file_range,
    +
    #endif
    +
    .lseek = xmp_lseek,
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    umask(0);
    +
    return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    fuse_fill_dir_flags
    Definition fuse.h:58
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    @ FUSE_BUF_SPLICE_NONBLOCK
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    + + +
    struct fuse_buf buf[1]
    + +
    int32_t nullpath_ok
    Definition fuse.h:273
    +
    int32_t parallel_direct_writes
    Definition fuse.h:312
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + +
    uint64_t lock_owner
    + +
    uint32_t parallel_direct_writes
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    +

    Definition in file passthrough_fh.c.

    +
    + + + + diff --git a/doc/html/passthrough__fh_8c_source.html b/doc/html/passthrough__fh_8c_source.html new file mode 100644 index 0000000..ccbf078 --- /dev/null +++ b/doc/html/passthrough__fh_8c_source.html @@ -0,0 +1,752 @@ + + + + + + + +libfuse: example/passthrough_fh.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    passthrough_fh.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file COPYING.
    +
    8*/
    +
    9
    +
    26#define FUSE_USE_VERSION 31
    +
    27
    +
    28#define _GNU_SOURCE
    +
    29
    +
    30#include <fuse.h>
    +
    31
    +
    32#ifdef HAVE_LIBULOCKMGR
    +
    33#include <ulockmgr.h>
    +
    34#endif
    +
    35
    +
    36#include <stdio.h>
    +
    37#include <stdlib.h>
    +
    38#include <string.h>
    +
    39#include <unistd.h>
    +
    40#include <fcntl.h>
    +
    41#include <sys/stat.h>
    +
    42#include <dirent.h>
    +
    43#include <errno.h>
    +
    44#include <sys/time.h>
    +
    45#ifdef HAVE_SETXATTR
    +
    46#include <sys/xattr.h>
    +
    47#endif
    +
    48#include <sys/file.h> /* flock(2) */
    +
    49
    +
    50static void *xmp_init(struct fuse_conn_info *conn,
    +
    51 struct fuse_config *cfg)
    +
    52{
    +
    53 (void) conn;
    +
    54 cfg->use_ino = 1;
    +
    55 cfg->nullpath_ok = 1;
    +
    56
    +
    57 /* parallel_direct_writes feature depends on direct_io features.
    +
    58 To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    59 in current function (recommended in high level API) or set fi->direct_io
    +
    60 in xmp_create() or xmp_open(). */
    +
    61 // cfg->direct_io = 1;
    + +
    63
    +
    64 /* Pick up changes from lower filesystem right away. This is
    +
    65 also necessary for better hardlink support. When the kernel
    +
    66 calls the unlink() handler, it does not know the inode of
    +
    67 the to-be-removed entry and can therefore not invalidate
    +
    68 the cache of the associated inode - resulting in an
    +
    69 incorrect st_nlink value being reported for any remaining
    +
    70 hardlinks to this inode. */
    +
    71 cfg->entry_timeout = 0;
    +
    72 cfg->attr_timeout = 0;
    +
    73 cfg->negative_timeout = 0;
    +
    74
    +
    75 return NULL;
    +
    76}
    +
    77
    +
    78static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    79 struct fuse_file_info *fi)
    +
    80{
    +
    81 int res;
    +
    82
    +
    83 (void) path;
    +
    84
    +
    85 if(fi)
    +
    86 res = fstat(fi->fh, stbuf);
    +
    87 else
    +
    88 res = lstat(path, stbuf);
    +
    89 if (res == -1)
    +
    90 return -errno;
    +
    91
    +
    92 return 0;
    +
    93}
    +
    94
    +
    95static int xmp_access(const char *path, int mask)
    +
    96{
    +
    97 int res;
    +
    98
    +
    99 res = access(path, mask);
    +
    100 if (res == -1)
    +
    101 return -errno;
    +
    102
    +
    103 return 0;
    +
    104}
    +
    105
    +
    106static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    107{
    +
    108 int res;
    +
    109
    +
    110 res = readlink(path, buf, size - 1);
    +
    111 if (res == -1)
    +
    112 return -errno;
    +
    113
    +
    114 buf[res] = '\0';
    +
    115 return 0;
    +
    116}
    +
    117
    +
    118struct xmp_dirp {
    +
    119 DIR *dp;
    +
    120 struct dirent *entry;
    +
    121 off_t offset;
    +
    122};
    +
    123
    +
    124static int xmp_opendir(const char *path, struct fuse_file_info *fi)
    +
    125{
    +
    126 int res;
    +
    127 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
    +
    128 if (d == NULL)
    +
    129 return -ENOMEM;
    +
    130
    +
    131 d->dp = opendir(path);
    +
    132 if (d->dp == NULL) {
    +
    133 res = -errno;
    +
    134 free(d);
    +
    135 return res;
    +
    136 }
    +
    137 d->offset = 0;
    +
    138 d->entry = NULL;
    +
    139
    +
    140 fi->fh = (unsigned long) d;
    +
    141 return 0;
    +
    142}
    +
    143
    +
    144static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
    +
    145{
    +
    146 return (struct xmp_dirp *) (uintptr_t) fi->fh;
    +
    147}
    +
    148
    +
    149static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    150 off_t offset, struct fuse_file_info *fi,
    +
    151 enum fuse_readdir_flags flags)
    +
    152{
    +
    153 struct xmp_dirp *d = get_dirp(fi);
    +
    154
    +
    155 (void) path;
    +
    156 if (offset != d->offset) {
    +
    157#ifndef __FreeBSD__
    +
    158 seekdir(d->dp, offset);
    +
    159#else
    +
    160 /* Subtract the one that we add when calling
    +
    161 telldir() below */
    +
    162 seekdir(d->dp, offset-1);
    +
    163#endif
    +
    164 d->entry = NULL;
    +
    165 d->offset = offset;
    +
    166 }
    +
    167 while (1) {
    +
    168 struct stat st;
    +
    169 off_t nextoff;
    + +
    171
    +
    172 if (!d->entry) {
    +
    173 d->entry = readdir(d->dp);
    +
    174 if (!d->entry)
    +
    175 break;
    +
    176 }
    +
    177#ifdef HAVE_FSTATAT
    +
    178 if (flags & FUSE_READDIR_PLUS) {
    +
    179 int res;
    +
    180
    +
    181 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
    +
    182 AT_SYMLINK_NOFOLLOW);
    +
    183 if (res != -1)
    +
    184 fill_flags |= FUSE_FILL_DIR_PLUS;
    +
    185 }
    +
    186#endif
    +
    187 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
    +
    188 memset(&st, 0, sizeof(st));
    +
    189 st.st_ino = d->entry->d_ino;
    +
    190 st.st_mode = d->entry->d_type << 12;
    +
    191 }
    +
    192 nextoff = telldir(d->dp);
    +
    193#ifdef __FreeBSD__
    +
    194 /* Under FreeBSD, telldir() may return 0 the first time
    +
    195 it is called. But for libfuse, an offset of zero
    +
    196 means that offsets are not supported, so we shift
    +
    197 everything by one. */
    +
    198 nextoff++;
    +
    199#endif
    +
    200 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
    +
    201 break;
    +
    202
    +
    203 d->entry = NULL;
    +
    204 d->offset = nextoff;
    +
    205 }
    +
    206
    +
    207 return 0;
    +
    208}
    +
    209
    +
    210static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
    +
    211{
    +
    212 struct xmp_dirp *d = get_dirp(fi);
    +
    213 (void) path;
    +
    214 closedir(d->dp);
    +
    215 free(d);
    +
    216 return 0;
    +
    217}
    +
    218
    +
    219static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    220{
    +
    221 int res;
    +
    222
    +
    223 if (S_ISFIFO(mode))
    +
    224 res = mkfifo(path, mode);
    +
    225 else
    +
    226 res = mknod(path, mode, rdev);
    +
    227 if (res == -1)
    +
    228 return -errno;
    +
    229
    +
    230 return 0;
    +
    231}
    +
    232
    +
    233static int xmp_mkdir(const char *path, mode_t mode)
    +
    234{
    +
    235 int res;
    +
    236
    +
    237 res = mkdir(path, mode);
    +
    238 if (res == -1)
    +
    239 return -errno;
    +
    240
    +
    241 return 0;
    +
    242}
    +
    243
    +
    244static int xmp_unlink(const char *path)
    +
    245{
    +
    246 int res;
    +
    247
    +
    248 res = unlink(path);
    +
    249 if (res == -1)
    +
    250 return -errno;
    +
    251
    +
    252 return 0;
    +
    253}
    +
    254
    +
    255static int xmp_rmdir(const char *path)
    +
    256{
    +
    257 int res;
    +
    258
    +
    259 res = rmdir(path);
    +
    260 if (res == -1)
    +
    261 return -errno;
    +
    262
    +
    263 return 0;
    +
    264}
    +
    265
    +
    266static int xmp_symlink(const char *from, const char *to)
    +
    267{
    +
    268 int res;
    +
    269
    +
    270 res = symlink(from, to);
    +
    271 if (res == -1)
    +
    272 return -errno;
    +
    273
    +
    274 return 0;
    +
    275}
    +
    276
    +
    277static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    278{
    +
    279 int res;
    +
    280
    +
    281 /* When we have renameat2() in libc, then we can implement flags */
    +
    282 if (flags)
    +
    283 return -EINVAL;
    +
    284
    +
    285 res = rename(from, to);
    +
    286 if (res == -1)
    +
    287 return -errno;
    +
    288
    +
    289 return 0;
    +
    290}
    +
    291
    +
    292static int xmp_link(const char *from, const char *to)
    +
    293{
    +
    294 int res;
    +
    295
    +
    296 res = link(from, to);
    +
    297 if (res == -1)
    +
    298 return -errno;
    +
    299
    +
    300 return 0;
    +
    301}
    +
    302
    +
    303static int xmp_chmod(const char *path, mode_t mode,
    +
    304 struct fuse_file_info *fi)
    +
    305{
    +
    306 int res;
    +
    307
    +
    308 if(fi)
    +
    309 res = fchmod(fi->fh, mode);
    +
    310 else
    +
    311 res = chmod(path, mode);
    +
    312 if (res == -1)
    +
    313 return -errno;
    +
    314
    +
    315 return 0;
    +
    316}
    +
    317
    +
    318static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    319 struct fuse_file_info *fi)
    +
    320{
    +
    321 int res;
    +
    322
    +
    323 if (fi)
    +
    324 res = fchown(fi->fh, uid, gid);
    +
    325 else
    +
    326 res = lchown(path, uid, gid);
    +
    327 if (res == -1)
    +
    328 return -errno;
    +
    329
    +
    330 return 0;
    +
    331}
    +
    332
    +
    333static int xmp_truncate(const char *path, off_t size,
    +
    334 struct fuse_file_info *fi)
    +
    335{
    +
    336 int res;
    +
    337
    +
    338 if(fi)
    +
    339 res = ftruncate(fi->fh, size);
    +
    340 else
    +
    341 res = truncate(path, size);
    +
    342
    +
    343 if (res == -1)
    +
    344 return -errno;
    +
    345
    +
    346 return 0;
    +
    347}
    +
    348
    +
    349#ifdef HAVE_UTIMENSAT
    +
    350static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    351 struct fuse_file_info *fi)
    +
    352{
    +
    353 int res;
    +
    354
    +
    355 /* don't use utime/utimes since they follow symlinks */
    +
    356 if (fi)
    +
    357 res = futimens(fi->fh, ts);
    +
    358 else
    +
    359 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    360 if (res == -1)
    +
    361 return -errno;
    +
    362
    +
    363 return 0;
    +
    364}
    +
    365#endif
    +
    366
    +
    367static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    368{
    +
    369 int fd;
    +
    370
    +
    371 fd = open(path, fi->flags, mode);
    +
    372 if (fd == -1)
    +
    373 return -errno;
    +
    374
    +
    375 fi->fh = fd;
    +
    376 return 0;
    +
    377}
    +
    378
    +
    379static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    380{
    +
    381 int fd;
    +
    382
    +
    383 fd = open(path, fi->flags);
    +
    384 if (fd == -1)
    +
    385 return -errno;
    +
    386
    +
    387 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    388 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    389 for writes to the same file). */
    +
    390 if (fi->flags & O_DIRECT) {
    +
    391 fi->direct_io = 1;
    + +
    393 }
    +
    394
    +
    395 fi->fh = fd;
    +
    396 return 0;
    +
    397}
    +
    398
    +
    399static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    400 struct fuse_file_info *fi)
    +
    401{
    +
    402 int res;
    +
    403
    +
    404 (void) path;
    +
    405 res = pread(fi->fh, buf, size, offset);
    +
    406 if (res == -1)
    +
    407 res = -errno;
    +
    408
    +
    409 return res;
    +
    410}
    +
    411
    +
    412static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    413 size_t size, off_t offset, struct fuse_file_info *fi)
    +
    414{
    +
    415 struct fuse_bufvec *src;
    +
    416
    +
    417 (void) path;
    +
    418
    +
    419 src = malloc(sizeof(struct fuse_bufvec));
    +
    420 if (src == NULL)
    +
    421 return -ENOMEM;
    +
    422
    +
    423 *src = FUSE_BUFVEC_INIT(size);
    +
    424
    + +
    426 src->buf[0].fd = fi->fh;
    +
    427 src->buf[0].pos = offset;
    +
    428
    +
    429 *bufp = src;
    +
    430
    +
    431 return 0;
    +
    432}
    +
    433
    +
    434static int xmp_write(const char *path, const char *buf, size_t size,
    +
    435 off_t offset, struct fuse_file_info *fi)
    +
    436{
    +
    437 int res;
    +
    438
    +
    439 (void) path;
    +
    440 res = pwrite(fi->fh, buf, size, offset);
    +
    441 if (res == -1)
    +
    442 res = -errno;
    +
    443
    +
    444 return res;
    +
    445}
    +
    446
    +
    447static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    448 off_t offset, struct fuse_file_info *fi)
    +
    449{
    +
    450 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
    +
    451
    +
    452 (void) path;
    +
    453
    + +
    455 dst.buf[0].fd = fi->fh;
    +
    456 dst.buf[0].pos = offset;
    +
    457
    + +
    459}
    +
    460
    +
    461static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    462{
    +
    463 int res;
    +
    464
    +
    465 res = statvfs(path, stbuf);
    +
    466 if (res == -1)
    +
    467 return -errno;
    +
    468
    +
    469 return 0;
    +
    470}
    +
    471
    +
    472static int xmp_flush(const char *path, struct fuse_file_info *fi)
    +
    473{
    +
    474 int res;
    +
    475
    +
    476 (void) path;
    +
    477 /* This is called from every close on an open file, so call the
    +
    478 close on the underlying filesystem. But since flush may be
    +
    479 called multiple times for an open file, this must not really
    +
    480 close the file. This is important if used on a network
    +
    481 filesystem like NFS which flush the data/metadata on close() */
    +
    482 res = close(dup(fi->fh));
    +
    483 if (res == -1)
    +
    484 return -errno;
    +
    485
    +
    486 return 0;
    +
    487}
    +
    488
    +
    489static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    490{
    +
    491 (void) path;
    +
    492 close(fi->fh);
    +
    493
    +
    494 return 0;
    +
    495}
    +
    496
    +
    497static int xmp_fsync(const char *path, int isdatasync,
    +
    498 struct fuse_file_info *fi)
    +
    499{
    +
    500 int res;
    +
    501 (void) path;
    +
    502
    +
    503#ifndef HAVE_FDATASYNC
    +
    504 (void) isdatasync;
    +
    505#else
    +
    506 if (isdatasync)
    +
    507 res = fdatasync(fi->fh);
    +
    508 else
    +
    509#endif
    +
    510 res = fsync(fi->fh);
    +
    511 if (res == -1)
    +
    512 return -errno;
    +
    513
    +
    514 return 0;
    +
    515}
    +
    516
    +
    517#ifdef HAVE_POSIX_FALLOCATE
    +
    518static int xmp_fallocate(const char *path, int mode,
    +
    519 off_t offset, off_t length, struct fuse_file_info *fi)
    +
    520{
    +
    521 (void) path;
    +
    522
    +
    523 if (mode)
    +
    524 return -EOPNOTSUPP;
    +
    525
    +
    526 return -posix_fallocate(fi->fh, offset, length);
    +
    527}
    +
    528#endif
    +
    529
    +
    530#ifdef HAVE_SETXATTR
    +
    531/* xattr operations are optional and can safely be left unimplemented */
    +
    532static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    533 size_t size, int flags)
    +
    534{
    +
    535 int res = lsetxattr(path, name, value, size, flags);
    +
    536 if (res == -1)
    +
    537 return -errno;
    +
    538 return 0;
    +
    539}
    +
    540
    +
    541static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    542 size_t size)
    +
    543{
    +
    544 int res = lgetxattr(path, name, value, size);
    +
    545 if (res == -1)
    +
    546 return -errno;
    +
    547 return res;
    +
    548}
    +
    549
    +
    550static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    551{
    +
    552 int res = llistxattr(path, list, size);
    +
    553 if (res == -1)
    +
    554 return -errno;
    +
    555 return res;
    +
    556}
    +
    557
    +
    558static int xmp_removexattr(const char *path, const char *name)
    +
    559{
    +
    560 int res = lremovexattr(path, name);
    +
    561 if (res == -1)
    +
    562 return -errno;
    +
    563 return 0;
    +
    564}
    +
    565#endif /* HAVE_SETXATTR */
    +
    566
    +
    567#ifdef HAVE_LIBULOCKMGR
    +
    568static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    569 struct flock *lock)
    +
    570{
    +
    571 (void) path;
    +
    572
    +
    573 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
    +
    574 sizeof(fi->lock_owner));
    +
    575}
    +
    576#endif
    +
    577
    +
    578static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    579{
    +
    580 int res;
    +
    581 (void) path;
    +
    582
    +
    583 res = flock(fi->fh, op);
    +
    584 if (res == -1)
    +
    585 return -errno;
    +
    586
    +
    587 return 0;
    +
    588}
    +
    589
    +
    590#ifdef HAVE_COPY_FILE_RANGE
    +
    591static ssize_t xmp_copy_file_range(const char *path_in,
    +
    592 struct fuse_file_info *fi_in,
    +
    593 off_t off_in, const char *path_out,
    +
    594 struct fuse_file_info *fi_out,
    +
    595 off_t off_out, size_t len, int flags)
    +
    596{
    +
    597 ssize_t res;
    +
    598 (void) path_in;
    +
    599 (void) path_out;
    +
    600
    +
    601 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    602 flags);
    +
    603 if (res == -1)
    +
    604 return -errno;
    +
    605
    +
    606 return res;
    +
    607}
    +
    608#endif
    +
    609
    +
    610static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    611{
    +
    612 off_t res;
    +
    613 (void) path;
    +
    614
    +
    615 res = lseek(fi->fh, off, whence);
    +
    616 if (res == -1)
    +
    617 return -errno;
    +
    618
    +
    619 return res;
    +
    620}
    +
    621
    +
    622static const struct fuse_operations xmp_oper = {
    +
    623 .init = xmp_init,
    +
    624 .getattr = xmp_getattr,
    +
    625 .access = xmp_access,
    +
    626 .readlink = xmp_readlink,
    +
    627 .opendir = xmp_opendir,
    +
    628 .readdir = xmp_readdir,
    +
    629 .releasedir = xmp_releasedir,
    +
    630 .mknod = xmp_mknod,
    +
    631 .mkdir = xmp_mkdir,
    +
    632 .symlink = xmp_symlink,
    +
    633 .unlink = xmp_unlink,
    +
    634 .rmdir = xmp_rmdir,
    +
    635 .rename = xmp_rename,
    +
    636 .link = xmp_link,
    +
    637 .chmod = xmp_chmod,
    +
    638 .chown = xmp_chown,
    +
    639 .truncate = xmp_truncate,
    +
    640#ifdef HAVE_UTIMENSAT
    +
    641 .utimens = xmp_utimens,
    +
    642#endif
    +
    643 .create = xmp_create,
    +
    644 .open = xmp_open,
    +
    645 .read = xmp_read,
    +
    646 .read_buf = xmp_read_buf,
    +
    647 .write = xmp_write,
    +
    648 .write_buf = xmp_write_buf,
    +
    649 .statfs = xmp_statfs,
    +
    650 .flush = xmp_flush,
    +
    651 .release = xmp_release,
    +
    652 .fsync = xmp_fsync,
    +
    653#ifdef HAVE_POSIX_FALLOCATE
    +
    654 .fallocate = xmp_fallocate,
    +
    655#endif
    +
    656#ifdef HAVE_SETXATTR
    +
    657 .setxattr = xmp_setxattr,
    +
    658 .getxattr = xmp_getxattr,
    +
    659 .listxattr = xmp_listxattr,
    +
    660 .removexattr = xmp_removexattr,
    +
    661#endif
    +
    662#ifdef HAVE_LIBULOCKMGR
    +
    663 .lock = xmp_lock,
    +
    664#endif
    +
    665 .flock = xmp_flock,
    +
    666#ifdef HAVE_COPY_FILE_RANGE
    +
    667 .copy_file_range = xmp_copy_file_range,
    +
    668#endif
    +
    669 .lseek = xmp_lseek,
    +
    670};
    +
    671
    +
    672int main(int argc, char *argv[])
    +
    673{
    +
    674 umask(0);
    +
    675 return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    676}
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    fuse_fill_dir_flags
    Definition fuse.h:58
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    @ FUSE_BUF_SPLICE_NONBLOCK
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    + + +
    struct fuse_buf buf[1]
    + +
    int32_t nullpath_ok
    Definition fuse.h:273
    +
    int32_t parallel_direct_writes
    Definition fuse.h:312
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + +
    uint64_t lock_owner
    + +
    uint32_t parallel_direct_writes
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    + + + + diff --git a/doc/html/passthrough__helpers_8h_source.html b/doc/html/passthrough__helpers_8h_source.html new file mode 100644 index 0000000..d0a8a59 --- /dev/null +++ b/doc/html/passthrough__helpers_8h_source.html @@ -0,0 +1,136 @@ + + + + + + + +libfuse: example/passthrough_helpers.h Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    passthrough_helpers.h
    +
    +
    +
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 *
    +
    4 * Redistribution and use in source and binary forms, with or without
    +
    5 * modification, are permitted provided that the following conditions
    +
    6 * are met:
    +
    7 * 1. Redistributions of source code must retain the above copyright
    +
    8 * notice, this list of conditions and the following disclaimer.
    +
    9 * 2. Redistributions in binary form must reproduce the above copyright
    +
    10 * notice, this list of conditions and the following disclaimer in the
    +
    11 * documentation and/or other materials provided with the distribution.
    +
    12 *
    +
    13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
    +
    14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +
    15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    +
    16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
    +
    17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +
    18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    +
    19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    +
    20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    +
    21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    +
    22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    +
    23 * SUCH DAMAGE
    +
    24 */
    +
    25
    +
    26/*
    +
    27 * Creates files on the underlying file system in response to a FUSE_MKNOD
    +
    28 * operation
    +
    29 */
    +
    30static int mknod_wrapper(int dirfd, const char *path, const char *link,
    +
    31 int mode, dev_t rdev)
    +
    32{
    +
    33 int res;
    +
    34
    +
    35 if (S_ISREG(mode)) {
    +
    36 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
    +
    37 if (res >= 0)
    +
    38 res = close(res);
    +
    39 } else if (S_ISDIR(mode)) {
    +
    40 res = mkdirat(dirfd, path, mode);
    +
    41 } else if (S_ISLNK(mode) && link != NULL) {
    +
    42 res = symlinkat(link, dirfd, path);
    +
    43 } else if (S_ISFIFO(mode)) {
    +
    44 res = mkfifoat(dirfd, path, mode);
    +
    45#ifdef __FreeBSD__
    +
    46 } else if (S_ISSOCK(mode)) {
    +
    47 struct sockaddr_un su;
    +
    48 int fd;
    +
    49
    +
    50 if (strlen(path) >= sizeof(su.sun_path)) {
    +
    51 errno = ENAMETOOLONG;
    +
    52 return -1;
    +
    53 }
    +
    54 fd = socket(AF_UNIX, SOCK_STREAM, 0);
    +
    55 if (fd >= 0) {
    +
    56 /*
    +
    57 * We must bind the socket to the underlying file
    +
    58 * system to create the socket file, even though
    +
    59 * we'll never listen on this socket.
    +
    60 */
    +
    61 su.sun_family = AF_UNIX;
    +
    62 strncpy(su.sun_path, path, sizeof(su.sun_path));
    +
    63 res = bindat(dirfd, fd, (struct sockaddr*)&su,
    +
    64 sizeof(su));
    +
    65 if (res == 0)
    +
    66 close(fd);
    +
    67 } else {
    +
    68 res = -1;
    +
    69 }
    +
    70#endif
    +
    71 } else {
    +
    72 res = mknodat(dirfd, path, mode, rdev);
    +
    73 }
    +
    74
    +
    75 return res;
    +
    76}
    +
    + + + + diff --git a/doc/html/passthrough__ll_8c.html b/doc/html/passthrough__ll_8c.html new file mode 100644 index 0000000..cda4c0d --- /dev/null +++ b/doc/html/passthrough__ll_8c.html @@ -0,0 +1,1530 @@ + + + + + + + +libfuse: example/passthrough_ll.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    passthrough_ll.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <unistd.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <stddef.h>
    +#include <stdbool.h>
    +#include <string.h>
    +#include <limits.h>
    +#include <dirent.h>
    +#include <assert.h>
    +#include <errno.h>
    +#include <inttypes.h>
    +#include <pthread.h>
    +#include <sys/file.h>
    +#include <sys/xattr.h>
    +#include "passthrough_helpers.h"
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three, but many operations are not implemented. In particular, it is not possible to remove files (or directories) because the code necessary to defer actual removal until the file is not opened anymore would make the example much more complicated.

    +

    When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

    +

    Compile with:

    gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define _GNU_SOURCE
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <unistd.h>
    +
    #include <stdlib.h>
    +
    #include <stdio.h>
    +
    #include <stddef.h>
    +
    #include <stdbool.h>
    +
    #include <string.h>
    +
    #include <limits.h>
    +
    #include <dirent.h>
    +
    #include <assert.h>
    +
    #include <errno.h>
    +
    #include <inttypes.h>
    +
    #include <pthread.h>
    +
    #include <sys/file.h>
    +
    #include <sys/xattr.h>
    +
    +
    #include "passthrough_helpers.h"
    +
    +
    /* We are re-using pointers to our `struct lo_inode` and `struct
    +
    lo_dirp` elements as inodes. This means that we must be able to
    +
    store uintptr_t values in a fuse_ino_t variable. The following
    +
    incantation checks this condition at compile time. */
    +
    #if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
    +
    _Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
    +
    "fuse_ino_t too small to hold uintptr_t values!");
    +
    #else
    +
    struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
    +
    { unsigned _uintptr_to_must_hold_fuse_ino_t:
    +
    ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
    +
    #endif
    +
    +
    struct lo_inode {
    +
    struct lo_inode *next; /* protected by lo->mutex */
    +
    struct lo_inode *prev; /* protected by lo->mutex */
    +
    int fd;
    +
    ino_t ino;
    +
    dev_t dev;
    +
    uint64_t refcount; /* protected by lo->mutex */
    +
    };
    +
    +
    enum {
    +
    CACHE_NEVER,
    +
    CACHE_NORMAL,
    +
    CACHE_ALWAYS,
    +
    };
    +
    +
    struct lo_data {
    +
    pthread_mutex_t mutex;
    +
    int debug;
    +
    int writeback;
    +
    int flock;
    +
    int xattr;
    +
    char *source;
    +
    double timeout;
    +
    int cache;
    +
    int timeout_set;
    +
    struct lo_inode root; /* protected by lo->mutex */
    +
    };
    +
    +
    static const struct fuse_opt lo_opts[] = {
    +
    { "writeback",
    +
    offsetof(struct lo_data, writeback), 1 },
    +
    { "no_writeback",
    +
    offsetof(struct lo_data, writeback), 0 },
    +
    { "source=%s",
    +
    offsetof(struct lo_data, source), 0 },
    +
    { "flock",
    +
    offsetof(struct lo_data, flock), 1 },
    +
    { "no_flock",
    +
    offsetof(struct lo_data, flock), 0 },
    +
    { "xattr",
    +
    offsetof(struct lo_data, xattr), 1 },
    +
    { "no_xattr",
    +
    offsetof(struct lo_data, xattr), 0 },
    +
    { "timeout=%lf",
    +
    offsetof(struct lo_data, timeout), 0 },
    +
    { "timeout=",
    +
    offsetof(struct lo_data, timeout_set), 1 },
    +
    { "cache=never",
    +
    offsetof(struct lo_data, cache), CACHE_NEVER },
    +
    { "cache=auto",
    +
    offsetof(struct lo_data, cache), CACHE_NORMAL },
    +
    { "cache=always",
    +
    offsetof(struct lo_data, cache), CACHE_ALWAYS },
    +
    + +
    };
    +
    +
    static void passthrough_ll_help(void)
    +
    {
    +
    printf(
    +
    " -o writeback Enable writeback\n"
    +
    " -o no_writeback Disable write back\n"
    +
    " -o source=/home/dir Source directory to be mounted\n"
    +
    " -o flock Enable flock\n"
    +
    " -o no_flock Disable flock\n"
    +
    " -o xattr Enable xattr\n"
    +
    " -o no_xattr Disable xattr\n"
    +
    " -o timeout=1.0 Caching timeout\n"
    +
    " -o timeout=0/1 Timeout is set\n"
    +
    " -o cache=never Disable cache\n"
    +
    " -o cache=auto Auto enable cache\n"
    +
    " -o cache=always Cache always\n");
    +
    }
    +
    +
    static struct lo_data *lo_data(fuse_req_t req)
    +
    {
    +
    return (struct lo_data *) fuse_req_userdata(req);
    +
    }
    +
    +
    static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    if (ino == FUSE_ROOT_ID)
    +
    return &lo_data(req)->root;
    +
    else
    +
    return (struct lo_inode *) (uintptr_t) ino;
    +
    }
    +
    +
    static int lo_fd(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    return lo_inode(req, ino)->fd;
    +
    }
    +
    +
    static bool lo_debug(fuse_req_t req)
    +
    {
    +
    return lo_data(req)->debug != 0;
    +
    }
    +
    +
    static void lo_init(void *userdata,
    +
    struct fuse_conn_info *conn)
    +
    {
    +
    struct lo_data *lo = (struct lo_data *)userdata;
    +
    bool has_flag;
    +
    +
    if (lo->writeback) {
    +
    has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
    +
    if (lo->debug && has_flag)
    +
    fuse_log(FUSE_LOG_DEBUG,
    +
    "lo_init: activating writeback\n");
    +
    }
    +
    if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
    +
    has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
    +
    if (lo->debug && has_flag)
    +
    fuse_log(FUSE_LOG_DEBUG,
    +
    "lo_init: activating flock locks\n");
    +
    }
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void lo_destroy(void *userdata)
    +
    {
    +
    struct lo_data *lo = (struct lo_data*) userdata;
    +
    +
    while (lo->root.next != &lo->root) {
    +
    struct lo_inode* next = lo->root.next;
    +
    lo->root.next = next->next;
    +
    close(next->fd);
    +
    free(next);
    +
    }
    +
    }
    +
    +
    static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    struct stat buf;
    +
    struct lo_data *lo = lo_data(req);
    +
    int fd = fi ? fi->fh : lo_fd(req, ino);
    +
    +
    (void) fi;
    +
    +
    res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fuse_reply_attr(req, &buf, lo->timeout);
    +
    }
    +
    +
    static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    int valid, struct fuse_file_info *fi)
    +
    {
    +
    int saverr;
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    int ifd = inode->fd;
    +
    int res;
    +
    +
    if (valid & FUSE_SET_ATTR_MODE) {
    +
    if (fi) {
    +
    res = fchmod(fi->fh, attr->st_mode);
    +
    } else {
    +
    sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    res = chmod(procname, attr->st_mode);
    +
    }
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
    +
    uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
    +
    attr->st_uid : (uid_t) -1;
    +
    gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
    +
    attr->st_gid : (gid_t) -1;
    +
    +
    res = fchownat(ifd, "", uid, gid,
    +
    AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    if (valid & FUSE_SET_ATTR_SIZE) {
    +
    if (fi) {
    +
    res = ftruncate(fi->fh, attr->st_size);
    +
    } else {
    +
    sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    res = truncate(procname, attr->st_size);
    +
    }
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
    +
    struct timespec tv[2];
    +
    +
    tv[0].tv_sec = 0;
    +
    tv[1].tv_sec = 0;
    +
    tv[0].tv_nsec = UTIME_OMIT;
    +
    tv[1].tv_nsec = UTIME_OMIT;
    +
    +
    if (valid & FUSE_SET_ATTR_ATIME_NOW)
    +
    tv[0].tv_nsec = UTIME_NOW;
    +
    else if (valid & FUSE_SET_ATTR_ATIME)
    +
    tv[0] = attr->st_atim;
    +
    +
    if (valid & FUSE_SET_ATTR_MTIME_NOW)
    +
    tv[1].tv_nsec = UTIME_NOW;
    +
    else if (valid & FUSE_SET_ATTR_MTIME)
    +
    tv[1] = attr->st_mtim;
    +
    +
    if (fi)
    +
    res = futimens(fi->fh, tv);
    +
    else {
    +
    sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    res = utimensat(AT_FDCWD, procname, tv, 0);
    +
    }
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    +
    return lo_getattr(req, ino, fi);
    +
    +
    out_err:
    +
    saverr = errno;
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
    +
    {
    +
    struct lo_inode *p;
    +
    struct lo_inode *ret = NULL;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    for (p = lo->root.next; p != &lo->root; p = p->next) {
    +
    if (p->ino == st->st_ino && p->dev == st->st_dev) {
    +
    assert(p->refcount > 0);
    +
    ret = p;
    +
    ret->refcount++;
    +
    break;
    +
    }
    +
    }
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    return ret;
    +
    }
    +
    +
    +
    static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
    +
    {
    +
    struct lo_inode *inode = NULL;
    +
    struct lo_inode *prev, *next;
    +
    +
    inode = calloc(1, sizeof(struct lo_inode));
    +
    if (!inode)
    +
    return NULL;
    +
    +
    inode->refcount = 1;
    +
    inode->fd = fd;
    +
    inode->ino = e->attr.st_ino;
    +
    inode->dev = e->attr.st_dev;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    prev = &lo->root;
    +
    next = prev->next;
    +
    next->prev = inode;
    +
    inode->next = next;
    +
    inode->prev = prev;
    +
    prev->next = inode;
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    return inode;
    +
    }
    +
    +
    static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
    +
    {
    +
    int res;
    +
    struct lo_data *lo = lo_data(req);
    +
    +
    memset(e, 0, sizeof(*e));
    +
    e->attr_timeout = lo->timeout;
    +
    e->entry_timeout = lo->timeout;
    +
    +
    res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return errno;
    +
    +
    e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
    +
    (unsigned long long) parent, fd, (unsigned long long) e->ino);
    +
    +
    return 0;
    +
    +
    }
    +
    +
    static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    struct fuse_entry_param *e)
    +
    {
    +
    int newfd;
    +
    int res;
    +
    int saverr;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_inode *inode;
    +
    +
    memset(e, 0, sizeof(*e));
    +
    e->attr_timeout = lo->timeout;
    +
    e->entry_timeout = lo->timeout;
    +
    +
    newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
    +
    if (newfd == -1)
    +
    goto out_err;
    +
    +
    res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    +
    inode = lo_find(lo_data(req), &e->attr);
    +
    if (inode) {
    +
    close(newfd);
    +
    newfd = -1;
    +
    } else {
    +
    inode = create_new_inode(newfd, e, lo);
    +
    if (!inode)
    +
    goto out_err;
    +
    }
    +
    e->ino = (uintptr_t) inode;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    (unsigned long long) parent, name, (unsigned long long) e->ino);
    +
    +
    return 0;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    if (newfd != -1)
    +
    close(newfd);
    +
    return saverr;
    +
    }
    +
    +
    static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    struct fuse_entry_param e;
    +
    int err;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
    +
    parent, name);
    +
    +
    err = lo_do_lookup(req, parent, name, &e);
    +
    if (err)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_entry(req, &e);
    +
    }
    +
    +
    static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
    +
    const char *name, mode_t mode, dev_t rdev,
    +
    const char *link)
    +
    {
    +
    int res;
    +
    int saverr;
    +
    struct lo_inode *dir = lo_inode(req, parent);
    +
    struct fuse_entry_param e;
    +
    +
    res = mknod_wrapper(dir->fd, name, link, mode, rdev);
    +
    +
    saverr = errno;
    +
    if (res == -1)
    +
    goto out;
    +
    +
    saverr = lo_do_lookup(req, parent, name, &e);
    +
    if (saverr)
    +
    goto out;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    (unsigned long long) parent, name, (unsigned long long) e.ino);
    +
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
    +
    const char *name, mode_t mode, dev_t rdev)
    +
    {
    +
    lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
    +
    }
    +
    +
    static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    mode_t mode)
    +
    {
    +
    lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
    +
    }
    +
    +
    static void lo_symlink(fuse_req_t req, const char *link,
    +
    fuse_ino_t parent, const char *name)
    +
    {
    +
    lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
    +
    }
    +
    +
    static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
    +
    const char *name)
    +
    {
    +
    int res;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    struct fuse_entry_param e;
    +
    char procname[64];
    +
    int saverr;
    +
    +
    memset(&e, 0, sizeof(struct fuse_entry_param));
    +
    e.attr_timeout = lo->timeout;
    +
    e.entry_timeout = lo->timeout;
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
    +
    AT_SYMLINK_FOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    +
    res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    inode->refcount++;
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    e.ino = (uintptr_t) inode;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    (unsigned long long) parent, name,
    +
    (unsigned long long) e.ino);
    +
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    int res;
    +
    +
    res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    fuse_ino_t newparent, const char *newname,
    +
    unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    if (flags) {
    +
    fuse_reply_err(req, EINVAL);
    +
    return;
    +
    }
    +
    +
    res = renameat(lo_fd(req, parent), name,
    +
    lo_fd(req, newparent), newname);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    int res;
    +
    +
    res = unlinkat(lo_fd(req, parent), name, 0);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
    +
    {
    +
    if (!inode)
    +
    return;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    assert(inode->refcount >= n);
    +
    inode->refcount -= n;
    +
    if (!inode->refcount) {
    +
    struct lo_inode *prev, *next;
    +
    +
    prev = inode->prev;
    +
    next = inode->next;
    +
    next->prev = prev;
    +
    prev->next = next;
    +
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    close(inode->fd);
    +
    free(inode);
    +
    +
    } else {
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    }
    +
    }
    +
    +
    static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    {
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
    +
    (unsigned long long) ino,
    +
    (unsigned long long) inode->refcount,
    +
    (unsigned long long) nlookup);
    +
    }
    +
    +
    unref_inode(lo, inode, nlookup);
    +
    }
    +
    +
    static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    {
    +
    lo_forget_one(req, ino, nlookup);
    + +
    }
    +
    +
    static void lo_forget_multi(fuse_req_t req, size_t count,
    +
    struct fuse_forget_data *forgets)
    +
    {
    +
    int i;
    +
    +
    for (i = 0; i < count; i++)
    +
    lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
    + +
    }
    +
    +
    static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    char buf[PATH_MAX + 1];
    +
    int res;
    +
    +
    res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
    +
    if (res == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    if (res == sizeof(buf))
    +
    return (void) fuse_reply_err(req, ENAMETOOLONG);
    +
    +
    buf[res] = '\0';
    +
    + +
    }
    +
    +
    struct lo_dirp {
    +
    DIR *dp;
    +
    struct dirent *entry;
    +
    off_t offset;
    +
    };
    +
    +
    static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
    +
    {
    +
    return (struct lo_dirp *) (uintptr_t) fi->fh;
    +
    }
    +
    +
    static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    int error = ENOMEM;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_dirp *d;
    +
    int fd = -1;
    +
    +
    d = calloc(1, sizeof(struct lo_dirp));
    +
    if (d == NULL)
    +
    goto out_err;
    +
    +
    fd = openat(lo_fd(req, ino), ".", O_RDONLY);
    +
    if (fd == -1)
    +
    goto out_errno;
    +
    +
    d->dp = fdopendir(fd);
    +
    if (d->dp == NULL)
    +
    goto out_errno;
    +
    +
    d->offset = 0;
    +
    d->entry = NULL;
    +
    +
    fi->fh = (uintptr_t) d;
    +
    if (lo->cache != CACHE_NEVER)
    +
    fi->cache_readdir = 1;
    +
    if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    fuse_reply_open(req, fi);
    +
    return;
    +
    +
    out_errno:
    +
    error = errno;
    +
    out_err:
    +
    if (d) {
    +
    if (fd != -1)
    +
    close(fd);
    +
    free(d);
    +
    }
    +
    fuse_reply_err(req, error);
    +
    }
    +
    +
    static int is_dot_or_dotdot(const char *name)
    +
    {
    +
    return name[0] == '.' && (name[1] == '\0' ||
    +
    (name[1] == '.' && name[2] == '\0'));
    +
    }
    +
    +
    static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi, int plus)
    +
    {
    +
    struct lo_dirp *d = lo_dirp(fi);
    +
    char *buf;
    +
    char *p;
    +
    size_t rem = size;
    +
    int err;
    +
    +
    (void) ino;
    +
    +
    buf = calloc(1, size);
    +
    if (!buf) {
    +
    err = ENOMEM;
    +
    goto error;
    +
    }
    +
    p = buf;
    +
    +
    if (offset != d->offset) {
    +
    seekdir(d->dp, offset);
    +
    d->entry = NULL;
    +
    d->offset = offset;
    +
    }
    +
    while (1) {
    +
    size_t entsize;
    +
    off_t nextoff;
    +
    const char *name;
    +
    +
    if (!d->entry) {
    +
    errno = 0;
    +
    d->entry = readdir(d->dp);
    +
    if (!d->entry) {
    +
    if (errno) { // Error
    +
    err = errno;
    +
    goto error;
    +
    } else { // End of stream
    +
    break;
    +
    }
    +
    }
    +
    }
    +
    nextoff = d->entry->d_off;
    +
    name = d->entry->d_name;
    +
    fuse_ino_t entry_ino = 0;
    +
    if (plus) {
    +
    struct fuse_entry_param e;
    +
    if (is_dot_or_dotdot(name)) {
    +
    e = (struct fuse_entry_param) {
    +
    .attr.st_ino = d->entry->d_ino,
    +
    .attr.st_mode = d->entry->d_type << 12,
    +
    };
    +
    } else {
    +
    err = lo_do_lookup(req, ino, name, &e);
    +
    if (err)
    +
    goto error;
    +
    entry_ino = e.ino;
    +
    }
    +
    +
    entsize = fuse_add_direntry_plus(req, p, rem, name,
    +
    &e, nextoff);
    +
    } else {
    +
    struct stat st = {
    +
    .st_ino = d->entry->d_ino,
    +
    .st_mode = d->entry->d_type << 12,
    +
    };
    +
    entsize = fuse_add_direntry(req, p, rem, name,
    +
    &st, nextoff);
    +
    }
    +
    if (entsize > rem) {
    +
    if (entry_ino != 0)
    +
    lo_forget_one(req, entry_ino, 1);
    +
    break;
    +
    }
    +
    +
    p += entsize;
    +
    rem -= entsize;
    +
    +
    d->entry = NULL;
    +
    d->offset = nextoff;
    +
    }
    +
    +
    err = 0;
    +
    error:
    +
    // If there's an error, we can only signal it if we haven't stored
    +
    // any entries yet - otherwise we'd end up with wrong lookup
    +
    // counts for the entries that are already in the buffer. So we
    +
    // return what we've collected until that point.
    +
    if (err && rem == size)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_buf(req, buf, size - rem);
    +
    free(buf);
    +
    }
    +
    +
    static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    lo_do_readdir(req, ino, size, offset, fi, 0);
    +
    }
    +
    +
    static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    lo_do_readdir(req, ino, size, offset, fi, 1);
    +
    }
    +
    +
    static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    struct lo_dirp *d = lo_dirp(fi);
    +
    (void) ino;
    +
    closedir(d->dp);
    +
    free(d);
    +
    fuse_reply_err(req, 0);
    +
    }
    +
    +
    static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
    +
    mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct fuse_entry_param e;
    +
    int err;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
    +
    parent);
    +
    +
    fd = openat(lo_fd(req, parent), ".",
    +
    (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
    +
    if (fd == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fi->fh = fd;
    +
    if (lo->cache == CACHE_NEVER)
    +
    fi->direct_io = 1;
    +
    else if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need set fi->direct_io
    +
    in current function. */
    + +
    +
    err = fill_entry_param_new_inode(req, parent, fd, &e);
    +
    if (err)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_create(req, &e, fi);
    +
    }
    +
    +
    static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct fuse_entry_param e;
    +
    int err;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
    +
    parent, name);
    +
    +
    fd = openat(lo_fd(req, parent), name,
    +
    (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
    +
    if (fd == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fi->fh = fd;
    +
    if (lo->cache == CACHE_NEVER)
    +
    fi->direct_io = 1;
    +
    else if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need set fi->direct_io
    +
    in current function. */
    + +
    +
    err = lo_do_lookup(req, parent, name, &e);
    +
    if (err)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_create(req, &e, fi);
    +
    }
    +
    +
    static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    int fd = dirfd(lo_dirp(fi)->dp);
    +
    (void) ino;
    +
    if (datasync)
    +
    res = fdatasync(fd);
    +
    else
    +
    res = fsync(fd);
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    char buf[64];
    +
    struct lo_data *lo = lo_data(req);
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
    +
    ino, fi->flags);
    +
    +
    /* With writeback cache, kernel may send read requests even
    +
    when userspace opened write-only */
    +
    if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
    +
    fi->flags &= ~O_ACCMODE;
    +
    fi->flags |= O_RDWR;
    +
    }
    +
    +
    /* With writeback cache, O_APPEND is handled by the kernel.
    +
    This breaks atomicity (since the file may change in the
    +
    underlying filesystem, so that the kernel's idea of the
    +
    end of the file isn't accurate anymore). In this example,
    +
    we just accept that. A more rigorous filesystem may want
    +
    to return an error here */
    +
    if (lo->writeback && (fi->flags & O_APPEND))
    +
    fi->flags &= ~O_APPEND;
    +
    +
    sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
    +
    fd = open(buf, fi->flags & ~O_NOFOLLOW);
    +
    if (fd == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fi->fh = fd;
    +
    if (lo->cache == CACHE_NEVER)
    +
    fi->direct_io = 1;
    +
    else if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file in the kernel). */
    +
    if (fi->flags & O_DIRECT)
    +
    fi->direct_io = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need set fi->direct_io
    +
    in current function. */
    + +
    +
    fuse_reply_open(req, fi);
    +
    }
    +
    +
    static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    (void) ino;
    +
    +
    close(fi->fh);
    +
    fuse_reply_err(req, 0);
    +
    }
    +
    +
    static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) ino;
    +
    res = close(dup(fi->fh));
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) ino;
    +
    if (datasync)
    +
    res = fdatasync(fi->fh);
    +
    else
    +
    res = fsync(fi->fh);
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
    +
    "off=%lu)\n", ino, size, (unsigned long) offset);
    +
    + +
    buf.buf[0].fd = fi->fh;
    +
    buf.buf[0].pos = offset;
    +
    + +
    }
    +
    +
    static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_bufvec *in_buf, off_t off,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) ino;
    +
    ssize_t res;
    +
    struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
    +
    + +
    out_buf.buf[0].fd = fi->fh;
    +
    out_buf.buf[0].pos = off;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
    +
    ino, out_buf.buf[0].size, (unsigned long) off);
    +
    +
    res = fuse_buf_copy(&out_buf, in_buf, 0);
    +
    if(res < 0)
    +
    fuse_reply_err(req, -res);
    +
    else
    +
    fuse_reply_write(req, (size_t) res);
    +
    }
    +
    +
    static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    int res;
    +
    struct statvfs stbuf;
    +
    +
    res = fstatvfs(lo_fd(req, ino), &stbuf);
    +
    if (res == -1)
    +
    fuse_reply_err(req, errno);
    +
    else
    +
    fuse_reply_statfs(req, &stbuf);
    +
    }
    +
    +
    static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    int err = EOPNOTSUPP;
    +
    (void) ino;
    +
    +
    #ifdef HAVE_FALLOCATE
    +
    err = fallocate(fi->fh, mode, offset, length);
    +
    if (err < 0)
    +
    err = errno;
    +
    +
    #elif defined(HAVE_POSIX_FALLOCATE)
    +
    if (mode) {
    +
    fuse_reply_err(req, EOPNOTSUPP);
    +
    return;
    +
    }
    +
    +
    err = posix_fallocate(fi->fh, offset, length);
    +
    #endif
    +
    +
    fuse_reply_err(req, err);
    +
    }
    +
    +
    static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
    +
    int op)
    +
    {
    +
    int res;
    +
    (void) ino;
    +
    +
    res = flock(fi->fh, op);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    size_t size)
    +
    {
    +
    char *value = NULL;
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
    +
    ino, name, size);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    if (size) {
    +
    value = malloc(size);
    +
    if (!value)
    +
    goto out_err;
    +
    +
    ret = getxattr(procname, name, value, size);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    saverr = 0;
    +
    if (ret == 0)
    +
    goto out;
    +
    +
    fuse_reply_buf(req, value, ret);
    +
    } else {
    +
    ret = getxattr(procname, name, NULL, 0);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    +
    fuse_reply_xattr(req, ret);
    +
    }
    +
    out_free:
    +
    free(value);
    +
    return;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    goto out_free;
    +
    }
    +
    +
    static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
    +
    {
    +
    char *value = NULL;
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
    +
    ino, size);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    if (size) {
    +
    value = malloc(size);
    +
    if (!value)
    +
    goto out_err;
    +
    +
    ret = listxattr(procname, value, size);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    saverr = 0;
    +
    if (ret == 0)
    +
    goto out;
    +
    +
    fuse_reply_buf(req, value, ret);
    +
    } else {
    +
    ret = listxattr(procname, NULL, 0);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    +
    fuse_reply_xattr(req, ret);
    +
    }
    +
    out_free:
    +
    free(value);
    +
    return;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    goto out_free;
    +
    }
    +
    +
    static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    const char *value, size_t size, int flags)
    +
    {
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
    +
    ino, name, value, size);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    ret = setxattr(procname, name, value, size, flags);
    +
    saverr = ret == -1 ? errno : 0;
    +
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
    +
    {
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
    +
    ino, name);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    ret = removexattr(procname, name);
    +
    saverr = ret == -1 ? errno : 0;
    +
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
    +
    struct fuse_file_info *fi_in,
    +
    fuse_ino_t ino_out, off_t off_out,
    +
    struct fuse_file_info *fi_out, size_t len,
    +
    int flags)
    +
    {
    +
    ssize_t res;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
    +
    "off=%lu, ino=%" PRIu64 "/fd=%lu, "
    +
    "off=%lu, size=%zd, flags=0x%x)\n",
    +
    ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
    +
    len, flags);
    +
    +
    res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    flags);
    +
    if (res < 0)
    +
    fuse_reply_err(req, errno);
    +
    else
    +
    fuse_reply_write(req, res);
    +
    }
    +
    #endif
    +
    +
    static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    off_t res;
    +
    +
    (void)ino;
    +
    res = lseek(fi->fh, off, whence);
    +
    if (res != -1)
    +
    fuse_reply_lseek(req, res);
    +
    else
    +
    fuse_reply_err(req, errno);
    +
    }
    +
    +
    static const struct fuse_lowlevel_ops lo_oper = {
    +
    .init = lo_init,
    +
    .destroy = lo_destroy,
    +
    .lookup = lo_lookup,
    +
    .mkdir = lo_mkdir,
    +
    .mknod = lo_mknod,
    +
    .symlink = lo_symlink,
    +
    .link = lo_link,
    +
    .unlink = lo_unlink,
    +
    .rmdir = lo_rmdir,
    +
    .rename = lo_rename,
    +
    .forget = lo_forget,
    +
    .forget_multi = lo_forget_multi,
    +
    .getattr = lo_getattr,
    +
    .setattr = lo_setattr,
    +
    .readlink = lo_readlink,
    +
    .opendir = lo_opendir,
    +
    .readdir = lo_readdir,
    +
    .readdirplus = lo_readdirplus,
    +
    .releasedir = lo_releasedir,
    +
    .fsyncdir = lo_fsyncdir,
    +
    .create = lo_create,
    +
    .tmpfile = lo_tmpfile,
    +
    .open = lo_open,
    +
    .release = lo_release,
    +
    .flush = lo_flush,
    +
    .fsync = lo_fsync,
    +
    .read = lo_read,
    +
    .write_buf = lo_write_buf,
    +
    .statfs = lo_statfs,
    +
    .fallocate = lo_fallocate,
    +
    .flock = lo_flock,
    +
    .getxattr = lo_getxattr,
    +
    .listxattr = lo_listxattr,
    +
    .setxattr = lo_setxattr,
    +
    .removexattr = lo_removexattr,
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = lo_copy_file_range,
    +
    #endif
    +
    .lseek = lo_lseek,
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    struct lo_data lo = { .debug = 0,
    +
    .writeback = 0 };
    +
    int ret = -1;
    +
    +
    /* Don't mask creation mode, kernel already did that */
    +
    umask(0);
    +
    +
    pthread_mutex_init(&lo.mutex, NULL);
    +
    lo.root.next = lo.root.prev = &lo.root;
    +
    lo.root.fd = -1;
    +
    lo.cache = CACHE_NORMAL;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    return 1;
    +
    if (opts.show_help) {
    +
    printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
    + + +
    passthrough_ll_help();
    +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    if(opts.mountpoint == NULL) {
    +
    printf("usage: %s [options] <mountpoint>\n", argv[0]);
    +
    printf(" %s --help\n", argv[0]);
    +
    ret = 1;
    +
    goto err_out1;
    +
    }
    +
    +
    if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
    +
    return 1;
    +
    +
    lo.debug = opts.debug;
    +
    lo.root.refcount = 2;
    +
    if (lo.source) {
    +
    struct stat stat;
    +
    int res;
    +
    +
    res = lstat(lo.source, &stat);
    +
    if (res == -1) {
    +
    fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
    +
    lo.source);
    +
    exit(1);
    +
    }
    +
    if (!S_ISDIR(stat.st_mode)) {
    +
    fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
    +
    exit(1);
    +
    }
    +
    +
    } else {
    +
    lo.source = strdup("/");
    +
    if(!lo.source) {
    +
    fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
    +
    exit(1);
    +
    }
    +
    }
    +
    if (!lo.timeout_set) {
    +
    switch (lo.cache) {
    +
    case CACHE_NEVER:
    +
    lo.timeout = 0.0;
    +
    break;
    +
    +
    case CACHE_NORMAL:
    +
    lo.timeout = 1.0;
    +
    break;
    +
    +
    case CACHE_ALWAYS:
    +
    lo.timeout = 86400.0;
    +
    break;
    +
    }
    +
    } else if (lo.timeout < 0) {
    +
    fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
    +
    lo.timeout);
    +
    exit(1);
    +
    }
    +
    +
    lo.root.fd = open(lo.source, O_PATH);
    +
    if (lo.root.fd == -1) {
    +
    fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
    +
    lo.source);
    +
    exit(1);
    +
    }
    +
    +
    se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread)
    +
    ret = fuse_session_loop(se);
    +
    else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    free(opts.mountpoint);
    + +
    +
    if (lo.root.fd >= 0)
    +
    close(lo.root.fd);
    +
    +
    free(lo.source);
    +
    return ret ? 1 : 0;
    +
    }
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    @ FUSE_BUF_SPLICE_MOVE
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    +
    #define FUSE_CAP_FLOCK_LOCKS
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...)
    Definition fuse_log.c:77
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    void * fuse_req_userdata(fuse_req_t req)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
    +
    int fuse_reply_readlink(fuse_req_t req, const char *link)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
    +
    int fuse_reply_lseek(fuse_req_t req, off_t off)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_reply_xattr(fuse_req_t req, size_t count)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    +
    size_t size
    + + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    uint32_t capable
    +
    +
    double entry_timeout
    +
    fuse_ino_t ino
    +
    double attr_timeout
    +
    struct stat attr
    + + +
    uint32_t cache_readdir
    Definition fuse_common.h:95
    +
    uint32_t parallel_direct_writes
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    +
    uint32_t keep_cache
    Definition fuse_common.h:75
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    +

    Definition in file passthrough_ll.c.

    +
    + + + + diff --git a/doc/html/passthrough__ll_8c_source.html b/doc/html/passthrough__ll_8c_source.html new file mode 100644 index 0000000..ec8fa2e --- /dev/null +++ b/doc/html/passthrough__ll_8c_source.html @@ -0,0 +1,1509 @@ + + + + + + + +libfuse: example/passthrough_ll.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    passthrough_ll.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    37#define _GNU_SOURCE
    +
    38#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    39
    +
    40#include <fuse_lowlevel.h>
    +
    41#include <unistd.h>
    +
    42#include <stdlib.h>
    +
    43#include <stdio.h>
    +
    44#include <stddef.h>
    +
    45#include <stdbool.h>
    +
    46#include <string.h>
    +
    47#include <limits.h>
    +
    48#include <dirent.h>
    +
    49#include <assert.h>
    +
    50#include <errno.h>
    +
    51#include <inttypes.h>
    +
    52#include <pthread.h>
    +
    53#include <sys/file.h>
    +
    54#include <sys/xattr.h>
    +
    55
    +
    56#include "passthrough_helpers.h"
    +
    57
    +
    58/* We are re-using pointers to our `struct lo_inode` and `struct
    +
    59 lo_dirp` elements as inodes. This means that we must be able to
    +
    60 store uintptr_t values in a fuse_ino_t variable. The following
    +
    61 incantation checks this condition at compile time. */
    +
    62#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
    +
    63_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
    +
    64 "fuse_ino_t too small to hold uintptr_t values!");
    +
    65#else
    +
    66struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
    +
    67 { unsigned _uintptr_to_must_hold_fuse_ino_t:
    +
    68 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
    +
    69#endif
    +
    70
    +
    71struct lo_inode {
    +
    72 struct lo_inode *next; /* protected by lo->mutex */
    +
    73 struct lo_inode *prev; /* protected by lo->mutex */
    +
    74 int fd;
    +
    75 ino_t ino;
    +
    76 dev_t dev;
    +
    77 uint64_t refcount; /* protected by lo->mutex */
    +
    78};
    +
    79
    +
    80enum {
    +
    81 CACHE_NEVER,
    +
    82 CACHE_NORMAL,
    +
    83 CACHE_ALWAYS,
    +
    84};
    +
    85
    +
    86struct lo_data {
    +
    87 pthread_mutex_t mutex;
    +
    88 int debug;
    +
    89 int writeback;
    +
    90 int flock;
    +
    91 int xattr;
    +
    92 char *source;
    +
    93 double timeout;
    +
    94 int cache;
    +
    95 int timeout_set;
    +
    96 struct lo_inode root; /* protected by lo->mutex */
    +
    97};
    +
    98
    +
    99static const struct fuse_opt lo_opts[] = {
    +
    100 { "writeback",
    +
    101 offsetof(struct lo_data, writeback), 1 },
    +
    102 { "no_writeback",
    +
    103 offsetof(struct lo_data, writeback), 0 },
    +
    104 { "source=%s",
    +
    105 offsetof(struct lo_data, source), 0 },
    +
    106 { "flock",
    +
    107 offsetof(struct lo_data, flock), 1 },
    +
    108 { "no_flock",
    +
    109 offsetof(struct lo_data, flock), 0 },
    +
    110 { "xattr",
    +
    111 offsetof(struct lo_data, xattr), 1 },
    +
    112 { "no_xattr",
    +
    113 offsetof(struct lo_data, xattr), 0 },
    +
    114 { "timeout=%lf",
    +
    115 offsetof(struct lo_data, timeout), 0 },
    +
    116 { "timeout=",
    +
    117 offsetof(struct lo_data, timeout_set), 1 },
    +
    118 { "cache=never",
    +
    119 offsetof(struct lo_data, cache), CACHE_NEVER },
    +
    120 { "cache=auto",
    +
    121 offsetof(struct lo_data, cache), CACHE_NORMAL },
    +
    122 { "cache=always",
    +
    123 offsetof(struct lo_data, cache), CACHE_ALWAYS },
    +
    124
    + +
    126};
    +
    127
    +
    128static void passthrough_ll_help(void)
    +
    129{
    +
    130 printf(
    +
    131" -o writeback Enable writeback\n"
    +
    132" -o no_writeback Disable write back\n"
    +
    133" -o source=/home/dir Source directory to be mounted\n"
    +
    134" -o flock Enable flock\n"
    +
    135" -o no_flock Disable flock\n"
    +
    136" -o xattr Enable xattr\n"
    +
    137" -o no_xattr Disable xattr\n"
    +
    138" -o timeout=1.0 Caching timeout\n"
    +
    139" -o timeout=0/1 Timeout is set\n"
    +
    140" -o cache=never Disable cache\n"
    +
    141" -o cache=auto Auto enable cache\n"
    +
    142" -o cache=always Cache always\n");
    +
    143}
    +
    144
    +
    145static struct lo_data *lo_data(fuse_req_t req)
    +
    146{
    +
    147 return (struct lo_data *) fuse_req_userdata(req);
    +
    148}
    +
    149
    +
    150static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
    +
    151{
    +
    152 if (ino == FUSE_ROOT_ID)
    +
    153 return &lo_data(req)->root;
    +
    154 else
    +
    155 return (struct lo_inode *) (uintptr_t) ino;
    +
    156}
    +
    157
    +
    158static int lo_fd(fuse_req_t req, fuse_ino_t ino)
    +
    159{
    +
    160 return lo_inode(req, ino)->fd;
    +
    161}
    +
    162
    +
    163static bool lo_debug(fuse_req_t req)
    +
    164{
    +
    165 return lo_data(req)->debug != 0;
    +
    166}
    +
    167
    +
    168static void lo_init(void *userdata,
    +
    169 struct fuse_conn_info *conn)
    +
    170{
    +
    171 struct lo_data *lo = (struct lo_data *)userdata;
    +
    172 bool has_flag;
    +
    173
    +
    174 if (lo->writeback) {
    +
    175 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
    +
    176 if (lo->debug && has_flag)
    +
    177 fuse_log(FUSE_LOG_DEBUG,
    +
    178 "lo_init: activating writeback\n");
    +
    179 }
    +
    180 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
    +
    181 has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS);
    +
    182 if (lo->debug && has_flag)
    +
    183 fuse_log(FUSE_LOG_DEBUG,
    +
    184 "lo_init: activating flock locks\n");
    +
    185 }
    +
    186
    +
    187 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    188 conn->no_interrupt = 1;
    +
    189}
    +
    190
    +
    191static void lo_destroy(void *userdata)
    +
    192{
    +
    193 struct lo_data *lo = (struct lo_data*) userdata;
    +
    194
    +
    195 while (lo->root.next != &lo->root) {
    +
    196 struct lo_inode* next = lo->root.next;
    +
    197 lo->root.next = next->next;
    +
    198 close(next->fd);
    +
    199 free(next);
    +
    200 }
    +
    201}
    +
    202
    +
    203static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    204 struct fuse_file_info *fi)
    +
    205{
    +
    206 int res;
    +
    207 struct stat buf;
    +
    208 struct lo_data *lo = lo_data(req);
    +
    209 int fd = fi ? fi->fh : lo_fd(req, ino);
    +
    210
    +
    211 (void) fi;
    +
    212
    +
    213 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    214 if (res == -1)
    +
    215 return (void) fuse_reply_err(req, errno);
    +
    216
    +
    217 fuse_reply_attr(req, &buf, lo->timeout);
    +
    218}
    +
    219
    +
    220static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    221 int valid, struct fuse_file_info *fi)
    +
    222{
    +
    223 int saverr;
    +
    224 char procname[64];
    +
    225 struct lo_inode *inode = lo_inode(req, ino);
    +
    226 int ifd = inode->fd;
    +
    227 int res;
    +
    228
    +
    229 if (valid & FUSE_SET_ATTR_MODE) {
    +
    230 if (fi) {
    +
    231 res = fchmod(fi->fh, attr->st_mode);
    +
    232 } else {
    +
    233 sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    234 res = chmod(procname, attr->st_mode);
    +
    235 }
    +
    236 if (res == -1)
    +
    237 goto out_err;
    +
    238 }
    +
    239 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
    +
    240 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
    +
    241 attr->st_uid : (uid_t) -1;
    +
    242 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
    +
    243 attr->st_gid : (gid_t) -1;
    +
    244
    +
    245 res = fchownat(ifd, "", uid, gid,
    +
    246 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    247 if (res == -1)
    +
    248 goto out_err;
    +
    249 }
    +
    250 if (valid & FUSE_SET_ATTR_SIZE) {
    +
    251 if (fi) {
    +
    252 res = ftruncate(fi->fh, attr->st_size);
    +
    253 } else {
    +
    254 sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    255 res = truncate(procname, attr->st_size);
    +
    256 }
    +
    257 if (res == -1)
    +
    258 goto out_err;
    +
    259 }
    +
    260 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
    +
    261 struct timespec tv[2];
    +
    262
    +
    263 tv[0].tv_sec = 0;
    +
    264 tv[1].tv_sec = 0;
    +
    265 tv[0].tv_nsec = UTIME_OMIT;
    +
    266 tv[1].tv_nsec = UTIME_OMIT;
    +
    267
    +
    268 if (valid & FUSE_SET_ATTR_ATIME_NOW)
    +
    269 tv[0].tv_nsec = UTIME_NOW;
    +
    270 else if (valid & FUSE_SET_ATTR_ATIME)
    +
    271 tv[0] = attr->st_atim;
    +
    272
    +
    273 if (valid & FUSE_SET_ATTR_MTIME_NOW)
    +
    274 tv[1].tv_nsec = UTIME_NOW;
    +
    275 else if (valid & FUSE_SET_ATTR_MTIME)
    +
    276 tv[1] = attr->st_mtim;
    +
    277
    +
    278 if (fi)
    +
    279 res = futimens(fi->fh, tv);
    +
    280 else {
    +
    281 sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    282 res = utimensat(AT_FDCWD, procname, tv, 0);
    +
    283 }
    +
    284 if (res == -1)
    +
    285 goto out_err;
    +
    286 }
    +
    287
    +
    288 return lo_getattr(req, ino, fi);
    +
    289
    +
    290out_err:
    +
    291 saverr = errno;
    +
    292 fuse_reply_err(req, saverr);
    +
    293}
    +
    294
    +
    295static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
    +
    296{
    +
    297 struct lo_inode *p;
    +
    298 struct lo_inode *ret = NULL;
    +
    299
    +
    300 pthread_mutex_lock(&lo->mutex);
    +
    301 for (p = lo->root.next; p != &lo->root; p = p->next) {
    +
    302 if (p->ino == st->st_ino && p->dev == st->st_dev) {
    +
    303 assert(p->refcount > 0);
    +
    304 ret = p;
    +
    305 ret->refcount++;
    +
    306 break;
    +
    307 }
    +
    308 }
    +
    309 pthread_mutex_unlock(&lo->mutex);
    +
    310 return ret;
    +
    311}
    +
    312
    +
    313
    +
    314static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
    +
    315{
    +
    316 struct lo_inode *inode = NULL;
    +
    317 struct lo_inode *prev, *next;
    +
    318
    +
    319 inode = calloc(1, sizeof(struct lo_inode));
    +
    320 if (!inode)
    +
    321 return NULL;
    +
    322
    +
    323 inode->refcount = 1;
    +
    324 inode->fd = fd;
    +
    325 inode->ino = e->attr.st_ino;
    +
    326 inode->dev = e->attr.st_dev;
    +
    327
    +
    328 pthread_mutex_lock(&lo->mutex);
    +
    329 prev = &lo->root;
    +
    330 next = prev->next;
    +
    331 next->prev = inode;
    +
    332 inode->next = next;
    +
    333 inode->prev = prev;
    +
    334 prev->next = inode;
    +
    335 pthread_mutex_unlock(&lo->mutex);
    +
    336 return inode;
    +
    337}
    +
    338
    +
    339static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
    +
    340{
    +
    341 int res;
    +
    342 struct lo_data *lo = lo_data(req);
    +
    343
    +
    344 memset(e, 0, sizeof(*e));
    +
    345 e->attr_timeout = lo->timeout;
    +
    346 e->entry_timeout = lo->timeout;
    +
    347
    +
    348 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    349 if (res == -1)
    +
    350 return errno;
    +
    351
    +
    352 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
    +
    353
    +
    354 if (lo_debug(req))
    +
    355 fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
    +
    356 (unsigned long long) parent, fd, (unsigned long long) e->ino);
    +
    357
    +
    358 return 0;
    +
    359
    +
    360}
    +
    361
    +
    362static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    363 struct fuse_entry_param *e)
    +
    364{
    +
    365 int newfd;
    +
    366 int res;
    +
    367 int saverr;
    +
    368 struct lo_data *lo = lo_data(req);
    +
    369 struct lo_inode *inode;
    +
    370
    +
    371 memset(e, 0, sizeof(*e));
    +
    372 e->attr_timeout = lo->timeout;
    +
    373 e->entry_timeout = lo->timeout;
    +
    374
    +
    375 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
    +
    376 if (newfd == -1)
    +
    377 goto out_err;
    +
    378
    +
    379 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    380 if (res == -1)
    +
    381 goto out_err;
    +
    382
    +
    383 inode = lo_find(lo_data(req), &e->attr);
    +
    384 if (inode) {
    +
    385 close(newfd);
    +
    386 newfd = -1;
    +
    387 } else {
    +
    388 inode = create_new_inode(newfd, e, lo);
    +
    389 if (!inode)
    +
    390 goto out_err;
    +
    391 }
    +
    392 e->ino = (uintptr_t) inode;
    +
    393
    +
    394 if (lo_debug(req))
    +
    395 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    396 (unsigned long long) parent, name, (unsigned long long) e->ino);
    +
    397
    +
    398 return 0;
    +
    399
    +
    400out_err:
    +
    401 saverr = errno;
    +
    402 if (newfd != -1)
    +
    403 close(newfd);
    +
    404 return saverr;
    +
    405}
    +
    406
    +
    407static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    408{
    +
    409 struct fuse_entry_param e;
    +
    410 int err;
    +
    411
    +
    412 if (lo_debug(req))
    +
    413 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
    +
    414 parent, name);
    +
    415
    +
    416 err = lo_do_lookup(req, parent, name, &e);
    +
    417 if (err)
    +
    418 fuse_reply_err(req, err);
    +
    419 else
    +
    420 fuse_reply_entry(req, &e);
    +
    421}
    +
    422
    +
    423static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
    +
    424 const char *name, mode_t mode, dev_t rdev,
    +
    425 const char *link)
    +
    426{
    +
    427 int res;
    +
    428 int saverr;
    +
    429 struct lo_inode *dir = lo_inode(req, parent);
    +
    430 struct fuse_entry_param e;
    +
    431
    +
    432 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
    +
    433
    +
    434 saverr = errno;
    +
    435 if (res == -1)
    +
    436 goto out;
    +
    437
    +
    438 saverr = lo_do_lookup(req, parent, name, &e);
    +
    439 if (saverr)
    +
    440 goto out;
    +
    441
    +
    442 if (lo_debug(req))
    +
    443 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    444 (unsigned long long) parent, name, (unsigned long long) e.ino);
    +
    445
    +
    446 fuse_reply_entry(req, &e);
    +
    447 return;
    +
    448
    +
    449out:
    +
    450 fuse_reply_err(req, saverr);
    +
    451}
    +
    452
    +
    453static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
    +
    454 const char *name, mode_t mode, dev_t rdev)
    +
    455{
    +
    456 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
    +
    457}
    +
    458
    +
    459static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    460 mode_t mode)
    +
    461{
    +
    462 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
    +
    463}
    +
    464
    +
    465static void lo_symlink(fuse_req_t req, const char *link,
    +
    466 fuse_ino_t parent, const char *name)
    +
    467{
    +
    468 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
    +
    469}
    +
    470
    +
    471static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
    +
    472 const char *name)
    +
    473{
    +
    474 int res;
    +
    475 struct lo_data *lo = lo_data(req);
    +
    476 struct lo_inode *inode = lo_inode(req, ino);
    +
    477 struct fuse_entry_param e;
    +
    478 char procname[64];
    +
    479 int saverr;
    +
    480
    +
    481 memset(&e, 0, sizeof(struct fuse_entry_param));
    +
    482 e.attr_timeout = lo->timeout;
    +
    483 e.entry_timeout = lo->timeout;
    +
    484
    +
    485 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    486 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
    +
    487 AT_SYMLINK_FOLLOW);
    +
    488 if (res == -1)
    +
    489 goto out_err;
    +
    490
    +
    491 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    492 if (res == -1)
    +
    493 goto out_err;
    +
    494
    +
    495 pthread_mutex_lock(&lo->mutex);
    +
    496 inode->refcount++;
    +
    497 pthread_mutex_unlock(&lo->mutex);
    +
    498 e.ino = (uintptr_t) inode;
    +
    499
    +
    500 if (lo_debug(req))
    +
    501 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    502 (unsigned long long) parent, name,
    +
    503 (unsigned long long) e.ino);
    +
    504
    +
    505 fuse_reply_entry(req, &e);
    +
    506 return;
    +
    507
    +
    508out_err:
    +
    509 saverr = errno;
    +
    510 fuse_reply_err(req, saverr);
    +
    511}
    +
    512
    +
    513static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    514{
    +
    515 int res;
    +
    516
    +
    517 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
    +
    518
    +
    519 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    520}
    +
    521
    +
    522static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    523 fuse_ino_t newparent, const char *newname,
    +
    524 unsigned int flags)
    +
    525{
    +
    526 int res;
    +
    527
    +
    528 if (flags) {
    +
    529 fuse_reply_err(req, EINVAL);
    +
    530 return;
    +
    531 }
    +
    532
    +
    533 res = renameat(lo_fd(req, parent), name,
    +
    534 lo_fd(req, newparent), newname);
    +
    535
    +
    536 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    537}
    +
    538
    +
    539static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    540{
    +
    541 int res;
    +
    542
    +
    543 res = unlinkat(lo_fd(req, parent), name, 0);
    +
    544
    +
    545 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    546}
    +
    547
    +
    548static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
    +
    549{
    +
    550 if (!inode)
    +
    551 return;
    +
    552
    +
    553 pthread_mutex_lock(&lo->mutex);
    +
    554 assert(inode->refcount >= n);
    +
    555 inode->refcount -= n;
    +
    556 if (!inode->refcount) {
    +
    557 struct lo_inode *prev, *next;
    +
    558
    +
    559 prev = inode->prev;
    +
    560 next = inode->next;
    +
    561 next->prev = prev;
    +
    562 prev->next = next;
    +
    563
    +
    564 pthread_mutex_unlock(&lo->mutex);
    +
    565 close(inode->fd);
    +
    566 free(inode);
    +
    567
    +
    568 } else {
    +
    569 pthread_mutex_unlock(&lo->mutex);
    +
    570 }
    +
    571}
    +
    572
    +
    573static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    574{
    +
    575 struct lo_data *lo = lo_data(req);
    +
    576 struct lo_inode *inode = lo_inode(req, ino);
    +
    577
    +
    578 if (lo_debug(req)) {
    +
    579 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
    +
    580 (unsigned long long) ino,
    +
    581 (unsigned long long) inode->refcount,
    +
    582 (unsigned long long) nlookup);
    +
    583 }
    +
    584
    +
    585 unref_inode(lo, inode, nlookup);
    +
    586}
    +
    587
    +
    588static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    589{
    +
    590 lo_forget_one(req, ino, nlookup);
    +
    591 fuse_reply_none(req);
    +
    592}
    +
    593
    +
    594static void lo_forget_multi(fuse_req_t req, size_t count,
    +
    595 struct fuse_forget_data *forgets)
    +
    596{
    +
    597 int i;
    +
    598
    +
    599 for (i = 0; i < count; i++)
    +
    600 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
    +
    601 fuse_reply_none(req);
    +
    602}
    +
    603
    +
    604static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
    +
    605{
    +
    606 char buf[PATH_MAX + 1];
    +
    607 int res;
    +
    608
    +
    609 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
    +
    610 if (res == -1)
    +
    611 return (void) fuse_reply_err(req, errno);
    +
    612
    +
    613 if (res == sizeof(buf))
    +
    614 return (void) fuse_reply_err(req, ENAMETOOLONG);
    +
    615
    +
    616 buf[res] = '\0';
    +
    617
    +
    618 fuse_reply_readlink(req, buf);
    +
    619}
    +
    620
    +
    621struct lo_dirp {
    +
    622 DIR *dp;
    +
    623 struct dirent *entry;
    +
    624 off_t offset;
    +
    625};
    +
    626
    +
    627static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
    +
    628{
    +
    629 return (struct lo_dirp *) (uintptr_t) fi->fh;
    +
    630}
    +
    631
    +
    632static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    633{
    +
    634 int error = ENOMEM;
    +
    635 struct lo_data *lo = lo_data(req);
    +
    636 struct lo_dirp *d;
    +
    637 int fd = -1;
    +
    638
    +
    639 d = calloc(1, sizeof(struct lo_dirp));
    +
    640 if (d == NULL)
    +
    641 goto out_err;
    +
    642
    +
    643 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
    +
    644 if (fd == -1)
    +
    645 goto out_errno;
    +
    646
    +
    647 d->dp = fdopendir(fd);
    +
    648 if (d->dp == NULL)
    +
    649 goto out_errno;
    +
    650
    +
    651 d->offset = 0;
    +
    652 d->entry = NULL;
    +
    653
    +
    654 fi->fh = (uintptr_t) d;
    +
    655 if (lo->cache != CACHE_NEVER)
    +
    656 fi->cache_readdir = 1;
    +
    657 if (lo->cache == CACHE_ALWAYS)
    +
    658 fi->keep_cache = 1;
    +
    659 fuse_reply_open(req, fi);
    +
    660 return;
    +
    661
    +
    662out_errno:
    +
    663 error = errno;
    +
    664out_err:
    +
    665 if (d) {
    +
    666 if (fd != -1)
    +
    667 close(fd);
    +
    668 free(d);
    +
    669 }
    +
    670 fuse_reply_err(req, error);
    +
    671}
    +
    672
    +
    673static int is_dot_or_dotdot(const char *name)
    +
    674{
    +
    675 return name[0] == '.' && (name[1] == '\0' ||
    +
    676 (name[1] == '.' && name[2] == '\0'));
    +
    677}
    +
    678
    +
    679static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    680 off_t offset, struct fuse_file_info *fi, int plus)
    +
    681{
    +
    682 struct lo_dirp *d = lo_dirp(fi);
    +
    683 char *buf;
    +
    684 char *p;
    +
    685 size_t rem = size;
    +
    686 int err;
    +
    687
    +
    688 (void) ino;
    +
    689
    +
    690 buf = calloc(1, size);
    +
    691 if (!buf) {
    +
    692 err = ENOMEM;
    +
    693 goto error;
    +
    694 }
    +
    695 p = buf;
    +
    696
    +
    697 if (offset != d->offset) {
    +
    698 seekdir(d->dp, offset);
    +
    699 d->entry = NULL;
    +
    700 d->offset = offset;
    +
    701 }
    +
    702 while (1) {
    +
    703 size_t entsize;
    +
    704 off_t nextoff;
    +
    705 const char *name;
    +
    706
    +
    707 if (!d->entry) {
    +
    708 errno = 0;
    +
    709 d->entry = readdir(d->dp);
    +
    710 if (!d->entry) {
    +
    711 if (errno) { // Error
    +
    712 err = errno;
    +
    713 goto error;
    +
    714 } else { // End of stream
    +
    715 break;
    +
    716 }
    +
    717 }
    +
    718 }
    +
    719 nextoff = d->entry->d_off;
    +
    720 name = d->entry->d_name;
    +
    721 fuse_ino_t entry_ino = 0;
    +
    722 if (plus) {
    +
    723 struct fuse_entry_param e;
    +
    724 if (is_dot_or_dotdot(name)) {
    +
    725 e = (struct fuse_entry_param) {
    +
    726 .attr.st_ino = d->entry->d_ino,
    +
    727 .attr.st_mode = d->entry->d_type << 12,
    +
    728 };
    +
    729 } else {
    +
    730 err = lo_do_lookup(req, ino, name, &e);
    +
    731 if (err)
    +
    732 goto error;
    +
    733 entry_ino = e.ino;
    +
    734 }
    +
    735
    +
    736 entsize = fuse_add_direntry_plus(req, p, rem, name,
    +
    737 &e, nextoff);
    +
    738 } else {
    +
    739 struct stat st = {
    +
    740 .st_ino = d->entry->d_ino,
    +
    741 .st_mode = d->entry->d_type << 12,
    +
    742 };
    +
    743 entsize = fuse_add_direntry(req, p, rem, name,
    +
    744 &st, nextoff);
    +
    745 }
    +
    746 if (entsize > rem) {
    +
    747 if (entry_ino != 0)
    +
    748 lo_forget_one(req, entry_ino, 1);
    +
    749 break;
    +
    750 }
    +
    751
    +
    752 p += entsize;
    +
    753 rem -= entsize;
    +
    754
    +
    755 d->entry = NULL;
    +
    756 d->offset = nextoff;
    +
    757 }
    +
    758
    +
    759 err = 0;
    +
    760error:
    +
    761 // If there's an error, we can only signal it if we haven't stored
    +
    762 // any entries yet - otherwise we'd end up with wrong lookup
    +
    763 // counts for the entries that are already in the buffer. So we
    +
    764 // return what we've collected until that point.
    +
    765 if (err && rem == size)
    +
    766 fuse_reply_err(req, err);
    +
    767 else
    +
    768 fuse_reply_buf(req, buf, size - rem);
    +
    769 free(buf);
    +
    770}
    +
    771
    +
    772static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    773 off_t offset, struct fuse_file_info *fi)
    +
    774{
    +
    775 lo_do_readdir(req, ino, size, offset, fi, 0);
    +
    776}
    +
    777
    +
    778static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    779 off_t offset, struct fuse_file_info *fi)
    +
    780{
    +
    781 lo_do_readdir(req, ino, size, offset, fi, 1);
    +
    782}
    +
    783
    +
    784static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    785{
    +
    786 struct lo_dirp *d = lo_dirp(fi);
    +
    787 (void) ino;
    +
    788 closedir(d->dp);
    +
    789 free(d);
    +
    790 fuse_reply_err(req, 0);
    +
    791}
    +
    792
    +
    793static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
    +
    794 mode_t mode, struct fuse_file_info *fi)
    +
    795{
    +
    796 int fd;
    +
    797 struct lo_data *lo = lo_data(req);
    +
    798 struct fuse_entry_param e;
    +
    799 int err;
    +
    800
    +
    801 if (lo_debug(req))
    +
    802 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
    +
    803 parent);
    +
    804
    +
    805 fd = openat(lo_fd(req, parent), ".",
    +
    806 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
    +
    807 if (fd == -1)
    +
    808 return (void) fuse_reply_err(req, errno);
    +
    809
    +
    810 fi->fh = fd;
    +
    811 if (lo->cache == CACHE_NEVER)
    +
    812 fi->direct_io = 1;
    +
    813 else if (lo->cache == CACHE_ALWAYS)
    +
    814 fi->keep_cache = 1;
    +
    815
    +
    816 /* parallel_direct_writes feature depends on direct_io features.
    +
    817 To make parallel_direct_writes valid, need set fi->direct_io
    +
    818 in current function. */
    +
    819 fi->parallel_direct_writes = 1;
    +
    820
    +
    821 err = fill_entry_param_new_inode(req, parent, fd, &e);
    +
    822 if (err)
    +
    823 fuse_reply_err(req, err);
    +
    824 else
    +
    825 fuse_reply_create(req, &e, fi);
    +
    826}
    +
    827
    +
    828static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    829 mode_t mode, struct fuse_file_info *fi)
    +
    830{
    +
    831 int fd;
    +
    832 struct lo_data *lo = lo_data(req);
    +
    833 struct fuse_entry_param e;
    +
    834 int err;
    +
    835
    +
    836 if (lo_debug(req))
    +
    837 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
    +
    838 parent, name);
    +
    839
    +
    840 fd = openat(lo_fd(req, parent), name,
    +
    841 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
    +
    842 if (fd == -1)
    +
    843 return (void) fuse_reply_err(req, errno);
    +
    844
    +
    845 fi->fh = fd;
    +
    846 if (lo->cache == CACHE_NEVER)
    +
    847 fi->direct_io = 1;
    +
    848 else if (lo->cache == CACHE_ALWAYS)
    +
    849 fi->keep_cache = 1;
    +
    850
    +
    851 /* parallel_direct_writes feature depends on direct_io features.
    +
    852 To make parallel_direct_writes valid, need set fi->direct_io
    +
    853 in current function. */
    + +
    855
    +
    856 err = lo_do_lookup(req, parent, name, &e);
    +
    857 if (err)
    +
    858 fuse_reply_err(req, err);
    +
    859 else
    +
    860 fuse_reply_create(req, &e, fi);
    +
    861}
    +
    862
    +
    863static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    864 struct fuse_file_info *fi)
    +
    865{
    +
    866 int res;
    +
    867 int fd = dirfd(lo_dirp(fi)->dp);
    +
    868 (void) ino;
    +
    869 if (datasync)
    +
    870 res = fdatasync(fd);
    +
    871 else
    +
    872 res = fsync(fd);
    +
    873 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    874}
    +
    875
    +
    876static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    877{
    +
    878 int fd;
    +
    879 char buf[64];
    +
    880 struct lo_data *lo = lo_data(req);
    +
    881
    +
    882 if (lo_debug(req))
    +
    883 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
    +
    884 ino, fi->flags);
    +
    885
    +
    886 /* With writeback cache, kernel may send read requests even
    +
    887 when userspace opened write-only */
    +
    888 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
    +
    889 fi->flags &= ~O_ACCMODE;
    +
    890 fi->flags |= O_RDWR;
    +
    891 }
    +
    892
    +
    893 /* With writeback cache, O_APPEND is handled by the kernel.
    +
    894 This breaks atomicity (since the file may change in the
    +
    895 underlying filesystem, so that the kernel's idea of the
    +
    896 end of the file isn't accurate anymore). In this example,
    +
    897 we just accept that. A more rigorous filesystem may want
    +
    898 to return an error here */
    +
    899 if (lo->writeback && (fi->flags & O_APPEND))
    +
    900 fi->flags &= ~O_APPEND;
    +
    901
    +
    902 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
    +
    903 fd = open(buf, fi->flags & ~O_NOFOLLOW);
    +
    904 if (fd == -1)
    +
    905 return (void) fuse_reply_err(req, errno);
    +
    906
    +
    907 fi->fh = fd;
    +
    908 if (lo->cache == CACHE_NEVER)
    +
    909 fi->direct_io = 1;
    +
    910 else if (lo->cache == CACHE_ALWAYS)
    +
    911 fi->keep_cache = 1;
    +
    912
    +
    913 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    914 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    915 for writes to the same file in the kernel). */
    +
    916 if (fi->flags & O_DIRECT)
    +
    917 fi->direct_io = 1;
    +
    918
    +
    919 /* parallel_direct_writes feature depends on direct_io features.
    +
    920 To make parallel_direct_writes valid, need set fi->direct_io
    +
    921 in current function. */
    + +
    923
    +
    924 fuse_reply_open(req, fi);
    +
    925}
    +
    926
    +
    927static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    928{
    +
    929 (void) ino;
    +
    930
    +
    931 close(fi->fh);
    +
    932 fuse_reply_err(req, 0);
    +
    933}
    +
    934
    +
    935static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    936{
    +
    937 int res;
    +
    938 (void) ino;
    +
    939 res = close(dup(fi->fh));
    +
    940 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    941}
    +
    942
    +
    943static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    944 struct fuse_file_info *fi)
    +
    945{
    +
    946 int res;
    +
    947 (void) ino;
    +
    948 if (datasync)
    +
    949 res = fdatasync(fi->fh);
    +
    950 else
    +
    951 res = fsync(fi->fh);
    +
    952 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    953}
    +
    954
    +
    955static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    956 off_t offset, struct fuse_file_info *fi)
    +
    957{
    +
    958 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
    +
    959
    +
    960 if (lo_debug(req))
    +
    961 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
    +
    962 "off=%lu)\n", ino, size, (unsigned long) offset);
    +
    963
    + +
    965 buf.buf[0].fd = fi->fh;
    +
    966 buf.buf[0].pos = offset;
    +
    967
    + +
    969}
    +
    970
    +
    971static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
    +
    972 struct fuse_bufvec *in_buf, off_t off,
    +
    973 struct fuse_file_info *fi)
    +
    974{
    +
    975 (void) ino;
    +
    976 ssize_t res;
    +
    977 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
    +
    978
    + +
    980 out_buf.buf[0].fd = fi->fh;
    +
    981 out_buf.buf[0].pos = off;
    +
    982
    +
    983 if (lo_debug(req))
    +
    984 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n",
    +
    985 ino, out_buf.buf[0].size, (unsigned long) off);
    +
    986
    +
    987 res = fuse_buf_copy(&out_buf, in_buf, 0);
    +
    988 if(res < 0)
    +
    989 fuse_reply_err(req, -res);
    +
    990 else
    +
    991 fuse_reply_write(req, (size_t) res);
    +
    992}
    +
    993
    +
    994static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
    +
    995{
    +
    996 int res;
    +
    997 struct statvfs stbuf;
    +
    998
    +
    999 res = fstatvfs(lo_fd(req, ino), &stbuf);
    +
    1000 if (res == -1)
    +
    1001 fuse_reply_err(req, errno);
    +
    1002 else
    +
    1003 fuse_reply_statfs(req, &stbuf);
    +
    1004}
    +
    1005
    +
    1006static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
    +
    1007 off_t offset, off_t length, struct fuse_file_info *fi)
    +
    1008{
    +
    1009 int err = EOPNOTSUPP;
    +
    1010 (void) ino;
    +
    1011
    +
    1012#ifdef HAVE_FALLOCATE
    +
    1013 err = fallocate(fi->fh, mode, offset, length);
    +
    1014 if (err < 0)
    +
    1015 err = errno;
    +
    1016
    +
    1017#elif defined(HAVE_POSIX_FALLOCATE)
    +
    1018 if (mode) {
    +
    1019 fuse_reply_err(req, EOPNOTSUPP);
    +
    1020 return;
    +
    1021 }
    +
    1022
    +
    1023 err = posix_fallocate(fi->fh, offset, length);
    +
    1024#endif
    +
    1025
    +
    1026 fuse_reply_err(req, err);
    +
    1027}
    +
    1028
    +
    1029static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
    +
    1030 int op)
    +
    1031{
    +
    1032 int res;
    +
    1033 (void) ino;
    +
    1034
    +
    1035 res = flock(fi->fh, op);
    +
    1036
    +
    1037 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    1038}
    +
    1039
    +
    1040static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    1041 size_t size)
    +
    1042{
    +
    1043 char *value = NULL;
    +
    1044 char procname[64];
    +
    1045 struct lo_inode *inode = lo_inode(req, ino);
    +
    1046 ssize_t ret;
    +
    1047 int saverr;
    +
    1048
    +
    1049 saverr = ENOSYS;
    +
    1050 if (!lo_data(req)->xattr)
    +
    1051 goto out;
    +
    1052
    +
    1053 if (lo_debug(req)) {
    +
    1054 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
    +
    1055 ino, name, size);
    +
    1056 }
    +
    1057
    +
    1058 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1059
    +
    1060 if (size) {
    +
    1061 value = malloc(size);
    +
    1062 if (!value)
    +
    1063 goto out_err;
    +
    1064
    +
    1065 ret = getxattr(procname, name, value, size);
    +
    1066 if (ret == -1)
    +
    1067 goto out_err;
    +
    1068 saverr = 0;
    +
    1069 if (ret == 0)
    +
    1070 goto out;
    +
    1071
    +
    1072 fuse_reply_buf(req, value, ret);
    +
    1073 } else {
    +
    1074 ret = getxattr(procname, name, NULL, 0);
    +
    1075 if (ret == -1)
    +
    1076 goto out_err;
    +
    1077
    +
    1078 fuse_reply_xattr(req, ret);
    +
    1079 }
    +
    1080out_free:
    +
    1081 free(value);
    +
    1082 return;
    +
    1083
    +
    1084out_err:
    +
    1085 saverr = errno;
    +
    1086out:
    +
    1087 fuse_reply_err(req, saverr);
    +
    1088 goto out_free;
    +
    1089}
    +
    1090
    +
    1091static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
    +
    1092{
    +
    1093 char *value = NULL;
    +
    1094 char procname[64];
    +
    1095 struct lo_inode *inode = lo_inode(req, ino);
    +
    1096 ssize_t ret;
    +
    1097 int saverr;
    +
    1098
    +
    1099 saverr = ENOSYS;
    +
    1100 if (!lo_data(req)->xattr)
    +
    1101 goto out;
    +
    1102
    +
    1103 if (lo_debug(req)) {
    +
    1104 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
    +
    1105 ino, size);
    +
    1106 }
    +
    1107
    +
    1108 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1109
    +
    1110 if (size) {
    +
    1111 value = malloc(size);
    +
    1112 if (!value)
    +
    1113 goto out_err;
    +
    1114
    +
    1115 ret = listxattr(procname, value, size);
    +
    1116 if (ret == -1)
    +
    1117 goto out_err;
    +
    1118 saverr = 0;
    +
    1119 if (ret == 0)
    +
    1120 goto out;
    +
    1121
    +
    1122 fuse_reply_buf(req, value, ret);
    +
    1123 } else {
    +
    1124 ret = listxattr(procname, NULL, 0);
    +
    1125 if (ret == -1)
    +
    1126 goto out_err;
    +
    1127
    +
    1128 fuse_reply_xattr(req, ret);
    +
    1129 }
    +
    1130out_free:
    +
    1131 free(value);
    +
    1132 return;
    +
    1133
    +
    1134out_err:
    +
    1135 saverr = errno;
    +
    1136out:
    +
    1137 fuse_reply_err(req, saverr);
    +
    1138 goto out_free;
    +
    1139}
    +
    1140
    +
    1141static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    1142 const char *value, size_t size, int flags)
    +
    1143{
    +
    1144 char procname[64];
    +
    1145 struct lo_inode *inode = lo_inode(req, ino);
    +
    1146 ssize_t ret;
    +
    1147 int saverr;
    +
    1148
    +
    1149 saverr = ENOSYS;
    +
    1150 if (!lo_data(req)->xattr)
    +
    1151 goto out;
    +
    1152
    +
    1153 if (lo_debug(req)) {
    +
    1154 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
    +
    1155 ino, name, value, size);
    +
    1156 }
    +
    1157
    +
    1158 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1159
    +
    1160 ret = setxattr(procname, name, value, size, flags);
    +
    1161 saverr = ret == -1 ? errno : 0;
    +
    1162
    +
    1163out:
    +
    1164 fuse_reply_err(req, saverr);
    +
    1165}
    +
    1166
    +
    1167static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
    +
    1168{
    +
    1169 char procname[64];
    +
    1170 struct lo_inode *inode = lo_inode(req, ino);
    +
    1171 ssize_t ret;
    +
    1172 int saverr;
    +
    1173
    +
    1174 saverr = ENOSYS;
    +
    1175 if (!lo_data(req)->xattr)
    +
    1176 goto out;
    +
    1177
    +
    1178 if (lo_debug(req)) {
    +
    1179 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
    +
    1180 ino, name);
    +
    1181 }
    +
    1182
    +
    1183 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1184
    +
    1185 ret = removexattr(procname, name);
    +
    1186 saverr = ret == -1 ? errno : 0;
    +
    1187
    +
    1188out:
    +
    1189 fuse_reply_err(req, saverr);
    +
    1190}
    +
    1191
    +
    1192#ifdef HAVE_COPY_FILE_RANGE
    +
    1193static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
    +
    1194 struct fuse_file_info *fi_in,
    +
    1195 fuse_ino_t ino_out, off_t off_out,
    +
    1196 struct fuse_file_info *fi_out, size_t len,
    +
    1197 int flags)
    +
    1198{
    +
    1199 ssize_t res;
    +
    1200
    +
    1201 if (lo_debug(req))
    +
    1202 fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, "
    +
    1203 "off=%lu, ino=%" PRIu64 "/fd=%lu, "
    +
    1204 "off=%lu, size=%zd, flags=0x%x)\n",
    +
    1205 ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out,
    +
    1206 len, flags);
    +
    1207
    +
    1208 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    1209 flags);
    +
    1210 if (res < 0)
    +
    1211 fuse_reply_err(req, errno);
    +
    1212 else
    +
    1213 fuse_reply_write(req, res);
    +
    1214}
    +
    1215#endif
    +
    1216
    +
    1217static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
    +
    1218 struct fuse_file_info *fi)
    +
    1219{
    +
    1220 off_t res;
    +
    1221
    +
    1222 (void)ino;
    +
    1223 res = lseek(fi->fh, off, whence);
    +
    1224 if (res != -1)
    +
    1225 fuse_reply_lseek(req, res);
    +
    1226 else
    +
    1227 fuse_reply_err(req, errno);
    +
    1228}
    +
    1229
    +
    1230static const struct fuse_lowlevel_ops lo_oper = {
    +
    1231 .init = lo_init,
    +
    1232 .destroy = lo_destroy,
    +
    1233 .lookup = lo_lookup,
    +
    1234 .mkdir = lo_mkdir,
    +
    1235 .mknod = lo_mknod,
    +
    1236 .symlink = lo_symlink,
    +
    1237 .link = lo_link,
    +
    1238 .unlink = lo_unlink,
    +
    1239 .rmdir = lo_rmdir,
    +
    1240 .rename = lo_rename,
    +
    1241 .forget = lo_forget,
    +
    1242 .forget_multi = lo_forget_multi,
    +
    1243 .getattr = lo_getattr,
    +
    1244 .setattr = lo_setattr,
    +
    1245 .readlink = lo_readlink,
    +
    1246 .opendir = lo_opendir,
    +
    1247 .readdir = lo_readdir,
    +
    1248 .readdirplus = lo_readdirplus,
    +
    1249 .releasedir = lo_releasedir,
    +
    1250 .fsyncdir = lo_fsyncdir,
    +
    1251 .create = lo_create,
    +
    1252 .tmpfile = lo_tmpfile,
    +
    1253 .open = lo_open,
    +
    1254 .release = lo_release,
    +
    1255 .flush = lo_flush,
    +
    1256 .fsync = lo_fsync,
    +
    1257 .read = lo_read,
    +
    1258 .write_buf = lo_write_buf,
    +
    1259 .statfs = lo_statfs,
    +
    1260 .fallocate = lo_fallocate,
    +
    1261 .flock = lo_flock,
    +
    1262 .getxattr = lo_getxattr,
    +
    1263 .listxattr = lo_listxattr,
    +
    1264 .setxattr = lo_setxattr,
    +
    1265 .removexattr = lo_removexattr,
    +
    1266#ifdef HAVE_COPY_FILE_RANGE
    +
    1267 .copy_file_range = lo_copy_file_range,
    +
    1268#endif
    +
    1269 .lseek = lo_lseek,
    +
    1270};
    +
    1271
    +
    1272int main(int argc, char *argv[])
    +
    1273{
    +
    1274 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    1275 struct fuse_session *se;
    +
    1276 struct fuse_cmdline_opts opts;
    +
    1277 struct fuse_loop_config *config;
    +
    1278 struct lo_data lo = { .debug = 0,
    +
    1279 .writeback = 0 };
    +
    1280 int ret = -1;
    +
    1281
    +
    1282 /* Don't mask creation mode, kernel already did that */
    +
    1283 umask(0);
    +
    1284
    +
    1285 pthread_mutex_init(&lo.mutex, NULL);
    +
    1286 lo.root.next = lo.root.prev = &lo.root;
    +
    1287 lo.root.fd = -1;
    +
    1288 lo.cache = CACHE_NORMAL;
    +
    1289
    +
    1290 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    1291 return 1;
    +
    1292 if (opts.show_help) {
    +
    1293 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
    + + +
    1296 passthrough_ll_help();
    +
    1297 ret = 0;
    +
    1298 goto err_out1;
    +
    1299 } else if (opts.show_version) {
    +
    1300 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    1302 ret = 0;
    +
    1303 goto err_out1;
    +
    1304 }
    +
    1305
    +
    1306 if(opts.mountpoint == NULL) {
    +
    1307 printf("usage: %s [options] <mountpoint>\n", argv[0]);
    +
    1308 printf(" %s --help\n", argv[0]);
    +
    1309 ret = 1;
    +
    1310 goto err_out1;
    +
    1311 }
    +
    1312
    +
    1313 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
    +
    1314 return 1;
    +
    1315
    +
    1316 lo.debug = opts.debug;
    +
    1317 lo.root.refcount = 2;
    +
    1318 if (lo.source) {
    +
    1319 struct stat stat;
    +
    1320 int res;
    +
    1321
    +
    1322 res = lstat(lo.source, &stat);
    +
    1323 if (res == -1) {
    +
    1324 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
    +
    1325 lo.source);
    +
    1326 exit(1);
    +
    1327 }
    +
    1328 if (!S_ISDIR(stat.st_mode)) {
    +
    1329 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
    +
    1330 exit(1);
    +
    1331 }
    +
    1332
    +
    1333 } else {
    +
    1334 lo.source = strdup("/");
    +
    1335 if(!lo.source) {
    +
    1336 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
    +
    1337 exit(1);
    +
    1338 }
    +
    1339 }
    +
    1340 if (!lo.timeout_set) {
    +
    1341 switch (lo.cache) {
    +
    1342 case CACHE_NEVER:
    +
    1343 lo.timeout = 0.0;
    +
    1344 break;
    +
    1345
    +
    1346 case CACHE_NORMAL:
    +
    1347 lo.timeout = 1.0;
    +
    1348 break;
    +
    1349
    +
    1350 case CACHE_ALWAYS:
    +
    1351 lo.timeout = 86400.0;
    +
    1352 break;
    +
    1353 }
    +
    1354 } else if (lo.timeout < 0) {
    +
    1355 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
    +
    1356 lo.timeout);
    +
    1357 exit(1);
    +
    1358 }
    +
    1359
    +
    1360 lo.root.fd = open(lo.source, O_PATH);
    +
    1361 if (lo.root.fd == -1) {
    +
    1362 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
    +
    1363 lo.source);
    +
    1364 exit(1);
    +
    1365 }
    +
    1366
    +
    1367 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
    +
    1368 if (se == NULL)
    +
    1369 goto err_out1;
    +
    1370
    +
    1371 if (fuse_set_signal_handlers(se) != 0)
    +
    1372 goto err_out2;
    +
    1373
    +
    1374 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    1375 goto err_out3;
    +
    1376
    +
    1377 fuse_daemonize(opts.foreground);
    +
    1378
    +
    1379 /* Block until ctrl+c or fusermount -u */
    +
    1380 if (opts.singlethread)
    +
    1381 ret = fuse_session_loop(se);
    +
    1382 else {
    +
    1383 config = fuse_loop_cfg_create();
    +
    1384 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    1385 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    1386 ret = fuse_session_loop_mt(se, config);
    +
    1387 fuse_loop_cfg_destroy(config);
    +
    1388 config = NULL;
    +
    1389 }
    +
    1390
    + +
    1392err_out3:
    + +
    1394err_out2:
    + +
    1396err_out1:
    +
    1397 free(opts.mountpoint);
    +
    1398 fuse_opt_free_args(&args);
    +
    1399
    +
    1400 if (lo.root.fd >= 0)
    +
    1401 close(lo.root.fd);
    +
    1402
    +
    1403 free(lo.source);
    +
    1404 return ret ? 1 : 0;
    +
    1405}
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    @ FUSE_BUF_SPLICE_MOVE
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    +
    #define FUSE_CAP_FLOCK_LOCKS
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...)
    Definition fuse_log.c:77
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    void * fuse_req_userdata(fuse_req_t req)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
    +
    int fuse_reply_readlink(fuse_req_t req, const char *link)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
    +
    int fuse_reply_lseek(fuse_req_t req, off_t off)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_reply_xattr(fuse_req_t req, size_t count)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    +
    size_t size
    + + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    uint32_t capable
    +
    +
    double entry_timeout
    +
    fuse_ino_t ino
    +
    double attr_timeout
    +
    struct stat attr
    + + +
    uint32_t cache_readdir
    Definition fuse_common.h:95
    +
    uint32_t parallel_direct_writes
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    +
    uint32_t keep_cache
    Definition fuse_common.h:75
    + + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    + + + + diff --git a/doc/html/plus.svg b/doc/html/plus.svg new file mode 100644 index 0000000..0752016 --- /dev/null +++ b/doc/html/plus.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc/html/plusd.svg b/doc/html/plusd.svg new file mode 100644 index 0000000..0c65bfe --- /dev/null +++ b/doc/html/plusd.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc/html/poll_8c.html b/doc/html/poll_8c.html new file mode 100644 index 0000000..d3df63d --- /dev/null +++ b/doc/html/poll_8c.html @@ -0,0 +1,380 @@ + + + + + + + +libfuse: example/poll.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    poll.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <unistd.h>
    +#include <ctype.h>
    +#include <string.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <errno.h>
    +#include <time.h>
    +#include <pthread.h>
    +#include <poll.h>
    +#include <stdbool.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

    +

    Compile with:

    gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
    +

    +Source code

    +
    /*
    +
    FUSE fsel: FUSE select example
    +
    Copyright (C) 2008 SUSE Linux Products GmbH
    +
    Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #include <fuse.h>
    +
    #include <unistd.h>
    +
    #include <ctype.h>
    +
    #include <string.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <errno.h>
    +
    #include <time.h>
    +
    #include <pthread.h>
    +
    #include <poll.h>
    +
    #include <stdbool.h>
    +
    +
    /*
    +
    * fsel_open_mask is used to limit the number of opens to 1 per file.
    +
    * This is to use file index (0-F) as fh as poll support requires
    +
    * unique fh per open file. Lifting this would require proper open
    +
    * file management.
    +
    */
    +
    static unsigned fsel_open_mask;
    +
    static const char fsel_hex_map[] = "0123456789ABCDEF";
    +
    static struct fuse *fsel_fuse; /* needed for poll notification */
    +
    +
    #define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
    +
    #define FSEL_FILES 16
    +
    +
    static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
    +
    static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
    +
    static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
    +
    static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
    +
    static _Atomic bool fsel_stop = false;
    +
    static pthread_t fsel_producer_thread;
    +
    +
    +
    static int fsel_path_index(const char *path)
    +
    {
    +
    char ch = path[1];
    +
    +
    if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
    +
    return -1;
    +
    return ch <= '9' ? ch - '0' : ch - 'A' + 10;
    +
    }
    +
    +
    static void fsel_destroy(void *private_data)
    +
    {
    +
    (void)private_data;
    +
    +
    fsel_stop = true;
    +
    +
    pthread_join(fsel_producer_thread, NULL);
    +
    }
    +
    +
    static int fsel_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int idx;
    +
    +
    memset(stbuf, 0, sizeof(struct stat));
    +
    +
    if (strcmp(path, "/") == 0) {
    +
    stbuf->st_mode = S_IFDIR | 0555;
    +
    stbuf->st_nlink = 2;
    +
    return 0;
    +
    }
    +
    +
    idx = fsel_path_index(path);
    +
    if (idx < 0)
    +
    return -ENOENT;
    +
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = fsel_cnt[idx];
    +
    return 0;
    +
    }
    +
    +
    static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    char name[2] = { };
    +
    int i;
    +
    +
    (void) offset;
    +
    (void) fi;
    +
    (void) flags;
    +
    +
    if (strcmp(path, "/") != 0)
    +
    return -ENOENT;
    +
    +
    for (i = 0; i < FSEL_FILES; i++) {
    +
    name[0] = fsel_hex_map[i];
    +
    filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    }
    +
    +
    return 0;
    +
    }
    +
    +
    static int fsel_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int idx = fsel_path_index(path);
    +
    +
    if (idx < 0)
    +
    return -ENOENT;
    +
    if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    return -EACCES;
    +
    if (fsel_open_mask & (1 << idx))
    +
    return -EBUSY;
    +
    fsel_open_mask |= (1 << idx);
    +
    +
    /*
    +
    * fsel files are nonseekable somewhat pipe-like files which
    +
    * gets filled up periodically by producer thread and consumed
    +
    * on read. Tell FUSE as such.
    +
    */
    +
    fi->fh = idx;
    +
    fi->direct_io = 1;
    +
    fi->nonseekable = 1;
    +
    +
    return 0;
    +
    }
    +
    +
    static int fsel_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int idx = fi->fh;
    +
    +
    (void) path;
    +
    +
    fsel_open_mask &= ~(1 << idx);
    +
    return 0;
    +
    }
    +
    +
    static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int idx = fi->fh;
    +
    +
    (void) path;
    +
    (void) offset;
    +
    +
    pthread_mutex_lock(&fsel_mutex);
    +
    if (fsel_cnt[idx] < size)
    +
    size = fsel_cnt[idx];
    +
    printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
    +
    fsel_cnt[idx] -= size;
    +
    pthread_mutex_unlock(&fsel_mutex);
    +
    +
    memset(buf, fsel_hex_map[idx], size);
    +
    return size;
    +
    }
    +
    +
    static int fsel_poll(const char *path, struct fuse_file_info *fi,
    +
    struct fuse_pollhandle *ph, unsigned *reventsp)
    +
    {
    +
    static unsigned polled_zero;
    +
    int idx = fi->fh;
    +
    +
    (void) path;
    +
    +
    /*
    +
    * Poll notification requires pointer to struct fuse which
    +
    * can't be obtained when using fuse_main(). As notification
    +
    * happens only after poll is called, fill it here from
    +
    * fuse_context.
    +
    */
    +
    if (!fsel_fuse) {
    +
    struct fuse_context *cxt = fuse_get_context();
    +
    if (cxt)
    +
    fsel_fuse = cxt->fuse;
    +
    }
    +
    +
    pthread_mutex_lock(&fsel_mutex);
    +
    +
    if (ph != NULL) {
    +
    struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
    +
    +
    if (oldph)
    + +
    +
    fsel_poll_notify_mask |= (1 << idx);
    +
    fsel_poll_handle[idx] = ph;
    +
    }
    +
    +
    if (fsel_cnt[idx]) {
    +
    *reventsp |= POLLIN;
    +
    printf("POLL %X cnt=%u polled_zero=%u\n",
    +
    idx, fsel_cnt[idx], polled_zero);
    +
    polled_zero = 0;
    +
    } else
    +
    polled_zero++;
    +
    +
    pthread_mutex_unlock(&fsel_mutex);
    +
    return 0;
    +
    }
    +
    +
    static const struct fuse_operations fsel_oper = {
    +
    .destroy = fsel_destroy,
    +
    .getattr = fsel_getattr,
    +
    .readdir = fsel_readdir,
    +
    .open = fsel_open,
    +
    .release = fsel_release,
    +
    .read = fsel_read,
    +
    .poll = fsel_poll,
    +
    };
    +
    +
    static void *fsel_producer(void *data)
    +
    {
    +
    const struct timespec interval = { 0, 250000000 };
    +
    unsigned idx = 0, nr = 1;
    +
    +
    (void) data;
    +
    +
    while (!fsel_stop) {
    +
    int i, t;
    +
    +
    pthread_mutex_lock(&fsel_mutex);
    +
    +
    /*
    +
    * This is the main producer loop which is executed
    +
    * ever 500ms. On each iteration, it fills one byte
    +
    * to 1, 2 or 4 files and sends poll notification if
    +
    * requested.
    +
    */
    +
    for (i = 0, t = idx; i < nr;
    +
    i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
    +
    if (fsel_cnt[t] == FSEL_CNT_MAX)
    +
    continue;
    +
    +
    fsel_cnt[t]++;
    +
    if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
    +
    struct fuse_pollhandle *ph;
    +
    +
    printf("NOTIFY %X\n", t);
    +
    ph = fsel_poll_handle[t];
    +
    fuse_notify_poll(ph);
    + +
    fsel_poll_notify_mask &= ~(1 << t);
    +
    fsel_poll_handle[t] = NULL;
    +
    }
    +
    }
    +
    +
    idx = (idx + 1) % FSEL_FILES;
    +
    if (idx == 0)
    +
    nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
    +
    +
    pthread_mutex_unlock(&fsel_mutex);
    +
    +
    nanosleep(&interval, NULL);
    +
    }
    +
    +
    return NULL;
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    pthread_attr_t attr;
    +
    int ret;
    +
    +
    errno = pthread_mutex_init(&fsel_mutex, NULL);
    +
    if (errno) {
    +
    perror("pthread_mutex_init");
    +
    return 1;
    +
    }
    +
    +
    errno = pthread_attr_init(&attr);
    +
    if (errno) {
    +
    perror("pthread_attr_init");
    +
    return 1;
    +
    }
    +
    +
    errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
    +
    if (errno) {
    +
    perror("pthread_create");
    +
    return 1;
    +
    }
    +
    +
    ret = fuse_main(argc, argv, &fsel_oper, NULL);
    +
    +
    return ret;
    +
    }
    + +
    struct fuse_context * fuse_get_context(void)
    Definition fuse.c:4669
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
    + +
    struct fuse * fuse
    Definition fuse.h:862
    + + +
    uint32_t nonseekable
    Definition fuse_common.h:84
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    + + +
    void(* destroy)(void *private_data)
    Definition fuse.h:649
    +
    +

    Definition in file poll.c.

    +
    + + + + diff --git a/doc/html/poll_8c_source.html b/doc/html/poll_8c_source.html new file mode 100644 index 0000000..569c0b9 --- /dev/null +++ b/doc/html/poll_8c_source.html @@ -0,0 +1,365 @@ + + + + + + + +libfuse: example/poll.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    poll.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE fsel: FUSE select example
    +
    3 Copyright (C) 2008 SUSE Linux Products GmbH
    +
    4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file COPYING.
    +
    8*/
    +
    9
    +
    24#define FUSE_USE_VERSION 31
    +
    25
    +
    26#include <fuse.h>
    +
    27#include <unistd.h>
    +
    28#include <ctype.h>
    +
    29#include <string.h>
    +
    30#include <stdio.h>
    +
    31#include <stdlib.h>
    +
    32#include <errno.h>
    +
    33#include <time.h>
    +
    34#include <pthread.h>
    +
    35#include <poll.h>
    +
    36#include <stdbool.h>
    +
    37
    +
    38/*
    +
    39 * fsel_open_mask is used to limit the number of opens to 1 per file.
    +
    40 * This is to use file index (0-F) as fh as poll support requires
    +
    41 * unique fh per open file. Lifting this would require proper open
    +
    42 * file management.
    +
    43 */
    +
    44static unsigned fsel_open_mask;
    +
    45static const char fsel_hex_map[] = "0123456789ABCDEF";
    +
    46static struct fuse *fsel_fuse; /* needed for poll notification */
    +
    47
    +
    48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
    +
    49#define FSEL_FILES 16
    +
    50
    +
    51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
    +
    52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
    +
    53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
    +
    54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
    +
    55static _Atomic bool fsel_stop = false;
    +
    56static pthread_t fsel_producer_thread;
    +
    57
    +
    58
    +
    59static int fsel_path_index(const char *path)
    +
    60{
    +
    61 char ch = path[1];
    +
    62
    +
    63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
    +
    64 return -1;
    +
    65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
    +
    66}
    +
    67
    +
    68static void fsel_destroy(void *private_data)
    +
    69{
    +
    70 (void)private_data;
    +
    71
    +
    72 fsel_stop = true;
    +
    73
    +
    74 pthread_join(fsel_producer_thread, NULL);
    +
    75}
    +
    76
    +
    77static int fsel_getattr(const char *path, struct stat *stbuf,
    +
    78 struct fuse_file_info *fi)
    +
    79{
    +
    80 (void) fi;
    +
    81 int idx;
    +
    82
    +
    83 memset(stbuf, 0, sizeof(struct stat));
    +
    84
    +
    85 if (strcmp(path, "/") == 0) {
    +
    86 stbuf->st_mode = S_IFDIR | 0555;
    +
    87 stbuf->st_nlink = 2;
    +
    88 return 0;
    +
    89 }
    +
    90
    +
    91 idx = fsel_path_index(path);
    +
    92 if (idx < 0)
    +
    93 return -ENOENT;
    +
    94
    +
    95 stbuf->st_mode = S_IFREG | 0444;
    +
    96 stbuf->st_nlink = 1;
    +
    97 stbuf->st_size = fsel_cnt[idx];
    +
    98 return 0;
    +
    99}
    +
    100
    +
    101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    102 off_t offset, struct fuse_file_info *fi,
    +
    103 enum fuse_readdir_flags flags)
    +
    104{
    +
    105 char name[2] = { };
    +
    106 int i;
    +
    107
    +
    108 (void) offset;
    +
    109 (void) fi;
    +
    110 (void) flags;
    +
    111
    +
    112 if (strcmp(path, "/") != 0)
    +
    113 return -ENOENT;
    +
    114
    +
    115 for (i = 0; i < FSEL_FILES; i++) {
    +
    116 name[0] = fsel_hex_map[i];
    +
    117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    118 }
    +
    119
    +
    120 return 0;
    +
    121}
    +
    122
    +
    123static int fsel_open(const char *path, struct fuse_file_info *fi)
    +
    124{
    +
    125 int idx = fsel_path_index(path);
    +
    126
    +
    127 if (idx < 0)
    +
    128 return -ENOENT;
    +
    129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    130 return -EACCES;
    +
    131 if (fsel_open_mask & (1 << idx))
    +
    132 return -EBUSY;
    +
    133 fsel_open_mask |= (1 << idx);
    +
    134
    +
    135 /*
    +
    136 * fsel files are nonseekable somewhat pipe-like files which
    +
    137 * gets filled up periodically by producer thread and consumed
    +
    138 * on read. Tell FUSE as such.
    +
    139 */
    +
    140 fi->fh = idx;
    +
    141 fi->direct_io = 1;
    +
    142 fi->nonseekable = 1;
    +
    143
    +
    144 return 0;
    +
    145}
    +
    146
    +
    147static int fsel_release(const char *path, struct fuse_file_info *fi)
    +
    148{
    +
    149 int idx = fi->fh;
    +
    150
    +
    151 (void) path;
    +
    152
    +
    153 fsel_open_mask &= ~(1 << idx);
    +
    154 return 0;
    +
    155}
    +
    156
    +
    157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
    +
    158 struct fuse_file_info *fi)
    +
    159{
    +
    160 int idx = fi->fh;
    +
    161
    +
    162 (void) path;
    +
    163 (void) offset;
    +
    164
    +
    165 pthread_mutex_lock(&fsel_mutex);
    +
    166 if (fsel_cnt[idx] < size)
    +
    167 size = fsel_cnt[idx];
    +
    168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
    +
    169 fsel_cnt[idx] -= size;
    +
    170 pthread_mutex_unlock(&fsel_mutex);
    +
    171
    +
    172 memset(buf, fsel_hex_map[idx], size);
    +
    173 return size;
    +
    174}
    +
    175
    +
    176static int fsel_poll(const char *path, struct fuse_file_info *fi,
    +
    177 struct fuse_pollhandle *ph, unsigned *reventsp)
    +
    178{
    +
    179 static unsigned polled_zero;
    +
    180 int idx = fi->fh;
    +
    181
    +
    182 (void) path;
    +
    183
    +
    184 /*
    +
    185 * Poll notification requires pointer to struct fuse which
    +
    186 * can't be obtained when using fuse_main(). As notification
    +
    187 * happens only after poll is called, fill it here from
    +
    188 * fuse_context.
    +
    189 */
    +
    190 if (!fsel_fuse) {
    +
    191 struct fuse_context *cxt = fuse_get_context();
    +
    192 if (cxt)
    +
    193 fsel_fuse = cxt->fuse;
    +
    194 }
    +
    195
    +
    196 pthread_mutex_lock(&fsel_mutex);
    +
    197
    +
    198 if (ph != NULL) {
    +
    199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
    +
    200
    +
    201 if (oldph)
    + +
    203
    +
    204 fsel_poll_notify_mask |= (1 << idx);
    +
    205 fsel_poll_handle[idx] = ph;
    +
    206 }
    +
    207
    +
    208 if (fsel_cnt[idx]) {
    +
    209 *reventsp |= POLLIN;
    +
    210 printf("POLL %X cnt=%u polled_zero=%u\n",
    +
    211 idx, fsel_cnt[idx], polled_zero);
    +
    212 polled_zero = 0;
    +
    213 } else
    +
    214 polled_zero++;
    +
    215
    +
    216 pthread_mutex_unlock(&fsel_mutex);
    +
    217 return 0;
    +
    218}
    +
    219
    +
    220static const struct fuse_operations fsel_oper = {
    +
    221 .destroy = fsel_destroy,
    +
    222 .getattr = fsel_getattr,
    +
    223 .readdir = fsel_readdir,
    +
    224 .open = fsel_open,
    +
    225 .release = fsel_release,
    +
    226 .read = fsel_read,
    +
    227 .poll = fsel_poll,
    +
    228};
    +
    229
    +
    230static void *fsel_producer(void *data)
    +
    231{
    +
    232 const struct timespec interval = { 0, 250000000 };
    +
    233 unsigned idx = 0, nr = 1;
    +
    234
    +
    235 (void) data;
    +
    236
    +
    237 while (!fsel_stop) {
    +
    238 int i, t;
    +
    239
    +
    240 pthread_mutex_lock(&fsel_mutex);
    +
    241
    +
    242 /*
    +
    243 * This is the main producer loop which is executed
    +
    244 * ever 500ms. On each iteration, it fills one byte
    +
    245 * to 1, 2 or 4 files and sends poll notification if
    +
    246 * requested.
    +
    247 */
    +
    248 for (i = 0, t = idx; i < nr;
    +
    249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
    +
    250 if (fsel_cnt[t] == FSEL_CNT_MAX)
    +
    251 continue;
    +
    252
    +
    253 fsel_cnt[t]++;
    +
    254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
    +
    255 struct fuse_pollhandle *ph;
    +
    256
    +
    257 printf("NOTIFY %X\n", t);
    +
    258 ph = fsel_poll_handle[t];
    +
    259 fuse_notify_poll(ph);
    + +
    261 fsel_poll_notify_mask &= ~(1 << t);
    +
    262 fsel_poll_handle[t] = NULL;
    +
    263 }
    +
    264 }
    +
    265
    +
    266 idx = (idx + 1) % FSEL_FILES;
    +
    267 if (idx == 0)
    +
    268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
    +
    269
    +
    270 pthread_mutex_unlock(&fsel_mutex);
    +
    271
    +
    272 nanosleep(&interval, NULL);
    +
    273 }
    +
    274
    +
    275 return NULL;
    +
    276}
    +
    277
    +
    278int main(int argc, char *argv[])
    +
    279{
    +
    280 pthread_attr_t attr;
    +
    281 int ret;
    +
    282
    +
    283 errno = pthread_mutex_init(&fsel_mutex, NULL);
    +
    284 if (errno) {
    +
    285 perror("pthread_mutex_init");
    +
    286 return 1;
    +
    287 }
    +
    288
    +
    289 errno = pthread_attr_init(&attr);
    +
    290 if (errno) {
    +
    291 perror("pthread_attr_init");
    +
    292 return 1;
    +
    293 }
    +
    294
    +
    295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
    +
    296 if (errno) {
    +
    297 perror("pthread_create");
    +
    298 return 1;
    +
    299 }
    +
    300
    +
    301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
    +
    302
    +
    303 return ret;
    +
    304}
    + +
    struct fuse_context * fuse_get_context(void)
    Definition fuse.c:4669
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
    + +
    struct fuse * fuse
    Definition fuse.h:862
    + + +
    uint32_t nonseekable
    Definition fuse_common.h:84
    +
    uint32_t direct_io
    Definition fuse_common.h:69
    + + +
    void(* destroy)(void *private_data)
    Definition fuse.h:649
    +
    + + + + diff --git a/doc/html/poll__client_8c.html b/doc/html/poll__client_8c.html new file mode 100644 index 0000000..431d9ff --- /dev/null +++ b/doc/html/poll__client_8c.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: example/poll_client.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    poll_client.c File Reference
    +
    +
    +
    #include <sys/select.h>
    +#include <sys/time.h>
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +#include <fcntl.h>
    +#include <unistd.h>
    +#include <ctype.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <errno.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This program tests the poll.c example file systsem.

    +

    Compile with:

     gcc -Wall poll_client.c -o poll_client
    +

    +Source code

    +
    /*
    +
    FUSE fselclient: FUSE select example client
    +
    Copyright (C) 2008 SUSE Linux Products GmbH
    +
    Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #include <sys/select.h>
    +
    #include <sys/time.h>
    +
    #include <sys/types.h>
    +
    #include <sys/stat.h>
    +
    #include <fcntl.h>
    +
    #include <unistd.h>
    +
    #include <ctype.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <errno.h>
    +
    +
    #define FSEL_FILES 16
    +
    +
    int main(void)
    +
    {
    +
    static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
    +
    int fds[FSEL_FILES];
    +
    int i, nfds, tries;
    +
    +
    for (i = 0; i < FSEL_FILES; i++) {
    +
    char name[] = { hex_map[i], '\0' };
    +
    fds[i] = open(name, O_RDONLY);
    +
    if (fds[i] < 0) {
    +
    perror("open");
    +
    return 1;
    +
    }
    +
    }
    +
    nfds = fds[FSEL_FILES - 1] + 1;
    +
    +
    for(tries=0; tries < 16; tries++) {
    +
    static char buf[4096];
    +
    fd_set rfds;
    +
    int rc;
    +
    +
    FD_ZERO(&rfds);
    +
    for (i = 0; i < FSEL_FILES; i++)
    +
    FD_SET(fds[i], &rfds);
    +
    +
    rc = select(nfds, &rfds, NULL, NULL, NULL);
    +
    +
    if (rc < 0) {
    +
    perror("select");
    +
    return 1;
    +
    }
    +
    +
    for (i = 0; i < FSEL_FILES; i++) {
    +
    if (!FD_ISSET(fds[i], &rfds)) {
    +
    printf("_: ");
    +
    continue;
    +
    }
    +
    printf("%X:", i);
    +
    rc = read(fds[i], buf, sizeof(buf));
    +
    if (rc < 0) {
    +
    perror("read");
    +
    return 1;
    +
    }
    +
    printf("%02d ", rc);
    +
    }
    +
    printf("\n");
    +
    }
    +
    return 0;
    +
    }
    +
    +

    Definition in file poll_client.c.

    +
    + + + + diff --git a/doc/html/poll__client_8c_source.html b/doc/html/poll__client_8c_source.html new file mode 100644 index 0000000..fc927c6 --- /dev/null +++ b/doc/html/poll__client_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: example/poll_client.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    poll_client.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE fselclient: FUSE select example client
    +
    3 Copyright (C) 2008 SUSE Linux Products GmbH
    +
    4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file COPYING.
    +
    8*/
    +
    9
    +
    23#include <sys/select.h>
    +
    24#include <sys/time.h>
    +
    25#include <sys/types.h>
    +
    26#include <sys/stat.h>
    +
    27#include <fcntl.h>
    +
    28#include <unistd.h>
    +
    29#include <ctype.h>
    +
    30#include <stdio.h>
    +
    31#include <stdlib.h>
    +
    32#include <errno.h>
    +
    33
    +
    34#define FSEL_FILES 16
    +
    35
    +
    36int main(void)
    +
    37{
    +
    38 static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
    +
    39 int fds[FSEL_FILES];
    +
    40 int i, nfds, tries;
    +
    41
    +
    42 for (i = 0; i < FSEL_FILES; i++) {
    +
    43 char name[] = { hex_map[i], '\0' };
    +
    44 fds[i] = open(name, O_RDONLY);
    +
    45 if (fds[i] < 0) {
    +
    46 perror("open");
    +
    47 return 1;
    +
    48 }
    +
    49 }
    +
    50 nfds = fds[FSEL_FILES - 1] + 1;
    +
    51
    +
    52 for(tries=0; tries < 16; tries++) {
    +
    53 static char buf[4096];
    +
    54 fd_set rfds;
    +
    55 int rc;
    +
    56
    +
    57 FD_ZERO(&rfds);
    +
    58 for (i = 0; i < FSEL_FILES; i++)
    +
    59 FD_SET(fds[i], &rfds);
    +
    60
    +
    61 rc = select(nfds, &rfds, NULL, NULL, NULL);
    +
    62
    +
    63 if (rc < 0) {
    +
    64 perror("select");
    +
    65 return 1;
    +
    66 }
    +
    67
    +
    68 for (i = 0; i < FSEL_FILES; i++) {
    +
    69 if (!FD_ISSET(fds[i], &rfds)) {
    +
    70 printf("_: ");
    +
    71 continue;
    +
    72 }
    +
    73 printf("%X:", i);
    +
    74 rc = read(fds[i], buf, sizeof(buf));
    +
    75 if (rc < 0) {
    +
    76 perror("read");
    +
    77 return 1;
    +
    78 }
    +
    79 printf("%02d ", rc);
    +
    80 }
    +
    81 printf("\n");
    +
    82 }
    +
    83 return 0;
    +
    84}
    +
    + + + + diff --git a/doc/html/printcap_8c.html b/doc/html/printcap_8c.html new file mode 100644 index 0000000..493d356 --- /dev/null +++ b/doc/html/printcap_8c.html @@ -0,0 +1,240 @@ + + + + + + + +libfuse: example/printcap.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    printcap.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <unistd.h>
    +#include <string.h>
    +#include <stdlib.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

    +

    Compile with:

    gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <unistd.h>
    +
    #include <string.h>
    +
    #include <stdlib.h>
    +
    +
    struct fuse_session *se;
    +
    +
    // Define a structure to hold capability information
    +
    struct cap_info {
    +
    uint64_t flag;
    +
    const char *name;
    +
    };
    +
    +
    // Define an array of all capabilities
    +
    static const struct cap_info capabilities[] = {
    +
    {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
    +
    {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
    +
    {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
    +
    {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
    +
    {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
    +
    {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
    +
    {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
    +
    {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
    +
    {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
    +
    {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
    +
    {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
    +
    {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
    +
    {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
    +
    {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
    +
    {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
    +
    {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
    +
    {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
    +
    {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
    +
    {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
    +
    {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
    +
    {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
    +
    {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
    +
    {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
    +
    {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
    +
    {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
    +
    {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
    +
    {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
    +
    {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
    +
    // Add any new capabilities here
    +
    {0, NULL} // Sentinel to mark the end of the array
    +
    };
    +
    +
    static void print_capabilities(struct fuse_conn_info *conn)
    +
    {
    +
    printf("Capabilities:\n");
    +
    for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
    +
    if (fuse_get_feature_flag(conn, cap->flag)) {
    +
    printf("\t%s\n", cap->name);
    +
    }
    +
    }
    +
    }
    +
    +
    static void pc_init(void *userdata, struct fuse_conn_info *conn)
    +
    {
    +
    (void) userdata;
    +
    +
    printf("Protocol version: %d.%d\n", conn->proto_major,
    +
    conn->proto_minor);
    +
    print_capabilities(conn);
    + +
    }
    +
    +
    +
    static const struct fuse_lowlevel_ops pc_oper = {
    +
    .init = pc_init,
    +
    };
    +
    +
    int main(int argc, char **argv)
    +
    {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    char *mountpoint;
    +
    int ret = -1;
    +
    +
    mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
    +
    if(mkdtemp(mountpoint) == NULL) {
    +
    perror("mkdtemp");
    +
    return 1;
    +
    }
    +
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    +
    se = fuse_session_new(&args, &pc_oper,
    +
    sizeof(pc_oper), NULL);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    ret = fuse_session_loop(se);
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    rmdir(mountpoint);
    +
    free(mountpoint);
    + +
    +
    return ret ? 1 : 0;
    +
    }
    +
    #define FUSE_CAP_IOCTL_DIR
    +
    #define FUSE_CAP_DONT_MASK
    +
    #define FUSE_CAP_HANDLE_KILLPRIV
    +
    #define FUSE_CAP_AUTO_INVAL_DATA
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_HANDLE_KILLPRIV_V2
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_PARALLEL_DIROPS
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    #define FUSE_CAP_EXPIRE_ONLY
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_CACHE_SYMLINKS
    +
    #define FUSE_CAP_POSIX_ACL
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_EXPLICIT_INVAL_DATA
    +
    #define FUSE_CAP_READDIRPLUS_AUTO
    +
    #define FUSE_CAP_NO_OPENDIR_SUPPORT
    +
    #define FUSE_CAP_ASYNC_DIO
    +
    #define FUSE_CAP_PASSTHROUGH
    +
    #define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
    +
    #define FUSE_CAP_NO_OPEN_SUPPORT
    +
    #define FUSE_CAP_READDIRPLUS
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_SETXATTR_EXT
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_NO_EXPORT_SUPPORT
    +
    #define FUSE_CAP_FLOCK_LOCKS
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    uint32_t proto_major
    +
    uint32_t proto_minor
    + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    +
    +

    Definition in file printcap.c.

    +
    + + + + diff --git a/doc/html/printcap_8c_source.html b/doc/html/printcap_8c_source.html new file mode 100644 index 0000000..136b06d --- /dev/null +++ b/doc/html/printcap_8c_source.html @@ -0,0 +1,231 @@ + + + + + + + +libfuse: example/printcap.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    printcap.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    22#define FUSE_USE_VERSION 31
    +
    23
    +
    24#include <fuse_lowlevel.h>
    +
    25#include <stdio.h>
    +
    26#include <unistd.h>
    +
    27#include <string.h>
    +
    28#include <stdlib.h>
    +
    29
    +
    30struct fuse_session *se;
    +
    31
    +
    32// Define a structure to hold capability information
    +
    33struct cap_info {
    +
    34 uint64_t flag;
    +
    35 const char *name;
    +
    36};
    +
    37
    +
    38// Define an array of all capabilities
    +
    39static const struct cap_info capabilities[] = {
    +
    40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
    +
    41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
    +
    42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
    +
    43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
    +
    44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
    +
    45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
    +
    46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
    +
    47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
    +
    48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
    +
    49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
    +
    50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
    +
    51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
    +
    52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
    +
    53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
    +
    54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
    +
    55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
    +
    56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
    +
    57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
    +
    58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
    +
    59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
    +
    60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
    +
    61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
    +
    62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
    +
    63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
    +
    64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
    +
    65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
    +
    66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
    +
    67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
    +
    68 // Add any new capabilities here
    +
    69 {0, NULL} // Sentinel to mark the end of the array
    +
    70};
    +
    71
    +
    72static void print_capabilities(struct fuse_conn_info *conn)
    +
    73{
    +
    74 printf("Capabilities:\n");
    +
    75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
    +
    76 if (fuse_get_feature_flag(conn, cap->flag)) {
    +
    77 printf("\t%s\n", cap->name);
    +
    78 }
    +
    79 }
    +
    80}
    +
    81
    +
    82static void pc_init(void *userdata, struct fuse_conn_info *conn)
    +
    83{
    +
    84 (void) userdata;
    +
    85
    +
    86 printf("Protocol version: %d.%d\n", conn->proto_major,
    +
    87 conn->proto_minor);
    +
    88 print_capabilities(conn);
    + +
    90}
    +
    91
    +
    92
    +
    93static const struct fuse_lowlevel_ops pc_oper = {
    +
    94 .init = pc_init,
    +
    95};
    +
    96
    +
    97int main(int argc, char **argv)
    +
    98{
    +
    99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    100 char *mountpoint;
    +
    101 int ret = -1;
    +
    102
    +
    103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
    +
    104 if(mkdtemp(mountpoint) == NULL) {
    +
    105 perror("mkdtemp");
    +
    106 return 1;
    +
    107 }
    +
    108
    +
    109 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    111
    +
    112 se = fuse_session_new(&args, &pc_oper,
    +
    113 sizeof(pc_oper), NULL);
    +
    114 if (se == NULL)
    +
    115 goto err_out1;
    +
    116
    +
    117 if (fuse_set_signal_handlers(se) != 0)
    +
    118 goto err_out2;
    +
    119
    +
    120 if (fuse_session_mount(se, mountpoint) != 0)
    +
    121 goto err_out3;
    +
    122
    +
    123 ret = fuse_session_loop(se);
    +
    124
    + +
    126err_out3:
    + +
    128err_out2:
    + +
    130err_out1:
    +
    131 rmdir(mountpoint);
    +
    132 free(mountpoint);
    +
    133 fuse_opt_free_args(&args);
    +
    134
    +
    135 return ret ? 1 : 0;
    +
    136}
    +
    #define FUSE_CAP_IOCTL_DIR
    +
    #define FUSE_CAP_DONT_MASK
    +
    #define FUSE_CAP_HANDLE_KILLPRIV
    +
    #define FUSE_CAP_AUTO_INVAL_DATA
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_HANDLE_KILLPRIV_V2
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_PARALLEL_DIROPS
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    #define FUSE_CAP_EXPIRE_ONLY
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_CACHE_SYMLINKS
    +
    #define FUSE_CAP_POSIX_ACL
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_EXPLICIT_INVAL_DATA
    +
    #define FUSE_CAP_READDIRPLUS_AUTO
    +
    #define FUSE_CAP_NO_OPENDIR_SUPPORT
    +
    #define FUSE_CAP_ASYNC_DIO
    +
    #define FUSE_CAP_PASSTHROUGH
    +
    #define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
    +
    #define FUSE_CAP_NO_OPEN_SUPPORT
    +
    #define FUSE_CAP_READDIRPLUS
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5236
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_SETXATTR_EXT
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_NO_EXPORT_SUPPORT
    +
    #define FUSE_CAP_FLOCK_LOCKS
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    uint32_t proto_major
    +
    uint32_t proto_minor
    + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    +
    + + + + diff --git a/doc/html/readdir__inode_8c_source.html b/doc/html/readdir__inode_8c_source.html new file mode 100644 index 0000000..bf625c6 --- /dev/null +++ b/doc/html/readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: test/readdir_inode.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    readdir_inode.c
    +
    +
    +
    1/*
    +
    2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
    +
    3 * Skips '.' and '..' because readdir is not required to return them and
    +
    4 * some of our examples don't. However if they are returned, their d_type
    +
    5 * should be valid.
    +
    6 */
    +
    7
    +
    8#include <stdio.h>
    +
    9#include <string.h>
    +
    10#include <sys/types.h>
    +
    11#include <dirent.h>
    +
    12#include <errno.h>
    +
    13
    +
    14int main(int argc, char* argv[])
    +
    15{
    +
    16 DIR* dirp;
    +
    17 struct dirent* dent;
    +
    18
    +
    19 if (argc != 2) {
    +
    20 fprintf(stderr, "Usage: readdir_inode dir\n");
    +
    21 return 1;
    +
    22 }
    +
    23
    +
    24 dirp = opendir(argv[1]);
    +
    25 if (dirp == NULL) {
    +
    26 perror("failed to open directory");
    +
    27 return 2;
    +
    28 }
    +
    29
    +
    30 errno = 0;
    +
    31 dent = readdir(dirp);
    +
    32 while (dent != NULL) {
    +
    33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
    +
    34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
    +
    35 (int)dent->d_type, dent->d_name);
    +
    36 if ((long long)dent->d_ino < 0)
    +
    37 fprintf(stderr,"%s : bad d_ino %llu\n",
    +
    38 dent->d_name, (unsigned long long)dent->d_ino);
    +
    39 if ((dent->d_type < 1) || (dent->d_type > 15))
    +
    40 fprintf(stderr,"%s : bad d_type %d\n",
    +
    41 dent->d_name, (int)dent->d_type);
    +
    42 } else {
    +
    43 if (dent->d_type != DT_DIR)
    +
    44 fprintf(stderr,"%s : bad d_type %d\n",
    +
    45 dent->d_name, (int)dent->d_type);
    +
    46 }
    +
    47 dent = readdir(dirp);
    +
    48 }
    +
    49 if (errno != 0) {
    +
    50 perror("failed to read directory entry");
    +
    51 return 3;
    +
    52 }
    +
    53
    +
    54 closedir(dirp);
    +
    55
    +
    56 return 0;
    +
    57}
    +
    + + + + diff --git a/doc/html/release__unlink__race_8c_source.html b/doc/html/release__unlink__race_8c_source.html new file mode 100644 index 0000000..222e95c --- /dev/null +++ b/doc/html/release__unlink__race_8c_source.html @@ -0,0 +1,184 @@ + + + + + + + +libfuse: test/release_unlink_race.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    release_unlink_race.c
    +
    +
    +
    1/*
    +
    2 This program can be distributed under the terms of the GNU GPLv2.
    +
    3 See the file COPYING.
    +
    4*/
    +
    5
    +
    6#define FUSE_USE_VERSION 31
    +
    7
    +
    8#define _GNU_SOURCE
    +
    9
    +
    10#include <fuse.h>
    +
    11
    +
    12#include <stdio.h>
    +
    13#include <stdlib.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16
    +
    17static void *xmp_init(struct fuse_conn_info *conn,
    +
    18 struct fuse_config *cfg)
    +
    19{
    +
    20 (void) conn;
    +
    21
    +
    22 cfg->use_ino = 1;
    +
    23 cfg->nullpath_ok = 1;
    +
    24 cfg->entry_timeout = 0;
    +
    25 cfg->attr_timeout = 0;
    +
    26 cfg->negative_timeout = 0;
    +
    27
    +
    28 return NULL;
    +
    29}
    +
    30
    +
    31static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    32 struct fuse_file_info *fi)
    +
    33{
    +
    34 int res;
    +
    35
    +
    36 (void) path;
    +
    37
    +
    38 if(fi)
    +
    39 res = fstat(fi->fh, stbuf);
    +
    40 else
    +
    41 res = lstat(path, stbuf);
    +
    42 if (res == -1)
    +
    43 return -errno;
    +
    44
    +
    45 return 0;
    +
    46}
    +
    47
    +
    48static int xmp_unlink(const char *path)
    +
    49{
    +
    50 int res;
    +
    51
    +
    52 res = unlink(path);
    +
    53 if (res == -1)
    +
    54 return -errno;
    +
    55
    +
    56 return 0;
    +
    57}
    +
    58
    +
    59static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    60{
    +
    61 int res;
    +
    62
    +
    63 if (flags)
    +
    64 return -EINVAL;
    +
    65
    +
    66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    67
    +
    68 res = rename(from, to);
    +
    69 if (res == -1)
    +
    70 return -errno;
    +
    71
    +
    72 return 0;
    +
    73}
    +
    74
    +
    75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    76{
    +
    77 int fd;
    +
    78
    +
    79 fd = open(path, fi->flags, mode);
    +
    80 if (fd == -1)
    +
    81 return -errno;
    +
    82
    +
    83 fi->fh = fd;
    +
    84 return 0;
    +
    85}
    +
    86
    +
    87static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    88{
    +
    89 (void) path;
    +
    90
    +
    91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    92
    +
    93 close(fi->fh);
    +
    94
    +
    95 return 0;
    +
    96}
    +
    97
    +
    98static const struct fuse_operations xmp_oper = {
    +
    99 .init = xmp_init,
    +
    100 .getattr = xmp_getattr,
    +
    101 .unlink = xmp_unlink,
    +
    102 .rename = xmp_rename,
    +
    103 .create = xmp_create,
    +
    104 .release = xmp_release,
    +
    105};
    +
    106
    +
    107int main(int argc, char *argv[])
    +
    108{
    +
    109 umask(0);
    +
    110 return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    111}
    + + +
    int32_t nullpath_ok
    Definition fuse.h:273
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + + + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    + + + + diff --git a/doc/html/sanitycheckc_8c_source.html b/doc/html/sanitycheckc_8c_source.html new file mode 100644 index 0000000..68cfeaa --- /dev/null +++ b/doc/html/sanitycheckc_8c_source.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: build-ubuntu/meson-private/sanitycheckc.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    sanitycheckc.c
    +
    +
    +
    1int main(void) { int class=0; return class; }
    +
    + + + + diff --git a/doc/html/splitbar.png b/doc/html/splitbar.png new file mode 100644 index 0000000000000000000000000000000000000000..fe895f2c58179b471a22d8320b39a4bd7312ec8e GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^Yzz!63>-{AmhX=Jf(#6djGiuzAr*{o?=JLmPLyc> z_*`QK&+BH@jWrYJ7>r6%keRM@)Qyv8R=enp0jiI>aWlGyB58O zFVR20d+y`K7vDw(hJF3;>dD*3-?v=<8M)@x|EEGLnJsniYK!2U1 Y!`|5biEc?d1`HDhPgg&ebxsLQ02F6;9RL6T literal 0 HcmV?d00001 diff --git a/doc/html/splitbard.png b/doc/html/splitbard.png new file mode 100644 index 0000000000000000000000000000000000000000..8367416d757fd7b6dc4272b6432dc75a75abd068 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^Yzz!63>-{AmhX=Jf@VhhFKy35^fiT zT~&lUj3=cDh^%3HDY9k5CEku}PHXNoNC(_$U3XPb&Q*ME25pT;2(*BOgAf<+R$lzakPG`kF31()Fx{L5Wrac|GQzjeE= zueY1`Ze{#x<8=S|`~MgGetGce)#vN&|J{Cd^tS%;tBYTo?+^d68<#n_Y_xx`J||4O V@QB{^CqU0Kc)I$ztaD0e0svEzbJzd? literal 0 HcmV?d00001 diff --git a/doc/html/stracedecode_8c_source.html b/doc/html/stracedecode_8c_source.html new file mode 100644 index 0000000..8f898ca --- /dev/null +++ b/doc/html/stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: test/stracedecode.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    stracedecode.c
    +
    +
    +
    1#include <stdio.h>
    +
    2#include <string.h>
    +
    3#include "fuse_kernel.h"
    +
    4
    +
    5static struct {
    +
    6 const char *name;
    +
    7} fuse_ll_ops[] = {
    +
    8 [FUSE_LOOKUP] = { "LOOKUP" },
    +
    9 [FUSE_FORGET] = { "FORGET" },
    +
    10 [FUSE_GETATTR] = { "GETATTR" },
    +
    11 [FUSE_SETATTR] = { "SETATTR" },
    +
    12 [FUSE_READLINK] = { "READLINK" },
    +
    13 [FUSE_SYMLINK] = { "SYMLINK" },
    +
    14 [FUSE_MKNOD] = { "MKNOD" },
    +
    15 [FUSE_MKDIR] = { "MKDIR" },
    +
    16 [FUSE_UNLINK] = { "UNLINK" },
    +
    17 [FUSE_RMDIR] = { "RMDIR" },
    +
    18 [FUSE_RENAME] = { "RENAME" },
    +
    19 [FUSE_LINK] = { "LINK" },
    +
    20 [FUSE_OPEN] = { "OPEN" },
    +
    21 [FUSE_READ] = { "READ" },
    +
    22 [FUSE_WRITE] = { "WRITE" },
    +
    23 [FUSE_STATFS] = { "STATFS" },
    +
    24 [FUSE_RELEASE] = { "RELEASE" },
    +
    25 [FUSE_FSYNC] = { "FSYNC" },
    +
    26 [FUSE_SETXATTR] = { "SETXATTR" },
    +
    27 [FUSE_GETXATTR] = { "GETXATTR" },
    +
    28 [FUSE_LISTXATTR] = { "LISTXATTR" },
    +
    29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
    +
    30 [FUSE_FLUSH] = { "FLUSH" },
    +
    31 [FUSE_INIT] = { "INIT" },
    +
    32 [FUSE_OPENDIR] = { "OPENDIR" },
    +
    33 [FUSE_READDIR] = { "READDIR" },
    +
    34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
    +
    35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
    +
    36 [FUSE_GETLK] = { "GETLK" },
    +
    37 [FUSE_SETLK] = { "SETLK" },
    +
    38 [FUSE_SETLKW] = { "SETLKW" },
    +
    39 [FUSE_ACCESS] = { "ACCESS" },
    +
    40 [FUSE_CREATE] = { "CREATE" },
    +
    41 [FUSE_TMPFILE] = { "TMPFILE" },
    +
    42 [FUSE_INTERRUPT] = { "INTERRUPT" },
    +
    43 [FUSE_BMAP] = { "BMAP" },
    +
    44 [FUSE_DESTROY] = { "DESTROY" },
    +
    45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
    +
    46};
    +
    47
    +
    48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
    +
    49
    +
    50static const char *opname(enum fuse_opcode opcode)
    +
    51{
    +
    52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
    +
    53 return "???";
    +
    54 else
    +
    55 return fuse_ll_ops[opcode].name;
    +
    56}
    +
    57
    +
    58
    +
    59static void process_buf(int dir, char *buf, int len)
    +
    60{
    +
    61 static unsigned long long prevuniq = -1;
    +
    62 static int prevopcode;
    +
    63
    +
    64 if (!dir) {
    +
    65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
    +
    66 buf += sizeof(struct fuse_in_header);
    +
    67
    +
    68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
    +
    69 (unsigned long long) in->unique,
    +
    70 opname((enum fuse_opcode) in->opcode), in->opcode,
    +
    71 (unsigned long) in->nodeid, in->len, len);
    +
    72
    +
    73 switch (in->opcode) {
    +
    74 case FUSE_READ: {
    +
    75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
    +
    76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
    +
    77 arg->fh, arg->offset, arg->size, arg->read_flags,
    +
    78 arg->lock_owner, arg->flags);
    +
    79 break;
    +
    80 }
    +
    81 case FUSE_WRITE: {
    +
    82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
    +
    83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
    +
    84 arg->fh, arg->offset, arg->size, arg->write_flags,
    +
    85 arg->lock_owner, arg->flags);
    +
    86 break;
    +
    87 }
    +
    88 }
    +
    89 prevuniq = in->unique;
    +
    90 prevopcode = in->opcode;
    +
    91 } else {
    +
    92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
    +
    93 buf += sizeof(struct fuse_out_header);
    +
    94
    +
    95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
    +
    96 (unsigned long long) out->unique, out->error,
    +
    97 strerror(-out->error), out->len, len);
    +
    98
    +
    99 if (out->unique == prevuniq) {
    +
    100 switch (prevopcode) {
    +
    101 case FUSE_GETATTR: {
    +
    102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
    +
    103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    104 arg->attr_valid, arg->attr_valid_nsec,
    +
    105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    106 break;
    +
    107 }
    +
    108 case FUSE_LOOKUP: {
    +
    109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
    +
    110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
    +
    112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    113 break;
    +
    114 }
    +
    115 }
    +
    116 }
    +
    117 }
    +
    118
    +
    119}
    +
    120
    +
    121int main(void)
    +
    122{
    +
    123 FILE *in = stdin;
    +
    124 while (1) {
    +
    125 int dir;
    +
    126 int res;
    +
    127 char buf[1048576];
    +
    128 unsigned len = 0;
    +
    129
    +
    130 memset(buf, 0, sizeof(buf));
    +
    131 while (1) {
    +
    132 char str[32];
    +
    133
    +
    134 res = fscanf(in, "%30s", str);
    +
    135 if (res != 1 && feof(in))
    +
    136 return 0;
    +
    137
    +
    138 if (res == 0)
    +
    139 continue;
    +
    140
    +
    141 if (strncmp(str, "read(", 5) == 0) {
    +
    142 dir = 0;
    +
    143 break;
    +
    144 } else if (strncmp(str, "writev(", 7) == 0) {
    +
    145 dir = 1;
    +
    146 break;
    +
    147 }
    +
    148 }
    +
    149
    +
    150 while (1) {
    +
    151 int c = getc(in);
    +
    152 if (c == '"') {
    +
    153 while (1) {
    +
    154 int val;
    +
    155
    +
    156 c = getc(in);
    +
    157 if (c == EOF) {
    +
    158 fprintf(stderr, "eof in string\n");
    +
    159 break;
    +
    160 }
    +
    161 if (c == '\n') {
    +
    162 fprintf(stderr, "eol in string\n");
    +
    163 break;
    +
    164 }
    +
    165 if (c == '"')
    +
    166 break;
    +
    167 if (c != '\\') {
    +
    168 val = c;
    +
    169 } else {
    +
    170 c = getc(in);
    +
    171 switch (c) {
    +
    172 case 'n': val = '\n'; break;
    +
    173 case 'r': val = '\r'; break;
    +
    174 case 't': val = '\t'; break;
    +
    175 case '"': val = '"'; break;
    +
    176 case '\\': val = '\\'; break;
    +
    177 case 'x':
    +
    178 res = scanf("%x", &val);
    +
    179 if (res != 1) {
    +
    180 fprintf(stderr, "parse error\n");
    +
    181 continue;
    +
    182 }
    +
    183 break;
    +
    184 default:
    +
    185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
    +
    186 continue;
    +
    187 }
    +
    188 }
    +
    189 buf[len++] = val;
    +
    190 }
    +
    191 }
    +
    192 if (c == '\n')
    +
    193 break;
    +
    194 }
    +
    195 process_buf(dir, buf, len);
    +
    196 memset(buf, 0, len);
    +
    197 len = 0;
    +
    198 }
    +
    199}
    +
    + + + + diff --git a/doc/html/structfuse__args.html b/doc/html/structfuse__args.html new file mode 100644 index 0000000..0edbdc3 --- /dev/null +++ b/doc/html/structfuse__args.html @@ -0,0 +1,124 @@ + + + + + + + +libfuse: fuse_args Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_args Struct Reference
    +
    +
    + +

    #include <fuse_opt.h>

    + + + + + + + + +

    +Data Fields

    int argc
     
    char ** argv
     
    int allocated
     
    +

    Detailed Description

    +

    Argument list

    + +

    Definition at line 109 of file fuse_opt.h.

    +

    Field Documentation

    + +

    ◆ allocated

    + +
    +
    + + + + +
    int fuse_args::allocated
    +
    +

    Is 'argv' allocated?

    + +

    Definition at line 117 of file fuse_opt.h.

    + +
    +
    + +

    ◆ argc

    + +
    +
    + + + + +
    int fuse_args::argc
    +
    +

    Argument count

    + +

    Definition at line 111 of file fuse_opt.h.

    + +
    +
    + +

    ◆ argv

    + +
    +
    + + + + +
    char** fuse_args::argv
    +
    +

    Argument vector. NULL terminated

    + +

    Definition at line 114 of file fuse_opt.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__buf.html b/doc/html/structfuse__buf.html new file mode 100644 index 0000000..5a65739 --- /dev/null +++ b/doc/html/structfuse__buf.html @@ -0,0 +1,186 @@ + + + + + + + +libfuse: fuse_buf Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_buf Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + +

    +Data Fields

    size_t size
     
    enum fuse_buf_flags flags
     
    void * mem
     
    int fd
     
    off_t pos
     
    size_t mem_size
     
    +

    Detailed Description

    +

    Single data buffer

    +

    Generic data buffer for I/O, extended attributes, etc... Data may be supplied as a memory pointer or as a file descriptor

    + +

    Definition at line 884 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ fd

    + +
    +
    + + + + +
    int fuse_buf::fd
    +
    +

    File descriptor

    +

    Used if FUSE_BUF_IS_FD flag is set.

    + +

    Definition at line 907 of file fuse_common.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    enum fuse_buf_flags fuse_buf::flags
    +
    +

    Buffer flags

    + +

    Definition at line 893 of file fuse_common.h.

    + +
    +
    + +

    ◆ mem

    + +
    +
    + + + + +
    void* fuse_buf::mem
    +
    +

    Memory pointer

    +

    Used unless FUSE_BUF_IS_FD flag is set.

    + +

    Definition at line 900 of file fuse_common.h.

    + +
    +
    + +

    ◆ mem_size

    + +
    +
    + + + + +
    size_t fuse_buf::mem_size
    +
    +

    Size of memory pointer

    +

    Used only if mem was internally allocated. Not used if mem was user-provided.

    + +

    Definition at line 922 of file fuse_common.h.

    + +
    +
    + +

    ◆ pos

    + +
    +
    + + + + +
    off_t fuse_buf::pos
    +
    +

    File position

    +

    Used if FUSE_BUF_FD_SEEK flag is set.

    + +

    Definition at line 914 of file fuse_common.h.

    + +
    +
    + +

    ◆ size

    + +
    +
    + + + + +
    size_t fuse_buf::size
    +
    +

    Size of data in bytes

    + +

    Definition at line 888 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__bufvec.html b/doc/html/structfuse__bufvec.html new file mode 100644 index 0000000..a25768b --- /dev/null +++ b/doc/html/structfuse__bufvec.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse_bufvec Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_bufvec Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + +

    +Data Fields

    size_t count
     
    size_t idx
     
    size_t off
     
    struct fuse_buf buf [1]
     
    +

    Detailed Description

    +

    Data buffer vector

    +

    An array of data buffers, each containing a memory pointer or a file descriptor.

    +

    Allocate dynamically to add more than one buffer.

    + +

    Definition at line 933 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ buf

    + +
    +
    + + + + +
    struct fuse_buf fuse_bufvec::buf[1]
    +
    +

    Array of buffers

    + +

    Definition at line 952 of file fuse_common.h.

    + +
    +
    + +

    ◆ count

    + +
    +
    + + + + +
    size_t fuse_bufvec::count
    +
    +

    Number of buffers in the array

    + +

    Definition at line 937 of file fuse_common.h.

    + +
    +
    + +

    ◆ idx

    + +
    +
    + + + + +
    size_t fuse_bufvec::idx
    +
    +

    Index of current buffer within the array

    + +

    Definition at line 942 of file fuse_common.h.

    + +
    +
    + +

    ◆ off

    + +
    +
    + + + + +
    size_t fuse_bufvec::off
    +
    +

    Current offset within the current buffer

    + +

    Definition at line 947 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__cmdline__opts.html b/doc/html/structfuse__cmdline__opts.html new file mode 100644 index 0000000..53b7f70 --- /dev/null +++ b/doc/html/structfuse__cmdline__opts.html @@ -0,0 +1,60 @@ + + + + + + + +libfuse: fuse_cmdline_opts Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_cmdline_opts Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    +

    Detailed Description

    +

    Note: Any addition to this struct needs to create a compatibility symbol for fuse_parse_cmdline(). For ABI compatibility reasons it is also not possible to remove struct members.

    + +

    Definition at line 2003 of file fuse_lowlevel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__config.html b/doc/html/structfuse__config.html new file mode 100644 index 0000000..4d5a592 --- /dev/null +++ b/doc/html/structfuse__config.html @@ -0,0 +1,500 @@ + + + + + + + +libfuse: fuse_config Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_config Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int32_t set_gid
     
    int32_t set_uid
     
    int32_t set_mode
     
    double entry_timeout
     
    double negative_timeout
     
    double attr_timeout
     
    int32_t intr
     
    int32_t intr_signal
     
    int32_t remember
     
    int32_t hard_remove
     
    int32_t use_ino
     
    int32_t readdir_ino
     
    int32_t direct_io
     
    int32_t kernel_cache
     
    int32_t auto_cache
     
    int32_t nullpath_ok
     
    int32_t show_help
     
    uint32_t fmask
     
    int32_t no_rofd_flush
     
    int32_t parallel_direct_writes
     
    uint32_t flags
     
    uint64_t reserved [48]
     
    +

    Detailed Description

    +

    Configuration of the high-level API

    +

    This structure is initialized from the arguments passed to fuse_new(), and then passed to the file system's init() handler which should ensure that the configuration is compatible with the file system implementation.

    +

    Note: this data structure is ABI sensitive, new options have to be appended at the end of the structure

    + +

    Definition at line 101 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ attr_timeout

    + +
    +
    + + + + +
    double fuse_config::attr_timeout
    +
    +

    The timeout in seconds for which file/directory attributes (as returned by e.g. the getattr handler) are cached.

    + +

    Definition at line 143 of file fuse.h.

    + +
    +
    + +

    ◆ auto_cache

    + +
    +
    + + + + +
    int32_t fuse_config::auto_cache
    +
    +

    This option is an alternative to kernel_cache. Instead of unconditionally keeping cached data, the cached data is invalidated on open(2) if if the modification time or the size of the file has changed since it was last opened.

    + +

    Definition at line 253 of file fuse.h.

    + +
    +
    + +

    ◆ direct_io

    + +
    +
    + + + + +
    int32_t fuse_config::direct_io
    +
    +

    This option disables the use of page cache (file content cache) in the kernel for this filesystem. This has several affects:

    +
      +
    1. Each read(2) or write(2) system call will initiate one or more read or write operations, data will not be cached in the kernel.
    2. +
    3. The return value of the read() and write() system calls will correspond to the return values of the read and write operations. This is useful for example if the file size is not known in advance (before reading it).
    4. +
    +

    Internally, enabling this option causes fuse to set the direct_io field of struct fuse_file_info - overwriting any value that was put there by the file system.

    + +

    Definition at line 226 of file fuse.h.

    + +
    +
    + +

    ◆ entry_timeout

    + +
    +
    + + + + +
    double fuse_config::entry_timeout
    +
    +

    The timeout in seconds for which name lookups will be cached.

    + +

    Definition at line 127 of file fuse.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    uint32_t fuse_config::flags
    +
    +

    Reserved for future use.

    + +

    Definition at line 318 of file fuse.h.

    + +
    +
    + +

    ◆ fmask

    + +
    +
    + + + + +
    uint32_t fuse_config::fmask
    +
    +

    fmask and dmask function the same way as umask, but apply to files and directories separately. If non-zero, fmask and dmask take precedence over the umask setting.

    + +

    Definition at line 288 of file fuse.h.

    + +
    +
    + +

    ◆ hard_remove

    + +
    +
    + + + + +
    int32_t fuse_config::hard_remove
    +
    +

    The default behavior is that if an open file is deleted, the file is renamed to a hidden file (.fuse_hiddenXXX), and only removed when the file is finally released. This relieves the filesystem implementation of having to deal with this problem. This option disables the hiding behavior, and files are removed immediately in an unlink operation (or in a rename operation which overwrites an existing file).

    +

    It is recommended that you not use the hard_remove option. When hard_remove is set, the following libc functions fail on unlinked files (returning errno of ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2), ftruncate(2), fstat(2), fchmod(2), fchown(2)

    + +

    Definition at line 185 of file fuse.h.

    + +
    +
    + +

    ◆ intr

    + +
    +
    + + + + +
    int32_t fuse_config::intr
    +
    +

    Allow requests to be interrupted

    + +

    Definition at line 148 of file fuse.h.

    + +
    +
    + +

    ◆ intr_signal

    + +
    +
    + + + + +
    int32_t fuse_config::intr_signal
    +
    +

    Specify which signal number to send to the filesystem when a request is interrupted. The default is hardcoded to USR1.

    + +

    Definition at line 155 of file fuse.h.

    + +
    +
    + +

    ◆ kernel_cache

    + +
    +
    + + + + +
    int32_t fuse_config::kernel_cache
    +
    +

    This option disables flushing the cache of the file contents on every open(2). This should only be enabled on filesystems where the file data is never changed externally (not through the mounted FUSE filesystem). Thus it is not suitable for network filesystems and other intermediate filesystems.

    +

    NOTE: if this option is not specified (and neither direct_io) data is still cached after the open(2), so a read(2) system call will not always initiate a read operation.

    +

    Internally, enabling this option causes fuse to set the keep_cache field of struct fuse_file_info - overwriting any value that was put there by the file system.

    + +

    Definition at line 245 of file fuse.h.

    + +
    +
    + +

    ◆ negative_timeout

    + +
    +
    + + + + +
    double fuse_config::negative_timeout
    +
    +

    The timeout in seconds for which a negative lookup will be cached. This means, that if file did not exist (lookup returned ENOENT), the lookup will only be redone after the timeout, and the file/directory will be assumed to not exist until then. A value of zero means that negative lookups are not cached.

    + +

    Definition at line 137 of file fuse.h.

    + +
    +
    + +

    ◆ no_rofd_flush

    + +
    +
    + + + + +
    int32_t fuse_config::no_rofd_flush
    +
    +

    By default, fuse waits for all pending writes to complete and calls the FLUSH operation on close(2) of every fuse fd. With this option, wait and FLUSH are not done for read-only fuse fd, similar to the behavior of NFS/SMB clients.

    + +

    Definition at line 297 of file fuse.h.

    + +
    +
    + +

    ◆ nullpath_ok

    + +
    +
    + + + + +
    int32_t fuse_config::nullpath_ok
    +
    +

    If this option is given the file-system handlers for the following operations will not receive path information: read, write, flush, release, fallocate, fsync, readdir, releasedir, fsyncdir, lock, ioctl and poll.

    +

    For the truncate, getattr, chmod, chown and utimens operations the path will be provided only if the struct fuse_file_info argument is NULL.

    + +

    Definition at line 273 of file fuse.h.

    + +
    +
    + +

    ◆ parallel_direct_writes

    + +
    +
    + + + + +
    int32_t fuse_config::parallel_direct_writes
    +
    +

    Allow parallel direct-io writes to operate on the same file.

    +

    FUSE implementations which do not handle parallel writes on same file/region should NOT enable this option at all as it might lead to data inconsistencies.

    +

    For the FUSE implementations which have their own mechanism of cache/data integrity are beneficiaries of this setting as it now open doors to parallel writes on the same file (without enabling this setting, all direct writes on the same file are serialized, resulting in huge data bandwidth loss).

    + +

    Definition at line 312 of file fuse.h.

    + +
    +
    + +

    ◆ readdir_ino

    + +
    +
    + + + + +
    int32_t fuse_config::readdir_ino
    +
    +

    If use_ino option is not given, still try to fill in the d_ino field in readdir(2). If the name was previously looked up, and is still in the cache, the inode number found there will be used. Otherwise it will be set to -1. If use_ino option is given, this option is ignored.

    + +

    Definition at line 207 of file fuse.h.

    + +
    +
    + +

    ◆ remember

    + +
    +
    + + + + +
    int32_t fuse_config::remember
    +
    +

    Normally, FUSE assigns inodes to paths only for as long as the kernel is aware of them. With this option inodes are instead remembered for at least this many seconds. This will require more memory, but may be necessary when using applications that make use of inode numbers.

    +

    A number of -1 means that inodes will be remembered for the entire life-time of the file-system process.

    + +

    Definition at line 167 of file fuse.h.

    + +
    +
    + +

    ◆ reserved

    + +
    +
    + + + + +
    uint64_t fuse_config::reserved[48]
    +
    +

    Reserved for future use.

    + +

    Definition at line 323 of file fuse.h.

    + +
    +
    + +

    ◆ set_gid

    + +
    +
    + + + + +
    int32_t fuse_config::set_gid
    +
    +

    If set_gid is non-zero, the st_gid attribute of each file is overwritten with the value of gid.

    + +

    Definition at line 106 of file fuse.h.

    + +
    +
    + +

    ◆ set_mode

    + +
    +
    + + + + +
    int32_t fuse_config::set_mode
    +
    +

    If set_mode is non-zero, the any permissions bits set in umask are unset in the st_mode attribute of each file.

    + +

    Definition at line 120 of file fuse.h.

    + +
    +
    + +

    ◆ set_uid

    + +
    +
    + + + + +
    int32_t fuse_config::set_uid
    +
    +

    If set_uid is non-zero, the st_uid attribute of each file is overwritten with the value of uid.

    + +

    Definition at line 113 of file fuse.h.

    + +
    +
    + +

    ◆ show_help

    + +
    +
    + + + + +
    int32_t fuse_config::show_help
    +
    +

    These 3 options are used by libfuse internally and should not be touched.

    + +

    Definition at line 279 of file fuse.h.

    + +
    +
    + +

    ◆ use_ino

    + +
    +
    + + + + +
    int32_t fuse_config::use_ino
    +
    +

    Honor the st_ino field in the functions getattr() and fill_dir(). This value is used to fill in the st_ino field in the stat(2), lstat(2), fstat(2) functions and the d_ino field in the readdir(2) function. The filesystem does not have to guarantee uniqueness, however some applications rely on this value being unique for the whole filesystem.

    +

    Note that this does not affect the inode that libfuse and the kernel use internally (also called the "nodeid").

    + +

    Definition at line 198 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__conn__info.html b/doc/html/structfuse__conn__info.html new file mode 100644 index 0000000..53c643b --- /dev/null +++ b/doc/html/structfuse__conn__info.html @@ -0,0 +1,349 @@ + + + + + + + +libfuse: fuse_conn_info Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_conn_info Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    uint32_t proto_major
     
    uint32_t proto_minor
     
    uint32_t max_write
     
    uint32_t max_read
     
    uint32_t max_readahead
     
    uint32_t capable
     
    uint32_t want
     
    uint32_t max_background
     
    uint32_t congestion_threshold
     
    uint32_t time_gran
     
    uint32_t no_interrupt: 1
     
    uint64_t capable_ext
     
    uint64_t want_ext
     
    uint32_t reserved [16]
     
    +

    Detailed Description

    +

    Connection information, passed to the ->init() method

    +

    Some of the elements are read-write, these can be changed to indicate the value requested by the filesystem. The requested value must usually be smaller than the indicated value.

    +

    Note: The capable and want fields are limited to 32 bits for ABI compatibility. For full 64-bit capability support, use the capable_ext and want_ext fields instead.

    + +

    Definition at line 546 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ capable

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::capable
    +
    +

    Capability flags that the kernel supports (read-only)

    +

    Deprecated left over for ABI compatibility, use capable_ext

    + +

    Definition at line 586 of file fuse_common.h.

    + +
    +
    + +

    ◆ capable_ext

    + +
    +
    + + + + +
    uint64_t fuse_conn_info::capable_ext
    +
    +

    Extended capability flags that the kernel supports (read-only) This field provides full 64-bit capability support.

    + +

    Definition at line 694 of file fuse_common.h.

    + +
    +
    + +

    ◆ congestion_threshold

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::congestion_threshold
    +
    +

    Kernel congestion threshold parameter. If the number of pending background requests exceeds this number, the FUSE kernel module will mark the filesystem as "congested". This instructs the kernel to expect that queued requests will take some time to complete, and to adjust its algorithms accordingly (e.g. by putting a waiting thread to sleep instead of using a busy-loop).

    + +

    Definition at line 638 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_background

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_background
    +
    +

    Maximum number of pending "background" requests. A background request is any type of request for which the total number is not limited by other means. As of kernel 4.8, only two types of requests fall into this category:

    +
      +
    1. Read-ahead requests
    2. +
    3. Asynchronous direct I/O requests
    4. +
    +

    Read-ahead requests are generated (if max_readahead is non-zero) by the kernel to preemptively fill its caches when it anticipates that userspace will soon read more data.

    +

    Asynchronous direct I/O requests are generated if FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large direct I/O request. In this case the kernel will internally split it up into multiple smaller requests and submit them to the filesystem concurrently.

    +

    Note that the following requests are not background requests: writeback requests (limited by the kernel's flusher algorithm), regular (i.e., synchronous and buffered) userspace read/write requests (limited to one per thread), asynchronous read requests (Linux's io_submit(2) call actually blocks, so these are also limited to one per thread).

    + +

    Definition at line 628 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_read

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_read
    +
    +

    Maximum size of read requests. A value of zero indicates no limit. However, even if the filesystem does not specify a limit, the maximum size of read requests will still be limited by the kernel.

    +

    NOTE: For the time being, the maximum size of read requests must be set both here and passed to fuse_session_new() using the -o max_read=<n> mount option. At some point in the future, specifying the mount option will no longer be necessary.

    + +

    Definition at line 574 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_readahead

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_readahead
    +
    +

    Maximum readahead

    + +

    Definition at line 579 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_write

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_write
    +
    +

    Maximum size of the write buffer

    + +

    Definition at line 560 of file fuse_common.h.

    + +
    +
    + +

    ◆ no_interrupt

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::no_interrupt
    +
    +

    Disable FUSE_INTERRUPT requests.

    +

    Enable no_interrupt option to: 1) Avoid unnecessary locking operations and list operations, 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to inform the kernel not to send the FUSE_INTERRUPT request.

    + +

    Definition at line 685 of file fuse_common.h.

    + +
    +
    + +

    ◆ proto_major

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::proto_major
    +
    +

    Major version of the protocol (read-only)

    + +

    Definition at line 550 of file fuse_common.h.

    + +
    +
    + +

    ◆ proto_minor

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::proto_minor
    +
    +

    Minor version of the protocol (read-only)

    + +

    Definition at line 555 of file fuse_common.h.

    + +
    +
    + +

    ◆ reserved

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::reserved[16]
    +
    +

    For future use.

    + +

    Definition at line 709 of file fuse_common.h.

    + +
    +
    + +

    ◆ time_gran

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::time_gran
    +
    +

    When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible for updating mtime and ctime when write requests are received. The updated values are passed to the filesystem with setattr() requests. However, if the filesystem does not support the full resolution of the kernel timestamps (nanoseconds), the mtime and ctime values used by kernel and filesystem will differ (and result in an apparent change of times after a cache flush).

    +

    To prevent this problem, this variable can be used to inform the kernel about the timestamp granularity supported by the file-system. The value should be power of 10. The default is 1, i.e. full nano-second resolution. Filesystems supporting only second resolution should set this to 1000000000.

    + +

    Definition at line 655 of file fuse_common.h.

    + +
    +
    + +

    ◆ want

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::want
    +
    +

    Capability flags that the filesystem wants to enable.

    +

    libfuse attempts to initialize this field with reasonable default values before calling the init() handler.

    +

    Deprecated left over for ABI compatibility. Use want_ext with the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag()

    + +

    Definition at line 598 of file fuse_common.h.

    + +
    +
    + +

    ◆ want_ext

    + +
    +
    + + + + +
    uint64_t fuse_conn_info::want_ext
    +
    +

    Extended capability flags that the filesystem wants to enable. This field provides full 64-bit capability support.

    +

    Don't set this field directly, but use the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag()

    + +

    Definition at line 704 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__context.html b/doc/html/structfuse__context.html new file mode 100644 index 0000000..8b22610 --- /dev/null +++ b/doc/html/structfuse__context.html @@ -0,0 +1,182 @@ + + + + + + + +libfuse: fuse_context Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_context Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + +

    +Data Fields

    struct fuse * fuse
     
    uid_t uid
     
    gid_t gid
     
    pid_t pid
     
    void * private_data
     
    mode_t umask
     
    +

    Detailed Description

    +

    Extra context that may be needed by some filesystems

    +

    The uid, gid and pid fields are not filled in case of a writepage operation.

    + +

    Definition at line 860 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ fuse

    + +
    +
    + + + + +
    struct fuse* fuse_context::fuse
    +
    +

    Pointer to the fuse object

    + +

    Definition at line 862 of file fuse.h.

    + +
    +
    + +

    ◆ gid

    + +
    +
    + + + + +
    gid_t fuse_context::gid
    +
    +

    Group ID of the calling process

    + +

    Definition at line 868 of file fuse.h.

    + +
    +
    + +

    ◆ pid

    + +
    +
    + + + + +
    pid_t fuse_context::pid
    +
    +

    Process ID of the calling thread

    + +

    Definition at line 871 of file fuse.h.

    + +
    +
    + +

    ◆ private_data

    + +
    +
    + + + + +
    void* fuse_context::private_data
    +
    +

    Private filesystem data

    + +

    Definition at line 874 of file fuse.h.

    + +
    +
    + +

    ◆ uid

    + +
    +
    + + + + +
    uid_t fuse_context::uid
    +
    +

    User ID of the calling process

    + +

    Definition at line 865 of file fuse.h.

    + +
    +
    + +

    ◆ umask

    + +
    +
    + + + + +
    mode_t fuse_context::umask
    +
    +

    Umask of the calling process

    + +

    Definition at line 877 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__ctx.html b/doc/html/structfuse__ctx.html new file mode 100644 index 0000000..3129986 --- /dev/null +++ b/doc/html/structfuse__ctx.html @@ -0,0 +1,144 @@ + + + + + + + +libfuse: fuse_ctx Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_ctx Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + + + + +

    +Data Fields

    uid_t uid
     
    gid_t gid
     
    pid_t pid
     
    mode_t umask
     
    +

    Detailed Description

    +

    Additional context associated with requests.

    +

    Note that the reported client uid, gid and pid may be zero in some situations. For example, if the FUSE file system is running in a PID or user namespace but then accessed from outside the namespace, there is no valid uid/pid/gid that could be reported.

    + +

    Definition at line 112 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ gid

    + +
    +
    + + + + +
    gid_t fuse_ctx::gid
    +
    +

    Group ID of the calling process

    + +

    Definition at line 117 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ pid

    + +
    +
    + + + + +
    pid_t fuse_ctx::pid
    +
    +

    Thread ID of the calling process

    + +

    Definition at line 120 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ uid

    + +
    +
    + + + + +
    uid_t fuse_ctx::uid
    +
    +

    User ID of the calling process

    + +

    Definition at line 114 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ umask

    + +
    +
    + + + + +
    mode_t fuse_ctx::umask
    +
    +

    Umask of the calling process

    + +

    Definition at line 123 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__entry__param.html b/doc/html/structfuse__entry__param.html new file mode 100644 index 0000000..d7456c1 --- /dev/null +++ b/doc/html/structfuse__entry__param.html @@ -0,0 +1,165 @@ + + + + + + + +libfuse: fuse_entry_param Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_entry_param Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + + + + + + +

    +Data Fields

    fuse_ino_t ino
     
    uint64_t generation
     
    struct stat attr
     
    double attr_timeout
     
    double entry_timeout
     
    +

    Detailed Description

    +

    Directory entry parameters supplied to fuse_reply_entry()

    + +

    Definition at line 60 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ attr

    + +
    +
    + + + + +
    struct stat fuse_entry_param::attr
    +
    +

    Inode attributes.

    +

    Even if attr_timeout == 0, attr must be correct. For example, for open(), FUSE uses attr.st_size from lookup() to determine how many bytes to request. If this value is not correct, incorrect data will be returned.

    + +

    Definition at line 89 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ attr_timeout

    + +
    +
    + + + + +
    double fuse_entry_param::attr_timeout
    +
    +

    Validity timeout (in seconds) for inode attributes. If attributes only change as a result of requests that come through the kernel, this should be set to a very large value.

    + +

    Definition at line 95 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ entry_timeout

    + +
    +
    + + + + +
    double fuse_entry_param::entry_timeout
    +
    +

    Validity timeout (in seconds) for the name. If directory entries are changed/deleted only as a result of requests that come through the kernel, this should be set to a very large value.

    + +

    Definition at line 101 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ generation

    + +
    +
    + + + + +
    uint64_t fuse_entry_param::generation
    +
    +

    Generation number for this entry.

    +

    If the file system will be exported over NFS, the ino/generation pairs need to be unique over the file system's lifetime (rather than just the mount time). So if the file system reuses an inode after it has been deleted, it must assign a new, previously unused generation number to the inode at the same time.

    + +

    Definition at line 80 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ ino

    + +
    +
    + + + + +
    fuse_ino_t fuse_entry_param::ino
    +
    +

    Unique inode number

    +

    In lookup, zero means negative entry (from version 2.5) Returning ENOENT also means negative entry, but by setting zero ino the kernel may cache negative entries for entry_timeout seconds.

    + +

    Definition at line 68 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__ext__header.html b/doc/html/structfuse__ext__header.html new file mode 100644 index 0000000..51aaf4f --- /dev/null +++ b/doc/html/structfuse__ext__header.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse_ext_header Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_ext_header Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    struct fuse_ext_header - extension header @size: total size of this extension including this header @type: type of extension

    +

    This is made compatible with fuse_secctx_header by using type values > FUSE_MAX_NR_SECCTX

    + +

    Definition at line 1174 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__file__info.html b/doc/html/structfuse__file__info.html new file mode 100644 index 0000000..8c7f93d --- /dev/null +++ b/doc/html/structfuse__file__info.html @@ -0,0 +1,355 @@ + + + + + + + +libfuse: fuse_file_info Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_file_info Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int32_t flags
     
    uint32_t writepage: 1
     
    uint32_t direct_io: 1
     
    uint32_t keep_cache: 1
     
    uint32_t flush: 1
     
    uint32_t nonseekable: 1
     
    uint32_t cache_readdir: 1
     
    uint32_t noflush: 1
     
    uint32_t parallel_direct_writes: 1
     
    uint32_t padding: 23
     
    uint64_t fh
     
    uint64_t lock_owner
     
    uint32_t poll_events
     
    int32_t backing_id
     
    uint64_t compat_flags
     
    +

    Detailed Description

    +

    Information about an open file.

    +

    File Handles are created by the open, opendir, and create methods and closed by the release and releasedir methods. Multiple file handles may be concurrently open for the same file. Generally, a client will create one file handle per file descriptor, though in some cases multiple file descriptors can share a single file handle.

    +

    Note: This data structure is ABI sensitive, new parameters have to be added within padding/padding2 bits and always below existing parameters.

    + +

    Definition at line 56 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ backing_id

    + +
    +
    + + + + +
    int32_t fuse_file_info::backing_id
    +
    +

    Passthrough backing file id. May be filled in by filesystem in create and open. It is used to create a passthrough connection between FUSE file and backing file.

    + +

    Definition at line 125 of file fuse_common.h.

    + +
    +
    + +

    ◆ cache_readdir

    + +
    +
    + + + + +
    uint32_t fuse_file_info::cache_readdir
    +
    +

    Can be filled in by opendir. It signals the kernel to enable caching of entries returned by readdir(). Has no effect when set in other contexts (in particular it does nothing when set by open()).

    + +

    Definition at line 95 of file fuse_common.h.

    + +
    +
    + +

    ◆ compat_flags

    + +
    +
    + + + + +
    uint64_t fuse_file_info::compat_flags
    +
    +

    struct fuse_file_info api and abi flags
    +

    + +

    Definition at line 128 of file fuse_common.h.

    + +
    +
    + +

    ◆ direct_io

    + +
    +
    + + + + +
    uint32_t fuse_file_info::direct_io
    +
    +

    Can be filled in by open/create, to use direct I/O on this file.

    + +

    Definition at line 69 of file fuse_common.h.

    + +
    +
    + +

    ◆ fh

    + +
    +
    + + + + +
    uint64_t fuse_file_info::fh
    +
    +

    File handle id. May be filled in by filesystem in create, open, and opendir(). Available in most other file operations on the same file handle.

    + +

    Definition at line 113 of file fuse_common.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    int32_t fuse_file_info::flags
    +
    +

    Open flags. Available in open(), release() and create()

    + +

    Definition at line 58 of file fuse_common.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    uint32_t fuse_file_info::flush
    +
    +

    Indicates a flush operation. Set in flush operation, also maybe set in highlevel lock operation and lowlevel release operation.

    + +

    Definition at line 80 of file fuse_common.h.

    + +
    +
    + +

    ◆ keep_cache

    + +
    +
    + + + + +
    uint32_t fuse_file_info::keep_cache
    +
    +

    Can be filled in by open and opendir. It signals the kernel that any currently cached data (ie., data that the filesystem provided the last time the file/directory was open) need not be invalidated when the file/directory is closed.

    + +

    Definition at line 75 of file fuse_common.h.

    + +
    +
    + +

    ◆ lock_owner

    + +
    +
    + + + + +
    uint64_t fuse_file_info::lock_owner
    +
    +

    Lock owner id. Available in locking operations and flush

    + +

    Definition at line 116 of file fuse_common.h.

    + +
    +
    + +

    ◆ noflush

    + +
    +
    + + + + +
    uint32_t fuse_file_info::noflush
    +
    +

    Can be filled in by open, to indicate that flush is not needed on close.

    + +

    Definition at line 99 of file fuse_common.h.

    + +
    +
    + +

    ◆ nonseekable

    + +
    +
    + + + + +
    uint32_t fuse_file_info::nonseekable
    +
    +

    Can be filled in by open, to indicate that the file is not seekable.

    + +

    Definition at line 84 of file fuse_common.h.

    + +
    +
    + +

    ◆ padding

    + +
    +
    + + + + +
    uint32_t fuse_file_info::padding
    +
    +

    Padding. Reserved for future use

    + +

    Definition at line 106 of file fuse_common.h.

    + +
    +
    + +

    ◆ parallel_direct_writes

    + +
    +
    + + + + +
    uint32_t fuse_file_info::parallel_direct_writes
    +
    +

    Can be filled by open/create, to allow parallel direct writes on this file

    + +

    Definition at line 103 of file fuse_common.h.

    + +
    +
    + +

    ◆ poll_events

    + +
    +
    + + + + +
    uint32_t fuse_file_info::poll_events
    +
    +

    Requested poll events. Available in ->poll. Only set on kernels which support it. If unsupported, this field is set to zero.

    + +

    Definition at line 120 of file fuse_common.h.

    + +
    +
    + +

    ◆ writepage

    + +
    +
    + + + + +
    uint32_t fuse_file_info::writepage
    +
    +

    In case of a write operation indicates if this was caused by a delayed write from the page cache. If so, then the context's pid, uid, and gid fields will not be valid, and the fh value may not match the fh value that would have been sent with the corresponding individual write requests if write caching had been disabled.

    + +

    Definition at line 66 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__loop__config.html b/doc/html/structfuse__loop__config.html new file mode 100644 index 0000000..98908b5 --- /dev/null +++ b/doc/html/structfuse__loop__config.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse_loop_config Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_loop_config Struct Reference
    +
    +
    + +

    #include <fuse_i.h>

    + + + + + + + + + + +

    +Data Fields

    int clone_fd
     
    unsigned int max_idle_threads
     
    int max_idle_threads
     
    unsigned int max_threads
     
    +

    Detailed Description

    +

    Configuration parameters passed to fuse_session_loop_mt() and fuse_loop_mt().

    +

    Internal API to avoid exposing the plain data structure and causing compat issues after adding or removing struct members.

    + +

    Definition at line 147 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ clone_fd

    + +
    +
    + + + + +
    int fuse_loop_config::clone_fd
    +
    +

    whether to use separate device fds for each thread (may increase performance)

    + +

    Definition at line 155 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_idle_threads [1/2]

    + +
    +
    + + + + +
    unsigned int fuse_loop_config::max_idle_threads
    +
    +

    The maximum number of available worker threads before they start to get deleted when they become idle. If not specified, the default is 10.

    +

    Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created to service every operation.

    + +

    Definition at line 167 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_idle_threads [2/2]

    + +
    +
    + + + + +
    int fuse_loop_config::max_idle_threads
    +
    +

    The maximum number of available worker threads before they start to get deleted when they become idle. If not specified, the default is 10.

    +

    Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created to service every operation. The special value of -1 means that this parameter is disabled.

    + +

    Definition at line 151 of file fuse_i.h.

    + +
    +
    + +

    ◆ max_threads

    + +
    +
    + + + + +
    unsigned int fuse_loop_config::max_threads
    +
    +

    max number of threads taking and processing kernel requests

    +

    As of now threads are created dynamically

    + +

    Definition at line 158 of file fuse_i.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + + + diff --git a/doc/html/structfuse__lowlevel__ops.html b/doc/html/structfuse__lowlevel__ops.html new file mode 100644 index 0000000..0f7c729 --- /dev/null +++ b/doc/html/structfuse__lowlevel__ops.html @@ -0,0 +1,1476 @@ + + + + + + + +libfuse: fuse_lowlevel_ops Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_lowlevel_ops Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    void(* init )(void *userdata, struct fuse_conn_info *conn)
     
    void(* destroy )(void *userdata)
     
    void(* lookup )(fuse_req_t req, fuse_ino_t parent, const char *name)
     
    void(* forget )(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
     
    void(* getattr )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* setattr )(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
     
    void(* readlink )(fuse_req_t req, fuse_ino_t ino)
     
    void(* mknod )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
     
    void(* mkdir )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
     
    void(* unlink )(fuse_req_t req, fuse_ino_t parent, const char *name)
     
    void(* rmdir )(fuse_req_t req, fuse_ino_t parent, const char *name)
     
    void(* symlink )(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
     
    void(* rename )(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
     
    void(* link )(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
     
    void(* open )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* read )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* write )(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* flush )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* release )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* fsync )(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
     
    void(* opendir )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* readdir )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* releasedir )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
     
    void(* fsyncdir )(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
     
    void(* statfs )(fuse_req_t req, fuse_ino_t ino)
     
    void(* setxattr )(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
     
    void(* getxattr )(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
     
    void(* listxattr )(fuse_req_t req, fuse_ino_t ino, size_t size)
     
    void(* removexattr )(fuse_req_t req, fuse_ino_t ino, const char *name)
     
    void(* access )(fuse_req_t req, fuse_ino_t ino, int mask)
     
    void(* create )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
     
    void(* getlk )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
     
    void(* setlk )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
     
    void(* bmap )(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
     
    void(* ioctl )(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
     
    void(* poll )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
     
    void(* write_buf )(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
     
    void(* retrieve_reply )(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
     
    void(* forget_multi )(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
     
    void(* flock )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
     
    void(* fallocate )(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
     
    void(* readdirplus )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
     
    void(* copy_file_range )(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
     
    void(* lseek )(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
     
    void(* tmpfile )(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
     
    +

    Detailed Description

    +

    Low level filesystem operations

    +

    Most of the methods (with the exception of init and destroy) receive a request handle (fuse_req_t) as their first argument. This handle must be passed to one of the specified reply functions.

    +

    This may be done inside the method invocation, or after the call has returned. The request handle is valid until one of the reply functions is called.

    +

    Other pointer arguments (name, fuse_file_info, etc) are not valid after the call has returned, so if they are needed later, their contents have to be copied.

    +

    In general, all methods are expected to perform any necessary permission checking. However, a filesystem may delegate this task to the kernel by passing the default_permissions mount option to fuse_session_new(). In this case, methods will only be called if the kernel's permission check has succeeded.

    +

    The filesystem sometimes needs to handle a return value of -ENOENT from the reply function, which means, that the request was interrupted, and the reply discarded. For example if fuse_reply_open() return -ENOENT means, that the release method for this file will not be called.

    +

    This data structure is ABI sensitive, on adding new functions these need to be appended at the end of the struct

    + +

    Definition at line 206 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ access

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::access) (fuse_req_t req, fuse_ino_t ino, int mask)
    +
    +

    Check file access permissions

    +

    This will be called for the access() and chdir() system calls. If the 'default_permissions' mount option is given, this method is not called.

    +

    This method is not called under Linux kernel versions 2.4.x

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent success, i.e. this and all future access() requests will succeed without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    maskrequested access mode
    +
    +
    + +

    Definition at line 951 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ bmap

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
    +
    +

    Map block index within file to block index within device

    +

    Note: This makes sense only for block device backed filesystems mounted with the 'blkdev' option

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure, i.e. all future bmap() requests will fail with the same error code without being send to the filesystem process.

    +

    Valid replies: fuse_reply_bmap fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    blocksizeunit of block index
    idxblock index within file
    +
    +
    + +

    Definition at line 1044 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ copy_file_range

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::copy_file_range) (fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
    +
    +

    Copy a range of data from one file to another

    +

    Performs an optimized copy between two file descriptors without the additional cost of transferring data through the FUSE kernel module to user space (glibc) and then back into the FUSE filesystem again.

    +

    In case this method is not implemented, glibc falls back to reading data from the source and writing to the destination. Effectively doing an inefficient copy of the data.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future copy_file_range() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + + + + + +
    reqrequest handle
    ino_inthe inode number or the source file
    off_instarting point from were the data should be read
    fi_infile information of the source file
    ino_outthe inode number or the destination file
    off_outstarting point where the data should be written
    fi_outfile information of the destination file
    lenmaximum size of the data to copy
    flagspassed along with the copy_file_range() syscall
    +
    +
    + +

    Definition at line 1279 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ create

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::create) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
    +
    +

    Create and open a file

    +

    If the file does not exist, first create it with the specified mode, and then open it.

    +

    See the description of the open handler for more information.

    +

    If this method is not implemented or under Linux kernel versions earlier than 2.6.15, the mknod() and open() methods will be called instead.

    +

    If this request is answered with an error code of ENOSYS, the handler is treated as not implemented (i.e., for this and future requests the mknod() and open() handlers will be called instead).

    +

    Valid replies: fuse_reply_create fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modefile type and mode with which to create the new file
    fifile information
    +
    +
    + +

    Definition at line 980 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ destroy

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::destroy) (void *userdata)
    +
    +

    Clean up filesystem.

    +

    Called on filesystem exit. When this method is called, the connection to the kernel may be gone already, so that eg. calls to fuse_lowlevel_notify_* will fail.

    +

    There's no reply to this function

    +
    Parameters
    + + +
    userdatathe user data passed to fuse_session_new()
    +
    +
    + +

    Definition at line 236 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fallocate

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fallocate) (fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
    +
    +

    Allocate requested space. If this function returns success then subsequent writes to the specified range shall not fail due to the lack of free space on the file system storage media.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future fallocate() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    offsetstarting point for allocated region
    lengthsize of allocated region
    modedetermines the operation to be performed on the given range, see fallocate(2)
    +
    +
    + +

    Definition at line 1218 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ flock

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::flock) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
    +
    +

    Acquire, modify or release a BSD file lock

    +

    Note: if the locking methods are not implemented, the kernel will still allow file locking to work locally. Hence these are only interesting for network filesystems and similar.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    opthe locking operation, see flock(2)
    +
    +
    + +

    Definition at line 1195 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::flush) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Flush method

    +

    This is called on each close() of the opened file.

    +

    Since file descriptors can be duplicated (dup, dup2, fork), for one open call there may be many flush calls.

    +

    Filesystems shouldn't assume that flush will always be called after some writes, or that if will be called at all.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    NOTE: the name of the method is misleading, since (unlike fsync) the filesystem is not forced to flush pending writes. One reason to flush data is if the filesystem wants to return write errors during close. However, such use is non-portable because POSIX does not require close to wait for delayed I/O to complete.

    +

    If the filesystem supports file locking operations (setlk, getlk) it should remove all locks belonging to 'fi->owner'.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to flush() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 652 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ forget

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    +

    Forget about an inode

    +

    This function is called when the kernel removes an inode from its internal caches.

    +

    The inode's lookup count increases by one for every call to fuse_reply_entry and fuse_reply_create. The nlookup parameter indicates by how much the lookup count should be decreased.

    +

    Inodes with a non-zero lookup count may receive request from the kernel even after calls to unlink, rmdir or (when overwriting an existing file) rename. Filesystems must handle such requests properly and it is recommended to defer removal of the inode until the lookup count reaches zero. Calls to unlink, rmdir or rename will be followed closely by forget unless the file or directory is open, in which case the kernel issues forget only after the release or releasedir calls.

    +

    Note that if a file system will be exported over NFS the inodes lifetime must extend even beyond forget. See the generation field in struct fuse_entry_param above.

    +

    On unmount the lookup count for all inodes implicitly drops to zero. It is not guaranteed that the file system will receive corresponding forget messages for the affected inodes.

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    nlookupthe number of lookups to forget
    +
    +
    + +

    Definition at line 287 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ forget_multi

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::forget_multi) (fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
    +
    +

    Forget about multiple inodes

    +

    See description of the forget function for more information.

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + +
    reqrequest handle
    +
    +
    + +

    Definition at line 1177 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fsync

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    +
    +

    Synchronize file contents

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to fsync() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    datasyncflag indicating if only data should be flushed
    fifile information
    +
    +
    + +

    Definition at line 702 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fsyncdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    +
    +

    Synchronize directory contents

    +

    If the datasync parameter is non-zero, then only the directory contents should be flushed, not the meta data.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to fsyncdir() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    datasyncflag indicating if only data should be flushed
    fifile information
    +
    +
    + +

    Definition at line 824 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getattr) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Get file attributes.

    +

    If writeback caching is enabled, the kernel may have a better idea of a file's length than the FUSE file system (eg if there has been a write that extended the file size, but that has not yet been passed to the filesystem.

    +

    In this case, the st_size value provided by the file system will be ignored.

    +

    Valid replies: fuse_reply_attr fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information, or NULL
    +
    +
    + +

    Definition at line 308 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getlk

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getlk) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
    +
    +

    Test for a POSIX file lock

    +

    Valid replies: fuse_reply_lock fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    lockthe region/type to test
    +
    +
    + +

    Definition at line 995 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
    +
    +

    Get an extended attribute

    +

    If size is zero, the size of the value should be sent with fuse_reply_xattr.

    +

    If the size is non-zero, and the value fits in the buffer, the value should be sent with fuse_reply_buf.

    +

    If the size is too small for the value, the ERANGE error should be sent.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future getxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_xattr fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    nameof the extended attribute
    sizemaximum size of the value to send
    +
    +
    + +

    Definition at line 881 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ init

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::init) (void *userdata, struct fuse_conn_info *conn)
    +
    +

    Initialize filesystem

    +

    This function is called when libfuse establishes communication with the FUSE kernel module. The file system should use this module to inspect and/or modify the connection parameters provided in the conn structure.

    +

    Note that some parameters may be overwritten by options passed to fuse_session_new() which take precedence over the values set in this handler.

    +

    There's no reply to this function

    +
    Parameters
    + + +
    userdatathe user data passed to fuse_session_new()
    +
    +
    + +

    Definition at line 223 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ ioctl

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
    +
    +

    Ioctl

    +

    Note: For unrestricted ioctls (not allowed for FUSE servers), data in and out areas can be discovered by giving iovs and setting FUSE_IOCTL_RETRY in flags. For restricted ioctls, kernel prepares in/out data area according to the information encoded in cmd.

    +

    Valid replies: fuse_reply_ioctl_retry fuse_reply_ioctl fuse_reply_ioctl_iov fuse_reply_err

    +
    Parameters
    + + + + + + + + + + +
    reqrequest handle
    inothe inode number
    cmdioctl command
    argioctl argument
    fifile information
    flagsfor FUSE_IOCTL_* flags
    in_bufdata fetched from the caller
    in_bufsznumber of fetched bytes
    out_bufszmaximum size of output data
    +
    +
    +

    Note : the unsigned long request submitted by the application is truncated to 32 bits.

    + +

    Definition at line 1080 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ link

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
    +
    +

    Create a hard link

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe old inode number
    newparentinode number of the new parent directory
    newnamenew name to create
    +
    +
    + +

    Definition at line 488 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ listxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size)
    +
    +

    List extended attribute names

    +

    If size is zero, the total size of the attribute list should be sent with fuse_reply_xattr.

    +

    If the size is non-zero, and the null character separated attribute list fits in the buffer, the list should be sent with fuse_reply_buf.

    +

    If the size is too small for the list, the ERANGE error should be sent.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future listxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_xattr fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum size of the list to send
    +
    +
    + +

    Definition at line 912 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ lookup

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::lookup) (fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Look up a directory entry by name and get its attributes.

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    namethe name to look up
    +
    +
    + +

    Definition at line 249 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ lseek

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
    +
    +

    Find next data or hole after the specified offset

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure, i.e. all future lseek() requests will fail with the same error code without being send to the filesystem process.

    +

    Valid replies: fuse_reply_lseek fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    offoffset to start search from
    whenceeither SEEK_DATA or SEEK_HOLE
    fifile information
    +
    +
    + +

    Definition at line 1303 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ mkdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
    +
    +

    Create a directory

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modewith which to create the new file
    +
    +
    + +

    Definition at line 391 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ mknod

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
    +
    +

    Create file node

    +

    Create a regular file, character device, block device, fifo or socket node.

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modefile type and mode with which to create the new file
    rdevthe device number (only valid if created file is a device)
    +
    +
    + +

    Definition at line 376 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ open

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::open) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Open a file

    +

    Open flags are available in fi->flags. The following rules apply.

    +
      +
    • Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel.
    • +
    • Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used by the filesystem to check if the operation is permitted. If the -o default_permissions mount option is given, this check is already done by the kernel before calling open() and may thus be omitted by the filesystem.
    • +
    • When writeback caching is enabled, the kernel may send read requests even for files opened with O_WRONLY. The filesystem should be prepared to handle this.
    • +
    • When writeback caching is disabled, the filesystem is expected to properly handle the O_APPEND flag and ensure that each write is appending to the end of the file.
    • +
    • When writeback caching is enabled, the kernel will handle O_APPEND. However, unless all changes to the file come through the kernel this will not work reliably. The filesystem should thus either ignore the O_APPEND flag (and let the kernel handle it), or return an error (indicating that reliably O_APPEND is not available).
    • +
    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other file operations (read, write, flush, release, fsync).

    +

    Filesystem may also implement stateless file I/O and not store anything in fi->fh.

    +

    There are also some flags (direct_io, keep_cache) which the filesystem may set in fi, to change the way the file is opened. See fuse_file_info structure in <fuse_common.h> for more details.

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPEN_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to open and release will also succeed without being sent to the filesystem process.

    +

    To get this behavior without providing an opendir handler, you may set FUSE_CAP_NO_OPEN_SUPPORT in fuse_conn_info.want on supported kernels to automatically get the zero message open().

    +

    If this callback is not provided and FUSE_CAP_NO_OPEN_SUPPORT is not set in fuse_conn_info.want then an empty reply will be sent.

    +

    Valid replies: fuse_reply_open fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 554 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ opendir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::opendir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Open a directory

    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other directory stream operations (readdir, releasedir, fsyncdir).

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPENDIR_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to opendir and releasedir will also succeed without being sent to the filesystem process. In addition, the kernel will cache readdir results as if opendir returned FOPEN_KEEP_CACHE | FOPEN_CACHE_DIR.

    +

    To get this behavior without providing an opendir handler, you may set FUSE_CAP_NO_OPENDIR_SUPPORT in fuse_conn_info.want on supported kernels to automatically get the zero message opendir().

    +

    If this callback is not provided and FUSE_CAP_NO_OPENDIR_SUPPORT is not set in fuse_conn_info.want then an empty reply will be sent.

    +

    Valid replies: fuse_reply_open fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 734 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ poll

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
    +
    +

    Poll for IO readiness

    +

    The client should immediately respond with fuse_reply_poll(), setting revents appropriately according to which events are ready.

    +

    Additionally, if ph is non-NULL, the client must retain it and notify when all future IO readiness events occur by calling fuse_lowlevel_notify_poll() with the specified ph.

    +

    Regardless of the number of times poll with a non-NULL ph is received, a single notify_poll is enough to service all. (Notifying more times incurs overhead but doesn't harm correctness.) Any additional received handles can be immediately destroyed.

    +

    The callee is responsible for destroying ph with fuse_pollhandle_destroy() when no longer in use.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success (with a kernel-defined default poll-mask) and future calls to poll() will succeed the same way without being send to the filesystem process.

    +

    Valid replies: fuse_reply_poll fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    phpoll handle to be used for notification
    +
    +
    + +

    Definition at line 1117 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ read

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read data

    +

    Read should send exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the file has been opened in 'direct_io' mode, in which case the return value of the read system call will reflect the return value of this operation.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    Valid replies: fuse_reply_buf fuse_reply_iov fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizenumber of bytes to read
    offoffset to read from
    fifile information
    +
    +
    + +

    Definition at line 582 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read directory

    +

    Send a buffer filled using fuse_add_direntry(), with size not exceeding the requested size. Send an empty buffer on end of stream.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    Returning a directory entry from readdir() does not affect its lookup count.

    +

    If off_t is non-zero, then it will correspond to one of the off_t values that was previously returned by readdir() for the same directory handle. In this case, readdir() should skip over entries coming before the position defined by the off_t value. If entries are added or removed while the directory handle is open, the filesystem may still include the entries that have been removed, and may not report the entries that have been created. However, addition or removal of entries must never cause readdir() to skip over unrelated entries or to report them more than once. This means that off_t can not be a simple index that enumerates the entries that have been returned but must contain sufficient information to uniquely determine the next directory entry to return even when the set of entries is changing.

    +

    The function does not have to report the '.' and '..' entries, but is allowed to do so. Note that, if readdir does not return '.' or '..', they will not be implicitly returned, and this behavior is observable by the caller.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum number of bytes to send
    offoffset to continue reading the directory stream
    fifile information
    +
    +
    + +

    Definition at line 780 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readdirplus

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read directory with attributes

    +

    Send a buffer filled using fuse_add_direntry_plus(), with size not exceeding the requested size. Send an empty buffer on end of stream.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    In contrast to readdir() (which does not affect the lookup counts), the lookup count of every entry returned by readdirplus(), except "." and "..", is incremented by one.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum number of bytes to send
    offoffset to continue reading the directory stream
    fifile information
    +
    +
    + +

    Definition at line 1246 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readlink) (fuse_req_t req, fuse_ino_t ino)
    +
    +

    Read symbolic link

    +

    Valid replies: fuse_reply_readlink fuse_reply_err

    +
    Parameters
    + + + +
    reqrequest handle
    inothe inode number
    +
    +
    + +

    Definition at line 358 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ release

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::release) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Release an open file

    +

    Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.

    +

    For every open call there will be exactly one release call (unless the filesystem is force-unmounted).

    +

    The filesystem may reply with an error, but error values are not returned to close() or munmap() which triggered the release.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value. fi->flags will contain the same flags as for open.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 680 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ releasedir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::releasedir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Release an open directory

    +

    For every opendir call there will be exactly one releasedir call (unless the filesystem is force-unmounted).

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 799 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ removexattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name)
    +
    +

    Remove an extended attribute

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future removexattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    nameof the extended attribute
    +
    +
    + +

    Definition at line 929 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ rename

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::rename) (fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
    +
    +

    Rename a file

    +

    If the target exists it should be atomically replaced. If the target's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EINVAL, i.e. all future bmap requests will fail with EINVAL without being send to the filesystem process.

    +

    flags may be RENAME_EXCHANGE or RENAME_NOREPLACE. If RENAME_NOREPLACE is specified, the filesystem must not overwrite newname if it exists and return an error instead. If RENAME_EXCHANGE is specified, the filesystem must atomically exchange the two files, i.e. both must exist and neither may be deleted.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the old parent directory
    nameold name
    newparentinode number of the new parent directory
    newnamenew name
    +
    +
    + +

    Definition at line 472 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ retrieve_reply

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
    +
    +

    Callback function for the retrieve request

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + + + + + +
    reqrequest handle
    cookieuser data supplied to fuse_lowlevel_notify_retrieve()
    inothe inode number supplied to fuse_lowlevel_notify_retrieve()
    offsetthe offset supplied to fuse_lowlevel_notify_retrieve()
    bufvthe buffer containing the returned data
    +
    +
    + +

    Definition at line 1163 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ rmdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Remove a directory

    +

    If the directory's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto remove
    +
    +
    + +

    Definition at line 426 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
    +
    +

    Set file attributes

    +

    In the 'attr' argument only members indicated by the 'to_set' bitmask contain valid values. Other members contain undefined values.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits if the file size or owner is being changed.

    +

    This method will not be called to update st_atime or st_ctime implicitly (eg. after a read() request), and only be called to implicitly update st_mtime if writeback caching is active. It is the filesystem's responsibility to update these timestamps when needed, and (if desired) to implement mount options like noatime or relatime.

    +

    If the setattr was invoked from the ftruncate() system call under Linux kernel versions 2.6.15 or later, the fi->fh will contain the value set by the open method or will be undefined if the open method didn't set any value. Otherwise (not ftruncate call, or kernel version earlier than 2.6.15) the fi parameter will be NULL.

    +

    Valid replies: fuse_reply_attr fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    attrthe attributes
    to_setbit mask of attributes which should be set
    fifile information, or NULL
    +
    +
    + +

    Definition at line 345 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setlk

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setlk) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
    +
    +

    Acquire, modify or release a POSIX file lock

    +

    For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but otherwise this is not always the case. For checking lock ownership, 'fi->owner' must be used. The l_pid field in 'struct flock' should only be used to fill in this field in getlk().

    +

    Note: if the locking methods are not implemented, the kernel will still allow file locking to work locally. Hence these are only interesting for network filesystems and similar.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    lockthe region/type to set
    sleeplocking operation may sleep
    +
    +
    + +

    Definition at line 1020 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
    +
    +

    Set an extended attribute

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future setxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    + +

    Definition at line 850 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ statfs

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::statfs) (fuse_req_t req, fuse_ino_t ino)
    +
    +

    Get file system statistics

    +

    Valid replies: fuse_reply_statfs fuse_reply_err

    +
    Parameters
    + + + +
    reqrequest handle
    inothe inode number, zero means "undefined"
    +
    +
    + +

    Definition at line 837 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ symlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
    +
    +

    Create a symbolic link

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    linkthe contents of the symbolic link
    parentinode number of the parent directory
    nameto create
    +
    +
    + +

    Definition at line 440 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ tmpfile

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::tmpfile) (fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
    +
    +

    Create a tempfile

    +

    Tempfile means an anonymous file. It can be made into a normal file later by using linkat or such.

    +

    If this is answered with an error ENOSYS this is treated by the kernel as a permanent failure and it will disable the feature and not ask again.

    +

    Valid replies: fuse_reply_create fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    modefile type and mode with which to create the new file
    fifile information
    +
    +
    + +

    Definition at line 1325 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ unlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::unlink) (fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Remove a file

    +

    If the file's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto remove
    +
    +
    + +

    Definition at line 409 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ write

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::write) (fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Write data

    +

    Write should return exactly the number of bytes requested except on error. An exception to this is when the file has been opened in 'direct_io' mode, in which case the return value of the write system call will reflect the return value of this operation.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + + +
    reqrequest handle
    inothe inode number
    bufdata to write
    sizenumber of bytes to write
    offoffset to write to
    fifile information
    +
    +
    + +

    Definition at line 611 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ write_buf

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::write_buf) (fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
    +
    +

    Write data made available in a buffer

    +

    This is a more generic version of the ->write() method. If FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the kernel supports splicing from the fuse device, then the data will be made available in pipe for supporting zero copy data transfer.

    +

    buf->count is guaranteed to be one (and thus buf->idx is always zero). The write_buf handler must ensure that bufv->off is correctly updated (reflecting the number of bytes read from bufv->buf[0]).

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    bufvbuffer containing the data
    offoffset to write to
    fifile information
    +
    +
    + +

    Definition at line 1147 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__module.html b/doc/html/structfuse__module.html new file mode 100644 index 0000000..24f14ec --- /dev/null +++ b/doc/html/structfuse__module.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: fuse_module Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_module Struct Reference
    +
    +
    + +

    #include <fuse_i.h>

    +

    Detailed Description

    +

    Filesystem module

    +

    Filesystem modules are registered with the FUSE_REGISTER_MODULE() macro.

    + +

    Definition at line 103 of file fuse_i.h.

    +

    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__operations.html b/doc/html/structfuse__operations.html new file mode 100644 index 0000000..b675ff3 --- /dev/null +++ b/doc/html/structfuse__operations.html @@ -0,0 +1,946 @@ + + + + + + + +libfuse: fuse_operations Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_operations Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int(* getattr )(const char *, struct stat *, struct fuse_file_info *fi)
     
    int(* readlink )(const char *, char *, size_t)
     
    int(* mknod )(const char *, mode_t, dev_t)
     
    int(* mkdir )(const char *, mode_t)
     
    int(* unlink )(const char *)
     
    int(* rmdir )(const char *)
     
    int(* symlink )(const char *, const char *)
     
    int(* rename )(const char *, const char *, unsigned int flags)
     
    int(* link )(const char *, const char *)
     
    int(* chmod )(const char *, mode_t, struct fuse_file_info *fi)
     
    int(* chown )(const char *, uid_t, gid_t, struct fuse_file_info *fi)
     
    int(* truncate )(const char *, off_t, struct fuse_file_info *fi)
     
    int(* open )(const char *, struct fuse_file_info *)
     
    int(* read )(const char *, char *, size_t, off_t, struct fuse_file_info *)
     
    int(* write )(const char *, const char *, size_t, off_t, struct fuse_file_info *)
     
    int(* statfs )(const char *, struct statvfs *)
     
    int(* flush )(const char *, struct fuse_file_info *)
     
    int(* release )(const char *, struct fuse_file_info *)
     
    int(* fsync )(const char *, int, struct fuse_file_info *)
     
    int(* setxattr )(const char *, const char *, const char *, size_t, int)
     
    int(* getxattr )(const char *, const char *, char *, size_t)
     
    int(* listxattr )(const char *, char *, size_t)
     
    int(* removexattr )(const char *, const char *)
     
    int(* opendir )(const char *, struct fuse_file_info *)
     
    int(* readdir )(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
     
    int(* releasedir )(const char *, struct fuse_file_info *)
     
    int(* fsyncdir )(const char *, int, struct fuse_file_info *)
     
    void *(* init )(struct fuse_conn_info *conn, struct fuse_config *cfg)
     
    void(* destroy )(void *private_data)
     
    int(* access )(const char *, int)
     
    int(* create )(const char *, mode_t, struct fuse_file_info *)
     
    int(* lock )(const char *, struct fuse_file_info *, int cmd, struct flock *)
     
    int(* utimens )(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
     
    int(* bmap )(const char *, size_t blocksize, uint64_t *idx)
     
    int(* ioctl )(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
     
    int(* poll )(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
     
    int(* write_buf )(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
     
    int(* read_buf )(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
     
    int(* flock )(const char *, struct fuse_file_info *, int op)
     
    int(* fallocate )(const char *, int, off_t, off_t, struct fuse_file_info *)
     
    ssize_t(* copy_file_range )(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
     
    off_t(* lseek )(const char *, off_t off, int whence, struct fuse_file_info *)
     
    +

    Detailed Description

    +

    The file system operations:

    +

    Most of these should work very similarly to the well known UNIX file system operations. A major exception is that instead of returning an error in 'errno', the operation should return the negated error value (-errno) directly.

    +

    All methods are optional, but some are essential for a useful filesystem (e.g. getattr). Open, flush, release, fsync, opendir, releasedir, fsyncdir, access, create, truncate, lock, init and destroy are special purpose methods, without which a full featured filesystem can still be implemented.

    +

    In general, all methods are expected to perform any necessary permission checking. However, a filesystem may delegate this task to the kernel by passing the default_permissions mount option to fuse_new(). In this case, methods will only be called if the kernel's permission check has succeeded.

    +

    Almost all operations take a path which can be of any length.

    + +

    Definition at line 349 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ access

    + +
    +
    + + + + +
    int(* fuse_operations::access) (const char *, int)
    +
    +

    Check file access permissions

    +

    This will be called for the access() system call. If the 'default_permissions' mount option is given, this method is not called.

    +

    This method is not called under Linux kernel versions 2.4.x

    + +

    Definition at line 660 of file fuse.h.

    + +
    +
    + +

    ◆ bmap

    + +
    +
    + + + + +
    int(* fuse_operations::bmap) (const char *, size_t blocksize, uint64_t *idx)
    +
    +

    Map block index within file to block index within device

    +

    Note: This makes sense only for block device backed filesystems mounted with the 'blkdev' option

    + +

    Definition at line 728 of file fuse.h.

    + +
    +
    + +

    ◆ chmod

    + +
    +
    + + + + +
    int(* fuse_operations::chmod) (const char *, mode_t, struct fuse_file_info *fi)
    +
    +

    Change the permission bits of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    + +

    Definition at line 417 of file fuse.h.

    + +
    +
    + +

    ◆ chown

    + +
    +
    + + + + +
    int(* fuse_operations::chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi)
    +
    +

    Change the owner and group of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 427 of file fuse.h.

    + +
    +
    + +

    ◆ copy_file_range

    + +
    +
    + + + + +
    ssize_t(* fuse_operations::copy_file_range) (const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
    +
    +

    Copy a range of data from one file to another

    +

    Performs an optimized copy between two file descriptors without the additional cost of transferring data through the FUSE kernel module to user space (glibc) and then back into the FUSE filesystem again.

    +

    In case this method is not implemented, applications are expected to fall back to a regular file copy. (Some glibc versions did this emulation automatically, but the emulation has been removed from all glibc release branches.)

    + +

    Definition at line 843 of file fuse.h.

    + +
    +
    + +

    ◆ create

    + +
    +
    + + + + +
    int(* fuse_operations::create) (const char *, mode_t, struct fuse_file_info *)
    +
    +

    Create and open a file

    +

    If the file does not exist, first create it with the specified mode, and then open it.

    +

    If this method is not implemented or under Linux kernel versions earlier than 2.6.15, the mknod() and open() methods will be called instead.

    + +

    Definition at line 672 of file fuse.h.

    + +
    +
    + +

    ◆ destroy

    + +
    +
    + + + + +
    void(* fuse_operations::destroy) (void *private_data)
    +
    +

    Clean up filesystem

    +

    Called on filesystem exit.

    + +

    Definition at line 649 of file fuse.h.

    + +
    +
    + +

    ◆ fallocate

    + +
    +
    + + + + +
    int(* fuse_operations::fallocate) (const char *, int, off_t, off_t, struct fuse_file_info *)
    +
    +

    Allocates space for an open file

    +

    This function ensures that required space is allocated for specified file. If this function returns success then any subsequent write request to specified range is guaranteed not to fail because of lack of space on the file system media.

    + +

    Definition at line 828 of file fuse.h.

    + +
    +
    + +

    ◆ flock

    + +
    +
    + + + + +
    int(* fuse_operations::flock) (const char *, struct fuse_file_info *, int op)
    +
    +

    Perform BSD file locking operation

    +

    The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN

    +

    Nonblocking requests will be indicated by ORing LOCK_NB to the above operations

    +

    For more information see the flock(2) manual page.

    +

    Additionally fi->owner will be set to a value unique to this open file. This same value will be supplied to ->release() when the file is released.

    +

    Note: if this method is not implemented, the kernel will still allow file locking to work locally. Hence it is only interesting for network filesystems and similar.

    + +

    Definition at line 818 of file fuse.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    int(* fuse_operations::flush) (const char *, struct fuse_file_info *)
    +
    +

    Possibly flush cached data

    +

    BIG NOTE: This is not equivalent to fsync(). It's not a request to sync dirty data.

    +

    Flush is called on each close() of a file descriptor, as opposed to release which is called on the close of the last file descriptor for a file. Under Linux, errors returned by flush() will be passed to userspace as errors from close(), so flush() is a good place to write back any cached dirty data. However, many applications ignore errors on close(), and on non-Linux systems, close() may succeed even if flush() returns an error. For these reasons, filesystems should not assume that errors returned by flush will ever be noticed or even delivered.

    +

    NOTE: The flush() method may be called more than once for each open(). This happens if more than one file descriptor refers to an open file handle, e.g. due to dup(), dup2() or fork() calls. It is not possible to determine if a flush is final, so each flush should be treated equally. Multiple write-flush sequences are relatively rare, so this shouldn't be a problem.

    +

    Filesystems shouldn't assume that flush will be called at any particular point. It may be called more times than expected, or not at all.

    + +

    Definition at line 546 of file fuse.h.

    + +
    +
    + +

    ◆ fsync

    + +
    +
    + + + + +
    int(* fuse_operations::fsync) (const char *, int, struct fuse_file_info *)
    +
    +

    Synchronize file contents

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data.

    + +

    Definition at line 567 of file fuse.h.

    + +
    +
    + +

    ◆ fsyncdir

    + +
    +
    + + + + +
    int(* fuse_operations::fsyncdir) (const char *, int, struct fuse_file_info *)
    +
    +

    Synchronize directory contents

    +

    If the directory has been removed after the call to opendir, the path parameter will be NULL.

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data

    + +

    Definition at line 631 of file fuse.h.

    + +
    +
    + +

    ◆ getattr

    + +
    +
    + + + + +
    int(* fuse_operations::getattr) (const char *, struct stat *, struct fuse_file_info *fi)
    +
    +

    Get file attributes.

    +

    Similar to stat(). The 'st_dev' and 'st_blksize' fields are ignored. The 'st_ino' field is ignored except if the 'use_ino' mount option is given. In that case it is passed to userspace, but libfuse and the kernel will still assign a different inode for internal use (called the "nodeid").

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    + +

    Definition at line 361 of file fuse.h.

    + +
    +
    + +

    ◆ getxattr

    + +
    +
    + + + + +
    int(* fuse_operations::getxattr) (const char *, const char *, char *, size_t)
    +
    +

    Get extended attributes

    + +

    Definition at line 573 of file fuse.h.

    + +
    +
    + +

    ◆ init

    + +
    +
    + + + + +
    void *(* fuse_operations::init) (struct fuse_conn_info *conn, struct fuse_config *cfg)
    +
    +

    Initialize filesystem

    +

    The return value will passed in the private_data field of struct fuse_context to all file operations, and as a parameter to the destroy() method. It overrides the initial value provided to fuse_main() / fuse_new().

    + +

    Definition at line 641 of file fuse.h.

    + +
    +
    + +

    ◆ ioctl

    + +
    +
    + + + + +
    int(* fuse_operations::ioctl) (const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
    +
    +

    Ioctl

    +

    flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in 64bit environment. The size and direction of data is determined by IOC*() decoding of cmd. For _IOC_NONE, data will be NULL, for _IOC_WRITE data is out area, for _IOC_READ in area and if both are set in/out area. In all non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.

    +

    If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a directory file handle.

    +

    Note : the unsigned long request submitted by the application is truncated to 32 bits.

    + +

    Definition at line 750 of file fuse.h.

    + +
    +
    + +

    ◆ link

    + +
    +
    + + + + +
    int(* fuse_operations::link) (const char *, const char *)
    +
    +

    Create a hard link to a file

    + +

    Definition at line 410 of file fuse.h.

    + +
    +
    + +

    ◆ listxattr

    + +
    +
    + + + + +
    int(* fuse_operations::listxattr) (const char *, char *, size_t)
    +
    +

    List extended attributes

    + +

    Definition at line 576 of file fuse.h.

    + +
    +
    + +

    ◆ lock

    + +
    +
    + + + + +
    int(* fuse_operations::lock) (const char *, struct fuse_file_info *, int cmd, struct flock *)
    +
    +

    Perform POSIX file locking operation

    +

    The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW.

    +

    For the meaning of fields in 'struct flock' see the man page for fcntl(2). The l_whence field will always be set to SEEK_SET.

    +

    For checking lock ownership, the 'fuse_file_info->owner' argument must be used.

    +

    For F_GETLK operation, the library will first check currently held locks, and if a conflicting lock is found it will return information without calling this method. This ensures, that for local locks the l_pid field is correctly filled in. The results may not be accurate in case of race conditions and in the presence of hard links, but it's unlikely that an application would rely on accurate GETLK results in these cases. If a conflicting lock is not found, this method will be called, and the filesystem may fill out l_pid by a meaningful value, or it may leave this field zero.

    +

    For F_SETLK and F_SETLKW the l_pid field will be set to the pid of the process performing the locking operation.

    +

    Note: if this method is not implemented, the kernel will still allow file locking to work locally. Hence it is only interesting for network filesystems and similar.

    + +

    Definition at line 704 of file fuse.h.

    + +
    +
    + +

    ◆ lseek

    + +
    +
    + + + + +
    off_t(* fuse_operations::lseek) (const char *, off_t off, int whence, struct fuse_file_info *)
    +
    +

    Find next data or hole after the specified offset

    + +

    Definition at line 852 of file fuse.h.

    + +
    +
    + +

    ◆ mkdir

    + +
    +
    + + + + +
    int(* fuse_operations::mkdir) (const char *, mode_t)
    +
    +

    Create a directory

    +

    Note that the mode argument may not have the type specification bits set, i.e. S_ISDIR(mode) can be false. To obtain the correct directory type bits use mode|S_IFDIR

    + +

    Definition at line 387 of file fuse.h.

    + +
    +
    + +

    ◆ mknod

    + +
    +
    + + + + +
    int(* fuse_operations::mknod) (const char *, mode_t, dev_t)
    +
    +

    Create a file node

    +

    This is called for creation of all non-directory, non-symlink nodes. If the filesystem defines a create() method, then for regular files that will be called instead.

    + +

    Definition at line 379 of file fuse.h.

    + +
    +
    + +

    ◆ open

    + +
    +
    + + + + +
    int(* fuse_operations::open) (const char *, struct fuse_file_info *)
    +
    +

    Open a file

    +

    Open flags are available in fi->flags. The following rules apply.

    +
      +
    • Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel.
    • +
    • Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) should be used by the filesystem to check if the operation is permitted. If the -o default_permissions mount option is given, this check is already done by the kernel before calling open() and may thus be omitted by the filesystem.
    • +
    • When writeback caching is enabled, the kernel may send read requests even for files opened with O_WRONLY. The filesystem should be prepared to handle this.
    • +
    • When writeback caching is disabled, the filesystem is expected to properly handle the O_APPEND flag and ensure that each write is appending to the end of the file.
    • +
    • When writeback caching is enabled, the kernel will handle O_APPEND. However, unless all changes to the file come through the kernel this will not work reliably. The filesystem should thus either ignore the O_APPEND flag (and let the kernel handle it), or return an error (indicating that reliably O_APPEND is not available).
    • +
    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other file operations (read, write, flush, release, fsync).

    +

    Filesystem may also implement stateless file I/O and not store anything in fi->fh.

    +

    There are also some flags (direct_io, keep_cache) which the filesystem may set in fi, to change the way the file is opened. See fuse_file_info structure in <fuse_common.h> for more details.

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPEN_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to open will also succeed without being sent to the filesystem process.

    + +

    Definition at line 486 of file fuse.h.

    + +
    +
    + +

    ◆ opendir

    + +
    +
    + + + + +
    int(* fuse_operations::opendir) (const char *, struct fuse_file_info *)
    +
    +

    Open directory

    +

    Unless the 'default_permissions' mount option is given, this method should check if opendir is permitted for this directory. Optionally opendir may also return an arbitrary filehandle in the fuse_file_info structure, which will be passed to readdir, releasedir and fsyncdir.

    + +

    Definition at line 589 of file fuse.h.

    + +
    +
    + +

    ◆ poll

    + +
    +
    + + + + +
    int(* fuse_operations::poll) (const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
    +
    +

    Poll for IO readiness events

    +

    Note: If ph is non-NULL, the client should notify when IO readiness events occur by calling fuse_notify_poll() with the specified ph.

    +

    Regardless of the number of times poll with a non-NULL ph is received, single notification is enough to clear all. Notifying more times incurs overhead but doesn't harm correctness.

    +

    The callee is responsible for destroying ph with fuse_pollhandle_destroy() when no longer in use.

    + +

    Definition at line 769 of file fuse.h.

    + +
    +
    + +

    ◆ read

    + +
    +
    + + + + +
    int(* fuse_operations::read) (const char *, char *, size_t, off_t, struct fuse_file_info *)
    +
    +

    Read data from an open file

    +

    Read should return exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the 'direct_io' mount option is specified, in which case the return value of the read system call will reflect the return value of this operation.

    + +

    Definition at line 497 of file fuse.h.

    + +
    +
    + +

    ◆ read_buf

    + +
    +
    + + + + +
    int(* fuse_operations::read_buf) (const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
    +
    +

    Store data from an open file in a buffer

    +

    Similar to the read() method, but data is stored and returned in a generic buffer.

    +

    No actual copying of data has to take place, the source file descriptor may simply be stored in the buffer for later data transfer.

    +

    The buffer must be allocated dynamically and stored at the location pointed to by bufp. If the buffer contains memory regions, they too must be allocated using malloc(). The allocated memory will be freed by the caller.

    + +

    Definition at line 798 of file fuse.h.

    + +
    +
    + +

    ◆ readdir

    + +
    +
    + + + + +
    int(* fuse_operations::readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
    +
    +

    Read directory

    +

    The filesystem may choose between two modes of operation:

    +

    1) The readdir implementation ignores the offset parameter, and passes zero to the filler function's offset. The filler function will not return '1' (unless an error happens), so the whole directory is read in a single readdir operation.

    +

    2) The readdir implementation keeps track of the offsets of the directory entries. It uses the offset parameter and always passes non-zero offset to the filler function. When the buffer is full (or an error happens) the filler function will return '1'.

    +

    When FUSE_READDIR_PLUS is not set, only some parameters of the fill function (the fuse_fill_dir_t parameter) are actually used: The file type (which is part of stat::st_mode) is used. And if fuse_config::use_ino is set, the inode (stat::st_ino) is also used. The other fields are ignored when FUSE_READDIR_PLUS is not set.

    + +

    Definition at line 613 of file fuse.h.

    + +
    +
    + +

    ◆ readlink

    + +
    +
    + + + + +
    int(* fuse_operations::readlink) (const char *, char *, size_t)
    +
    +

    Read the target of a symbolic link

    +

    The buffer should be filled with a null terminated string. The buffer size argument includes the space for the terminating null character. If the linkname is too long to fit in the buffer, it should be truncated. The return value should be 0 for success.

    + +

    Definition at line 371 of file fuse.h.

    + +
    +
    + +

    ◆ release

    + +
    +
    + + + + +
    int(* fuse_operations::release) (const char *, struct fuse_file_info *)
    +
    +

    Release an open file

    +

    Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.

    +

    For every open() call there will be exactly one release() call with the same flags and file handle. It is possible to have a file opened more than once, in which case only the last release will mean, that no more reads/writes will happen on the file. The return value of release is ignored.

    + +

    Definition at line 560 of file fuse.h.

    + +
    +
    + +

    ◆ releasedir

    + +
    +
    + + + + +
    int(* fuse_operations::releasedir) (const char *, struct fuse_file_info *)
    +
    +

    Release directory

    +

    If the directory has been removed after the call to opendir, the path parameter will be NULL.

    + +

    Definition at line 621 of file fuse.h.

    + +
    +
    + +

    ◆ removexattr

    + +
    +
    + + + + +
    int(* fuse_operations::removexattr) (const char *, const char *)
    +
    +

    Remove extended attributes

    + +

    Definition at line 579 of file fuse.h.

    + +
    +
    + +

    ◆ rename

    + +
    +
    + + + + +
    int(* fuse_operations::rename) (const char *, const char *, unsigned int flags)
    +
    +

    Rename a file

    +

    flags may be RENAME_EXCHANGE or RENAME_NOREPLACE. If RENAME_NOREPLACE is specified, the filesystem must not overwrite newname if it exists and return an error instead. If RENAME_EXCHANGE is specified, the filesystem must atomically exchange the two files, i.e. both must exist and neither may be deleted.

    + +

    Definition at line 407 of file fuse.h.

    + +
    +
    + +

    ◆ rmdir

    + +
    +
    + + + + +
    int(* fuse_operations::rmdir) (const char *)
    +
    +

    Remove a directory

    + +

    Definition at line 393 of file fuse.h.

    + +
    +
    + +

    ◆ setxattr

    + +
    +
    + + + + +
    int(* fuse_operations::setxattr) (const char *, const char *, const char *, size_t, int)
    +
    +

    Set extended attributes

    + +

    Definition at line 570 of file fuse.h.

    + +
    +
    + +

    ◆ statfs

    + +
    +
    + + + + +
    int(* fuse_operations::statfs) (const char *, struct statvfs *)
    +
    +

    Get file system statistics

    +

    The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored

    + +

    Definition at line 516 of file fuse.h.

    + +
    +
    + +

    ◆ symlink

    + +
    +
    + + + + +
    int(* fuse_operations::symlink) (const char *, const char *)
    +
    +

    Create a symbolic link

    + +

    Definition at line 396 of file fuse.h.

    + +
    +
    + +

    ◆ truncate

    + +
    +
    + + + + +
    int(* fuse_operations::truncate) (const char *, off_t, struct fuse_file_info *fi)
    +
    +

    Change the size of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 437 of file fuse.h.

    + +
    +
    + +

    ◆ unlink

    + +
    +
    + + + + +
    int(* fuse_operations::unlink) (const char *)
    +
    +

    Remove a file

    + +

    Definition at line 390 of file fuse.h.

    + +
    +
    + +

    ◆ utimens

    + +
    +
    + + + + +
    int(* fuse_operations::utimens) (const char *, const struct timespec tv[2], struct fuse_file_info *fi)
    +
    +

    Change the access and modification times of a file with nanosecond resolution

    +

    This supersedes the old utime() interface. New applications should use this.

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    See the utimensat(2) man page for details.

    + +

    Definition at line 719 of file fuse.h.

    + +
    +
    + +

    ◆ write

    + +
    +
    + + + + +
    int(* fuse_operations::write) (const char *, const char *, size_t, off_t, struct fuse_file_info *)
    +
    +

    Write data to an open file

    +

    Write should return exactly the number of bytes requested except on error. An exception to this is when the 'direct_io' mount option is specified (see read operation).

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 509 of file fuse.h.

    + +
    +
    + +

    ◆ write_buf

    + +
    +
    + + + + +
    int(* fuse_operations::write_buf) (const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
    +
    +

    Write contents of buffer to an open file

    +

    Similar to the write() method, but data is supplied in a generic buffer. Use fuse_buf_copy() to transfer data to the destination.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 781 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__opt.html b/doc/html/structfuse__opt.html new file mode 100644 index 0000000..bd691a9 --- /dev/null +++ b/doc/html/structfuse__opt.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse_opt Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    + +
    fuse_opt Struct Reference
    +
    +
    + +

    #include <fuse_opt.h>

    + + + + + + + + +

    +Data Fields

    const char * templ
     
    unsigned long offset
     
    int value
     
    +

    Detailed Description

    +

    Option description

    +

    This structure describes a single option, and action associated with it, in case it matches.

    +

    More than one such match may occur, in which case the action for each match is executed.

    +

    There are three possible actions in case of a match:

    +

    i) An integer (int or unsigned) variable determined by 'offset' is set to 'value'

    +

    ii) The processing function is called, with 'value' as the key

    +

    iii) An integer (any) or string (char *) variable determined by 'offset' is set to the value of an option parameter

    +

    'offset' should normally be either set to

    +
      +
    • 'offsetof(struct foo, member)' actions i) and iii)
    • +
    • -1 action ii)
    • +
    +

    The 'offsetof()' macro is defined in the <stddef.h> header.

    +

    The template determines which options match, and also have an effect on the action. Normally the action is either i) or ii), but if a format is present in the template, then action iii) is performed.

    +

    The types of templates are:

    +

    1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only themselves. Invalid values are "--" and anything beginning with "-o"

    +

    2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option list

    +

    3) "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter

    +

    4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform action iii).

    +

    5) "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments

    +

    6) "-x %s", etc. Combination of 4) and 5)

    +

    If the format is "%s", memory is allocated for the string unlike with scanf(). The previous value (if non-NULL) stored at the this location is freed.

    + +

    Definition at line 77 of file fuse_opt.h.

    +

    Field Documentation

    + +

    ◆ offset

    + +
    +
    + + + + +
    unsigned long fuse_opt::offset
    +
    +

    Offset of variable within 'data' parameter of fuse_opt_parse() or -1

    + +

    Definition at line 85 of file fuse_opt.h.

    + +
    +
    + +

    ◆ templ

    + +
    +
    + + + + +
    const char* fuse_opt::templ
    +
    +

    Matching template and optional parameter formatting

    + +

    Definition at line 79 of file fuse_opt.h.

    + +
    +
    + +

    ◆ value

    + +
    +
    + + + + +
    int fuse_opt::value
    +
    +

    Value to set the variable to, or to be passed as 'key' to the processing function. Ignored if template has a format

    + +

    Definition at line 91 of file fuse_opt.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structfuse__supp__groups.html b/doc/html/structfuse__supp__groups.html new file mode 100644 index 0000000..557980b --- /dev/null +++ b/doc/html/structfuse__supp__groups.html @@ -0,0 +1,60 @@ + + + + + + + +libfuse: fuse_supp_groups Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    fuse_supp_groups Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    struct fuse_supp_groups - Supplementary group extension @nr_groups: number of supplementary groups @groups: flexible array of group IDs

    + +

    Definition at line 1184 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/structlibfuse__version.html b/doc/html/structlibfuse__version.html new file mode 100644 index 0000000..abd05ed --- /dev/null +++ b/doc/html/structlibfuse__version.html @@ -0,0 +1,60 @@ + + + + + + + +libfuse: libfuse_version Struct Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + +
    +
    +
    libfuse_version Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    +

    Detailed Description

    +

    libfuse version a file system was compiled with. Should be filled in from defines in 'libfuse_config.h'

    + +

    Definition at line 959 of file fuse_common.h.

    +

    The documentation for this struct was generated from the following file: +
    + + + + diff --git a/doc/html/subdir_8c_source.html b/doc/html/subdir_8c_source.html new file mode 100644 index 0000000..3615081 --- /dev/null +++ b/doc/html/subdir_8c_source.html @@ -0,0 +1,768 @@ + + + + + + + +libfuse: lib/modules/subdir.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    subdir.c
    +
    +
    +
    1/*
    +
    2 fuse subdir module: offset paths with a base directory
    +
    3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU LGPLv2.
    +
    6 See the file COPYING.LIB
    +
    7*/
    +
    8
    +
    9#include <fuse_config.h>
    +
    10
    +
    11#include <fuse.h>
    +
    12#include <stdio.h>
    +
    13#include <stdlib.h>
    +
    14#include <stddef.h>
    +
    15#include <string.h>
    +
    16#include <errno.h>
    +
    17
    +
    18struct subdir {
    +
    19 char *base;
    +
    20 size_t baselen;
    +
    21 int rellinks;
    +
    22 struct fuse_fs *next;
    +
    23};
    +
    24
    +
    25static struct subdir *subdir_get(void)
    +
    26{
    + +
    28}
    +
    29
    +
    30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
    +
    31{
    +
    32 char *newpath = NULL;
    +
    33
    +
    34 if (path != NULL) {
    +
    35 unsigned newlen = d->baselen + strlen(path);
    +
    36
    +
    37 newpath = malloc(newlen + 2);
    +
    38 if (!newpath)
    +
    39 return -ENOMEM;
    +
    40
    +
    41 if (path[0] == '/')
    +
    42 path++;
    +
    43 strcpy(newpath, d->base);
    +
    44 strcpy(newpath + d->baselen, path);
    +
    45 if (!newpath[0])
    +
    46 strcpy(newpath, ".");
    +
    47 }
    +
    48 *newpathp = newpath;
    +
    49
    +
    50 return 0;
    +
    51}
    +
    52
    +
    53static int subdir_getattr(const char *path, struct stat *stbuf,
    +
    54 struct fuse_file_info *fi)
    +
    55{
    +
    56 struct subdir *d = subdir_get();
    +
    57 char *newpath;
    +
    58 int err = subdir_addpath(d, path, &newpath);
    +
    59 if (!err) {
    +
    60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
    +
    61 free(newpath);
    +
    62 }
    +
    63 return err;
    +
    64}
    +
    65
    +
    66static int subdir_access(const char *path, int mask)
    +
    67{
    +
    68 struct subdir *d = subdir_get();
    +
    69 char *newpath;
    +
    70 int err = subdir_addpath(d, path, &newpath);
    +
    71 if (!err) {
    +
    72 err = fuse_fs_access(d->next, newpath, mask);
    +
    73 free(newpath);
    +
    74 }
    +
    75 return err;
    +
    76}
    +
    77
    +
    78
    +
    79static int count_components(const char *p)
    +
    80{
    +
    81 int ctr;
    +
    82
    +
    83 for (; *p == '/'; p++);
    +
    84 for (ctr = 0; *p; ctr++) {
    +
    85 for (; *p && *p != '/'; p++);
    +
    86 for (; *p == '/'; p++);
    +
    87 }
    +
    88 return ctr;
    +
    89}
    +
    90
    +
    91static void strip_common(const char **sp, const char **tp)
    +
    92{
    +
    93 const char *s = *sp;
    +
    94 const char *t = *tp;
    +
    95 do {
    +
    96 for (; *s == '/'; s++);
    +
    97 for (; *t == '/'; t++);
    +
    98 *tp = t;
    +
    99 *sp = s;
    +
    100 for (; *s == *t && *s && *s != '/'; s++, t++);
    +
    101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
    +
    102}
    +
    103
    +
    104static void transform_symlink(struct subdir *d, const char *path,
    +
    105 char *buf, size_t size)
    +
    106{
    +
    107 const char *l = buf;
    +
    108 size_t llen;
    +
    109 char *s;
    +
    110 int dotdots;
    +
    111 int i;
    +
    112
    +
    113 if (l[0] != '/' || d->base[0] != '/')
    +
    114 return;
    +
    115
    +
    116 strip_common(&l, &path);
    +
    117 if (l - buf < (long) d->baselen)
    +
    118 return;
    +
    119
    +
    120 dotdots = count_components(path);
    +
    121 if (!dotdots)
    +
    122 return;
    +
    123 dotdots--;
    +
    124
    +
    125 llen = strlen(l);
    +
    126 if (dotdots * 3 + llen + 2 > size)
    +
    127 return;
    +
    128
    +
    129 s = buf + dotdots * 3;
    +
    130 if (llen)
    +
    131 memmove(s, l, llen + 1);
    +
    132 else if (!dotdots)
    +
    133 strcpy(s, ".");
    +
    134 else
    +
    135 *s = '\0';
    +
    136
    +
    137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
    +
    138 memcpy(s, "../", 3);
    +
    139}
    +
    140
    +
    141
    +
    142static int subdir_readlink(const char *path, char *buf, size_t size)
    +
    143{
    +
    144 struct subdir *d = subdir_get();
    +
    145 char *newpath;
    +
    146 int err = subdir_addpath(d, path, &newpath);
    +
    147 if (!err) {
    +
    148 err = fuse_fs_readlink(d->next, newpath, buf, size);
    +
    149 if (!err && d->rellinks)
    +
    150 transform_symlink(d, newpath, buf, size);
    +
    151 free(newpath);
    +
    152 }
    +
    153 return err;
    +
    154}
    +
    155
    +
    156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
    +
    157{
    +
    158 struct subdir *d = subdir_get();
    +
    159 char *newpath;
    +
    160 int err = subdir_addpath(d, path, &newpath);
    +
    161 if (!err) {
    +
    162 err = fuse_fs_opendir(d->next, newpath, fi);
    +
    163 free(newpath);
    +
    164 }
    +
    165 return err;
    +
    166}
    +
    167
    +
    168static int subdir_readdir(const char *path, void *buf,
    +
    169 fuse_fill_dir_t filler, off_t offset,
    +
    170 struct fuse_file_info *fi,
    +
    171 enum fuse_readdir_flags flags)
    +
    172{
    +
    173 struct subdir *d = subdir_get();
    +
    174 char *newpath;
    +
    175 int err = subdir_addpath(d, path, &newpath);
    +
    176 if (!err) {
    +
    177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
    +
    178 fi, flags);
    +
    179 free(newpath);
    +
    180 }
    +
    181 return err;
    +
    182}
    +
    183
    +
    184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
    +
    185{
    +
    186 struct subdir *d = subdir_get();
    +
    187 char *newpath;
    +
    188 int err = subdir_addpath(d, path, &newpath);
    +
    189 if (!err) {
    +
    190 err = fuse_fs_releasedir(d->next, newpath, fi);
    +
    191 free(newpath);
    +
    192 }
    +
    193 return err;
    +
    194}
    +
    195
    +
    196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    197{
    +
    198 struct subdir *d = subdir_get();
    +
    199 char *newpath;
    +
    200 int err = subdir_addpath(d, path, &newpath);
    +
    201 if (!err) {
    +
    202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
    +
    203 free(newpath);
    +
    204 }
    +
    205 return err;
    +
    206}
    +
    207
    +
    208static int subdir_mkdir(const char *path, mode_t mode)
    +
    209{
    +
    210 struct subdir *d = subdir_get();
    +
    211 char *newpath;
    +
    212 int err = subdir_addpath(d, path, &newpath);
    +
    213 if (!err) {
    +
    214 err = fuse_fs_mkdir(d->next, newpath, mode);
    +
    215 free(newpath);
    +
    216 }
    +
    217 return err;
    +
    218}
    +
    219
    +
    220static int subdir_unlink(const char *path)
    +
    221{
    +
    222 struct subdir *d = subdir_get();
    +
    223 char *newpath;
    +
    224 int err = subdir_addpath(d, path, &newpath);
    +
    225 if (!err) {
    +
    226 err = fuse_fs_unlink(d->next, newpath);
    +
    227 free(newpath);
    +
    228 }
    +
    229 return err;
    +
    230}
    +
    231
    +
    232static int subdir_rmdir(const char *path)
    +
    233{
    +
    234 struct subdir *d = subdir_get();
    +
    235 char *newpath;
    +
    236 int err = subdir_addpath(d, path, &newpath);
    +
    237 if (!err) {
    +
    238 err = fuse_fs_rmdir(d->next, newpath);
    +
    239 free(newpath);
    +
    240 }
    +
    241 return err;
    +
    242}
    +
    243
    +
    244static int subdir_symlink(const char *from, const char *path)
    +
    245{
    +
    246 struct subdir *d = subdir_get();
    +
    247 char *newpath;
    +
    248 int err = subdir_addpath(d, path, &newpath);
    +
    249 if (!err) {
    +
    250 err = fuse_fs_symlink(d->next, from, newpath);
    +
    251 free(newpath);
    +
    252 }
    +
    253 return err;
    +
    254}
    +
    255
    +
    256static int subdir_rename(const char *from, const char *to, unsigned int flags)
    +
    257{
    +
    258 struct subdir *d = subdir_get();
    +
    259 char *newfrom;
    +
    260 char *newto;
    +
    261 int err = subdir_addpath(d, from, &newfrom);
    +
    262 if (!err) {
    +
    263 err = subdir_addpath(d, to, &newto);
    +
    264 if (!err) {
    +
    265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
    +
    266 free(newto);
    +
    267 }
    +
    268 free(newfrom);
    +
    269 }
    +
    270 return err;
    +
    271}
    +
    272
    +
    273static int subdir_link(const char *from, const char *to)
    +
    274{
    +
    275 struct subdir *d = subdir_get();
    +
    276 char *newfrom;
    +
    277 char *newto;
    +
    278 int err = subdir_addpath(d, from, &newfrom);
    +
    279 if (!err) {
    +
    280 err = subdir_addpath(d, to, &newto);
    +
    281 if (!err) {
    +
    282 err = fuse_fs_link(d->next, newfrom, newto);
    +
    283 free(newto);
    +
    284 }
    +
    285 free(newfrom);
    +
    286 }
    +
    287 return err;
    +
    288}
    +
    289
    +
    290static int subdir_chmod(const char *path, mode_t mode,
    +
    291 struct fuse_file_info *fi)
    +
    292{
    +
    293 struct subdir *d = subdir_get();
    +
    294 char *newpath;
    +
    295 int err = subdir_addpath(d, path, &newpath);
    +
    296 if (!err) {
    +
    297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
    +
    298 free(newpath);
    +
    299 }
    +
    300 return err;
    +
    301}
    +
    302
    +
    303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
    +
    304 struct fuse_file_info *fi)
    +
    305{
    +
    306 struct subdir *d = subdir_get();
    +
    307 char *newpath;
    +
    308 int err = subdir_addpath(d, path, &newpath);
    +
    309 if (!err) {
    +
    310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
    +
    311 free(newpath);
    +
    312 }
    +
    313 return err;
    +
    314}
    +
    315
    +
    316static int subdir_truncate(const char *path, off_t size,
    +
    317 struct fuse_file_info *fi)
    +
    318{
    +
    319 struct subdir *d = subdir_get();
    +
    320 char *newpath;
    +
    321 int err = subdir_addpath(d, path, &newpath);
    +
    322 if (!err) {
    +
    323 err = fuse_fs_truncate(d->next, newpath, size, fi);
    +
    324 free(newpath);
    +
    325 }
    +
    326 return err;
    +
    327}
    +
    328
    +
    329static int subdir_utimens(const char *path, const struct timespec ts[2],
    +
    330 struct fuse_file_info *fi)
    +
    331{
    +
    332 struct subdir *d = subdir_get();
    +
    333 char *newpath;
    +
    334 int err = subdir_addpath(d, path, &newpath);
    +
    335 if (!err) {
    +
    336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
    +
    337 free(newpath);
    +
    338 }
    +
    339 return err;
    +
    340}
    +
    341
    +
    342static int subdir_create(const char *path, mode_t mode,
    +
    343 struct fuse_file_info *fi)
    +
    344{
    +
    345 struct subdir *d = subdir_get();
    +
    346 char *newpath;
    +
    347 int err = subdir_addpath(d, path, &newpath);
    +
    348 if (!err) {
    +
    349 err = fuse_fs_create(d->next, newpath, mode, fi);
    +
    350 free(newpath);
    +
    351 }
    +
    352 return err;
    +
    353}
    +
    354
    +
    355static int subdir_open(const char *path, struct fuse_file_info *fi)
    +
    356{
    +
    357 struct subdir *d = subdir_get();
    +
    358 char *newpath;
    +
    359 int err = subdir_addpath(d, path, &newpath);
    +
    360 if (!err) {
    +
    361 err = fuse_fs_open(d->next, newpath, fi);
    +
    362 free(newpath);
    +
    363 }
    +
    364 return err;
    +
    365}
    +
    366
    +
    367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    368 size_t size, off_t offset, struct fuse_file_info *fi)
    +
    369{
    +
    370 struct subdir *d = subdir_get();
    +
    371 char *newpath;
    +
    372 int err = subdir_addpath(d, path, &newpath);
    +
    373 if (!err) {
    +
    374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
    +
    375 free(newpath);
    +
    376 }
    +
    377 return err;
    +
    378}
    +
    379
    +
    380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    381 off_t offset, struct fuse_file_info *fi)
    +
    382{
    +
    383 struct subdir *d = subdir_get();
    +
    384 char *newpath;
    +
    385 int err = subdir_addpath(d, path, &newpath);
    +
    386 if (!err) {
    +
    387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
    +
    388 free(newpath);
    +
    389 }
    +
    390 return err;
    +
    391}
    +
    392
    +
    393static int subdir_statfs(const char *path, struct statvfs *stbuf)
    +
    394{
    +
    395 struct subdir *d = subdir_get();
    +
    396 char *newpath;
    +
    397 int err = subdir_addpath(d, path, &newpath);
    +
    398 if (!err) {
    +
    399 err = fuse_fs_statfs(d->next, newpath, stbuf);
    +
    400 free(newpath);
    +
    401 }
    +
    402 return err;
    +
    403}
    +
    404
    +
    405static int subdir_flush(const char *path, struct fuse_file_info *fi)
    +
    406{
    +
    407 struct subdir *d = subdir_get();
    +
    408 char *newpath;
    +
    409 int err = subdir_addpath(d, path, &newpath);
    +
    410 if (!err) {
    +
    411 err = fuse_fs_flush(d->next, newpath, fi);
    +
    412 free(newpath);
    +
    413 }
    +
    414 return err;
    +
    415}
    +
    416
    +
    417static int subdir_release(const char *path, struct fuse_file_info *fi)
    +
    418{
    +
    419 struct subdir *d = subdir_get();
    +
    420 char *newpath;
    +
    421 int err = subdir_addpath(d, path, &newpath);
    +
    422 if (!err) {
    +
    423 err = fuse_fs_release(d->next, newpath, fi);
    +
    424 free(newpath);
    +
    425 }
    +
    426 return err;
    +
    427}
    +
    428
    +
    429static int subdir_fsync(const char *path, int isdatasync,
    +
    430 struct fuse_file_info *fi)
    +
    431{
    +
    432 struct subdir *d = subdir_get();
    +
    433 char *newpath;
    +
    434 int err = subdir_addpath(d, path, &newpath);
    +
    435 if (!err) {
    +
    436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
    +
    437 free(newpath);
    +
    438 }
    +
    439 return err;
    +
    440}
    +
    441
    +
    442static int subdir_fsyncdir(const char *path, int isdatasync,
    +
    443 struct fuse_file_info *fi)
    +
    444{
    +
    445 struct subdir *d = subdir_get();
    +
    446 char *newpath;
    +
    447 int err = subdir_addpath(d, path, &newpath);
    +
    448 if (!err) {
    +
    449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
    +
    450 free(newpath);
    +
    451 }
    +
    452 return err;
    +
    453}
    +
    454
    +
    455static int subdir_setxattr(const char *path, const char *name,
    +
    456 const char *value, size_t size, int flags)
    +
    457{
    +
    458 struct subdir *d = subdir_get();
    +
    459 char *newpath;
    +
    460 int err = subdir_addpath(d, path, &newpath);
    +
    461 if (!err) {
    +
    462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
    +
    463 flags);
    +
    464 free(newpath);
    +
    465 }
    +
    466 return err;
    +
    467}
    +
    468
    +
    469static int subdir_getxattr(const char *path, const char *name, char *value,
    +
    470 size_t size)
    +
    471{
    +
    472 struct subdir *d = subdir_get();
    +
    473 char *newpath;
    +
    474 int err = subdir_addpath(d, path, &newpath);
    +
    475 if (!err) {
    +
    476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
    +
    477 free(newpath);
    +
    478 }
    +
    479 return err;
    +
    480}
    +
    481
    +
    482static int subdir_listxattr(const char *path, char *list, size_t size)
    +
    483{
    +
    484 struct subdir *d = subdir_get();
    +
    485 char *newpath;
    +
    486 int err = subdir_addpath(d, path, &newpath);
    +
    487 if (!err) {
    +
    488 err = fuse_fs_listxattr(d->next, newpath, list, size);
    +
    489 free(newpath);
    +
    490 }
    +
    491 return err;
    +
    492}
    +
    493
    +
    494static int subdir_removexattr(const char *path, const char *name)
    +
    495{
    +
    496 struct subdir *d = subdir_get();
    +
    497 char *newpath;
    +
    498 int err = subdir_addpath(d, path, &newpath);
    +
    499 if (!err) {
    +
    500 err = fuse_fs_removexattr(d->next, newpath, name);
    +
    501 free(newpath);
    +
    502 }
    +
    503 return err;
    +
    504}
    +
    505
    +
    506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    507 struct flock *lock)
    +
    508{
    +
    509 struct subdir *d = subdir_get();
    +
    510 char *newpath;
    +
    511 int err = subdir_addpath(d, path, &newpath);
    +
    512 if (!err) {
    +
    513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
    +
    514 free(newpath);
    +
    515 }
    +
    516 return err;
    +
    517}
    +
    518
    +
    519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    520{
    +
    521 struct subdir *d = subdir_get();
    +
    522 char *newpath;
    +
    523 int err = subdir_addpath(d, path, &newpath);
    +
    524 if (!err) {
    +
    525 err = fuse_fs_flock(d->next, newpath, fi, op);
    +
    526 free(newpath);
    +
    527 }
    +
    528 return err;
    +
    529}
    +
    530
    +
    531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
    +
    532{
    +
    533 struct subdir *d = subdir_get();
    +
    534 char *newpath;
    +
    535 int err = subdir_addpath(d, path, &newpath);
    +
    536 if (!err) {
    +
    537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
    +
    538 free(newpath);
    +
    539 }
    +
    540 return err;
    +
    541}
    +
    542
    +
    543static off_t subdir_lseek(const char *path, off_t off, int whence,
    +
    544 struct fuse_file_info *fi)
    +
    545{
    +
    546 struct subdir *ic = subdir_get();
    +
    547 char *newpath;
    +
    548 int res = subdir_addpath(ic, path, &newpath);
    +
    549 if (!res) {
    +
    550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
    +
    551 free(newpath);
    +
    552 }
    +
    553 return res;
    +
    554}
    +
    555
    +
    556static void *subdir_init(struct fuse_conn_info *conn,
    +
    557 struct fuse_config *cfg)
    +
    558{
    +
    559 struct subdir *d = subdir_get();
    +
    560 fuse_fs_init(d->next, conn, cfg);
    +
    561 /* Don't touch cfg->nullpath_ok, we can work with
    +
    562 either */
    +
    563 return d;
    +
    564}
    +
    565
    +
    566static void subdir_destroy(void *data)
    +
    567{
    +
    568 struct subdir *d = data;
    +
    569 fuse_fs_destroy(d->next);
    +
    570 free(d->base);
    +
    571 free(d);
    +
    572}
    +
    573
    +
    574static const struct fuse_operations subdir_oper = {
    +
    575 .destroy = subdir_destroy,
    +
    576 .init = subdir_init,
    +
    577 .getattr = subdir_getattr,
    +
    578 .access = subdir_access,
    +
    579 .readlink = subdir_readlink,
    +
    580 .opendir = subdir_opendir,
    +
    581 .readdir = subdir_readdir,
    +
    582 .releasedir = subdir_releasedir,
    +
    583 .mknod = subdir_mknod,
    +
    584 .mkdir = subdir_mkdir,
    +
    585 .symlink = subdir_symlink,
    +
    586 .unlink = subdir_unlink,
    +
    587 .rmdir = subdir_rmdir,
    +
    588 .rename = subdir_rename,
    +
    589 .link = subdir_link,
    +
    590 .chmod = subdir_chmod,
    +
    591 .chown = subdir_chown,
    +
    592 .truncate = subdir_truncate,
    +
    593 .utimens = subdir_utimens,
    +
    594 .create = subdir_create,
    +
    595 .open = subdir_open,
    +
    596 .read_buf = subdir_read_buf,
    +
    597 .write_buf = subdir_write_buf,
    +
    598 .statfs = subdir_statfs,
    +
    599 .flush = subdir_flush,
    +
    600 .release = subdir_release,
    +
    601 .fsync = subdir_fsync,
    +
    602 .fsyncdir = subdir_fsyncdir,
    +
    603 .setxattr = subdir_setxattr,
    +
    604 .getxattr = subdir_getxattr,
    +
    605 .listxattr = subdir_listxattr,
    +
    606 .removexattr = subdir_removexattr,
    +
    607 .lock = subdir_lock,
    +
    608 .flock = subdir_flock,
    +
    609 .bmap = subdir_bmap,
    +
    610 .lseek = subdir_lseek,
    +
    611};
    +
    612
    +
    613static const struct fuse_opt subdir_opts[] = {
    +
    614 FUSE_OPT_KEY("-h", 0),
    +
    615 FUSE_OPT_KEY("--help", 0),
    +
    616 { "subdir=%s", offsetof(struct subdir, base), 0 },
    +
    617 { "rellinks", offsetof(struct subdir, rellinks), 1 },
    +
    618 { "norellinks", offsetof(struct subdir, rellinks), 0 },
    + +
    620};
    +
    621
    +
    622static void subdir_help(void)
    +
    623{
    +
    624 printf(
    +
    625" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
    +
    626" -o [no]rellinks transform absolute symlinks to relative\n");
    +
    627}
    +
    628
    +
    629static int subdir_opt_proc(void *data, const char *arg, int key,
    +
    630 struct fuse_args *outargs)
    +
    631{
    +
    632 (void) data; (void) arg; (void) outargs;
    +
    633
    +
    634 if (!key) {
    +
    635 subdir_help();
    +
    636 return -1;
    +
    637 }
    +
    638
    +
    639 return 1;
    +
    640}
    +
    641
    +
    642static struct fuse_fs *subdir_new(struct fuse_args *args,
    +
    643 struct fuse_fs *next[])
    +
    644{
    +
    645 struct fuse_fs *fs;
    +
    646 struct subdir *d;
    +
    647
    +
    648 d = calloc(1, sizeof(struct subdir));
    +
    649 if (d == NULL) {
    +
    650 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
    +
    651 return NULL;
    +
    652 }
    +
    653
    +
    654 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
    +
    655 goto out_free;
    +
    656
    +
    657 if (!next[0] || next[1]) {
    +
    658 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
    +
    659 goto out_free;
    +
    660 }
    +
    661
    +
    662 if (!d->base) {
    +
    663 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
    +
    664 goto out_free;
    +
    665 }
    +
    666
    +
    667 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
    +
    668 char *tmp = realloc(d->base, strlen(d->base) + 2);
    +
    669 if (!tmp) {
    +
    670 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
    +
    671 goto out_free;
    +
    672 }
    +
    673 d->base = tmp;
    +
    674 strcat(d->base, "/");
    +
    675 }
    +
    676 d->baselen = strlen(d->base);
    +
    677 d->next = next[0];
    +
    678 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
    +
    679 if (!fs)
    +
    680 goto out_free;
    +
    681 return fs;
    +
    682
    +
    683out_free:
    +
    684 free(d->base);
    +
    685 free(d);
    +
    686 return NULL;
    +
    687}
    +
    688
    +
    689FUSE_REGISTER_MODULE(subdir, subdir_new);
    + +
    struct fuse_context * fuse_get_context(void)
    Definition fuse.c:4669
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
    Definition fuse.c:4879
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    #define FUSE_REGISTER_MODULE(name_, factory_)
    Definition fuse.h:1395
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...)
    Definition fuse_log.c:77
    +
    #define FUSE_OPT_KEY(templ, key)
    Definition fuse_opt.h:98
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + + + +
    void * private_data
    Definition fuse.h:874
    + + +
    void(* destroy)(void *private_data)
    Definition fuse.h:649
    + +
    + + + + diff --git a/doc/html/sync_off.png b/doc/html/sync_off.png new file mode 100644 index 0000000000000000000000000000000000000000..3b443fc62892114406e3d399421b2a881b897acc GIT binary patch literal 853 zcmV-b1FHOqP)oT|#XixUYy%lpuf3i8{fX!o zUyDD0jOrAiT^tq>fLSOOABs-#u{dV^F$b{L9&!2=9&RmV;;8s^x&UqB$PCj4FdKbh zoB1WTskPUPu05XzFbA}=KZ-GP1fPpAfSs>6AHb12UlR%-i&uOlTpFNS7{jm@mkU1V zh`nrXr~+^lsV-s1dkZOaI|kYyVj3WBpPCY{n~yd%u%e+d=f%`N0FItMPtdgBb@py; zq@v6NVArhyTC7)ULw-Jy8y42S1~4n(3LkrW8mW(F-4oXUP3E`e#g**YyqI7h-J2zK zK{m9##m4ri!7N>CqQqCcnI3hqo1I;Yh&QLNY4T`*ptiQGozK>FF$!$+84Z`xwmeMh zJ0WT+OH$WYFALEaGj2_l+#DC3t7_S`vHpSivNeFbP6+r50cO8iu)`7i%Z4BTPh@_m3Tk!nAm^)5Bqnr%Ov|Baunj#&RPtRuK& z4RGz|D5HNrW83-#ydk}tVKJrNmyYt-sTxLGlJY5nc&Re zU4SgHNPx8~Yxwr$bsju?4q&%T1874xxzq+_%?h8_ofw~(bld=o3iC)LUNR*BY%c0y zWd_jX{Y8`l%z+ol1$@Qa?Cy!(0CVIEeYpKZ`(9{z>3$CIe;pJDQk$m3p}$>xBm4lb zKo{4S)`wdU9Ba9jJbVJ0C=SOefZe%d$8=2r={nu<_^a3~>c#t_U6dye5)JrR(_a^E f@}b6j1K9lwFJq@>o)+Ry00000NkvXXu0mjfWa5j* literal 0 HcmV?d00001 diff --git a/doc/html/sync_on.png b/doc/html/sync_on.png new file mode 100644 index 0000000000000000000000000000000000000000..e08320fb64e6fa33b573005ed6d8fe294e19db76 GIT binary patch literal 845 zcmV-T1G4;yP)Y;xxyHF2B5Wzm| zOOGupOTn@c(JmBOl)e;XMNnZuiTJP>rM8<|Q`7I_))aP?*T)ow&n59{}X4$3Goat zgjs?*aasfbrokzG5cT4K=uG`E14xZl@z)F={P0Y^?$4t z>v!teRnNZym<6h{7sLyF1V0HsfEl+l6TrZpsfr1}luH~F7L}ktXu|*uVX^RG$L0`K zWs3j|0tIvVe(N%_?2{(iCPFGf#B6Hjy6o&}D$A%W%jfO8_W%ZO#-mh}EM$LMn7joJ z05dHr!5Y92g+31l<%i1(=L1a1pXX+OYnalY>31V4K}BjyRe3)9n#;-cCVRD_IG1fT zOKGeNY8q;TL@K{dj@D^scf&VCs*-Jb>8b>|`b*osv52-!A?BpbYtTQBns5EAU**$m zSnVSm(teh>tQi*S*A>#ySc=n;`BHz`DuG4&g4Kf8lLhca+zvZ7t7RflD6-i-mcK=M z!=^P$*u2)bkY5asG4gsss!Hn%u~>}kIW`vMs%lJLH+u*9<4PaV_c6U`KqWXQH%+Nu zTv41O(^ZVi@qhjQdG!fbZw&y+2o!iYymO^?ud3{P*HdoX83YV*Uu_HB=?U&W9%AU# z80}k1SS-CXTU7dcQlsm<^oYLxVSseqY6NO}dc`Nj?8vrhNuCdm@^{a3AQ_>6myOj+ z`1RsLUXF|dm|3k7s2jD(B{rzE>WI2scH8i1;=O5Cc9xB3^aJk%fQjqsu+kH#0=_5a z0nCE8@dbQa-|YIuUVvG0L_IwHMEhOj$Mj4Uq05 X8=0q~qBNan00000NkvXXu0mjfptF>5 literal 0 HcmV?d00001 diff --git a/doc/html/tab_a.png b/doc/html/tab_a.png new file mode 100644 index 0000000000000000000000000000000000000000..3b725c41c5a527a3a3e40097077d0e206a681247 GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!QlXwMjv*C{Z|8b*H5dputLHD# z=<0|*y7z(Vor?d;H&?EG&cXR}?!j-Lm&u1OOI7AIF5&c)RFE;&p0MYK>*Kl@eiymD r@|NpwKX@^z+;{u_Z~trSBfrMKa%3`zocFjEXaR$#tDnm{r-UW|TZ1%4 literal 0 HcmV?d00001 diff --git a/doc/html/tab_ad.png b/doc/html/tab_ad.png new file mode 100644 index 0000000000000000000000000000000000000000..e34850acfc24be58da6d2fd1ccc6b29cc84fe34d GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!QhuH;jv*C{Z|5d*H3V=pKi{In zd2jxLclDRPylmD}^l7{QOtL{vUjO{-WqItb5sQp2h-99b8^^Scr-=2mblCdZuUm?4 jzOJvgvt3{(cjKLW5(A@0qPS@<&}0TrS3j3^P6y&q2{!U5bk+Tso_B!YCpDh>v z{CM*1U8YvQRyBUHt^Ju0W_sq-?;9@_4equ-bavTs=gk796zopr0EBT&m;e9( literal 0 HcmV?d00001 diff --git a/doc/html/tab_s.png b/doc/html/tab_s.png new file mode 100644 index 0000000000000000000000000000000000000000..ab478c95b67371d700a20869f7de1ddd73522d50 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!QuUrLjv*C{Z|^p8HaRdjTwH7) zC?wLlL}}I{)n%R&r+1}IGmDnq;&J#%V6)9VsYhS`O^BVBQlxOUep0c$RENLq#g8A$ z)z7%K_bI&n@J+X_=x}fJoEKed-$<>=ZI-;YrdjIl`U`uzuDWSP?o#Dmo{%SgM#oan kX~E1%D-|#H#QbHoIja2U-MgvsK&LQxy85}Sb4q9e0Efg%P5=M^ literal 0 HcmV?d00001 diff --git a/doc/html/tab_sd.png b/doc/html/tab_sd.png new file mode 100644 index 0000000000000000000000000000000000000000..757a565ced4730f85c833fb2547d8e199ae68f19 GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!Qq7(&jv*C{Z|_!fH5o7*c=%9% zcILh!EA=pAQKdx-Cdiev=v{eg{8Ht<{e8_NAN~b=)%W>-WDCE0PyDHGemi$BoXwcK z{>e9^za6*c1ilttWw&V+U;WCPlV9{LdC~Ey%_H(qj`xgfES(4Yz5jSTZfCt`4E$0YRsR*S^mTCR^;V&sxC8{l_Cp7w8-YPgg&ebxsLQ00$vXK>z>% literal 0 HcmV?d00001 diff --git a/doc/html/tabs.css b/doc/html/tabs.css new file mode 100644 index 0000000..df7944b --- /dev/null +++ b/doc/html/tabs.css @@ -0,0 +1 @@ +.sm{position:relative;z-index:9999}.sm,.sm ul,.sm li{display:block;list-style:none;margin:0;padding:0;line-height:normal;direction:ltr;text-align:left;-webkit-tap-highlight-color:rgba(0,0,0,0)}.sm-rtl,.sm-rtl ul,.sm-rtl li{direction:rtl;text-align:right}.sm>li>h1,.sm>li>h2,.sm>li>h3,.sm>li>h4,.sm>li>h5,.sm>li>h6{margin:0;padding:0}.sm ul{display:none}.sm li,.sm a{position:relative}.sm a{display:block}.sm a.disabled{cursor:not-allowed}.sm:after{content:"\00a0";display:block;height:0;font:0px/0 serif;clear:both;visibility:hidden;overflow:hidden}.sm,.sm *,.sm *:before,.sm *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.main-menu-btn{position:relative;display:inline-block;width:36px;height:36px;text-indent:36px;margin-left:8px;white-space:nowrap;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.main-menu-btn-icon,.main-menu-btn-icon:before,.main-menu-btn-icon:after{position:absolute;top:50%;left:2px;height:2px;width:24px;background:var(--nav-menu-button-color);-webkit-transition:all 0.25s;transition:all 0.25s}.main-menu-btn-icon:before{content:'';top:-7px;left:0}.main-menu-btn-icon:after{content:'';top:7px;left:0}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon{height:0}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon:before{top:0;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon:after{top:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}#main-menu-state{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}#main-menu-state:not(:checked)~#main-menu{display:none}#main-menu-state:checked~#main-menu{display:block}@media (min-width: 768px){.main-menu-btn{position:absolute;top:-99999px}#main-menu-state:not(:checked)~#main-menu{display:block}}.sm-dox{background-image:var(--nav-gradient-image)}.sm-dox a,.sm-dox a:focus,.sm-dox a:hover,.sm-dox a:active{padding:0px 12px;padding-right:43px;font-family:var(--font-family-nav);font-size:13px;font-weight:bold;line-height:36px;text-decoration:none;text-shadow:var(--nav-text-normal-shadow);color:var(--nav-text-normal-color);outline:none}.sm-dox a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox a.current{color:#D23600}.sm-dox a.disabled{color:#bbb}.sm-dox a span.sub-arrow{position:absolute;top:50%;margin-top:-14px;left:auto;right:3px;width:28px;height:28px;overflow:hidden;font:bold 12px/28px monospace !important;text-align:center;text-shadow:none;background:var(--nav-menu-toggle-color);border-radius:5px}.sm-dox a span.sub-arrow:before{display:block;content:'+'}.sm-dox a.highlighted span.sub-arrow:before{display:block;content:'-'}.sm-dox>li:first-child>a,.sm-dox>li:first-child>:not(ul) a{border-radius:5px 5px 0 0}.sm-dox>li:last-child>a,.sm-dox>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul{border-radius:0 0 5px 5px}.sm-dox>li:last-child>a.highlighted,.sm-dox>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted{border-radius:0}.sm-dox ul{background:var(--nav-menu-background-color)}.sm-dox ul a,.sm-dox ul a:focus,.sm-dox ul a:hover,.sm-dox ul a:active{font-size:12px;border-left:8px solid transparent;line-height:36px;text-shadow:none;background-color:var(--nav-menu-background-color);background-image:none}.sm-dox ul a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:0px 1px 1px #000}.sm-dox ul ul a,.sm-dox ul ul a:hover,.sm-dox ul ul a:focus,.sm-dox ul ul a:active{border-left:16px solid transparent}.sm-dox ul ul ul a,.sm-dox ul ul ul a:hover,.sm-dox ul ul ul a:focus,.sm-dox ul ul ul a:active{border-left:24px solid transparent}.sm-dox ul ul ul ul a,.sm-dox ul ul ul ul a:hover,.sm-dox ul ul ul ul a:focus,.sm-dox ul ul ul ul a:active{border-left:32px solid transparent}.sm-dox ul ul ul ul ul a,.sm-dox ul ul ul ul ul a:hover,.sm-dox ul ul ul ul ul a:focus,.sm-dox ul ul ul ul ul a:active{border-left:40px solid transparent}@media (min-width: 768px){.sm-dox ul{position:absolute;width:12em}.sm-dox li{float:left}.sm-dox.sm-rtl li{float:right}.sm-dox ul li,.sm-dox.sm-rtl ul li,.sm-dox.sm-vertical li{float:none}.sm-dox a{white-space:nowrap}.sm-dox ul a,.sm-dox.sm-vertical a{white-space:normal}.sm-dox .sm-nowrap>li>a,.sm-dox .sm-nowrap>li>:not(ul) a{white-space:nowrap}.sm-dox{padding:0 10px;background-image:var(--nav-gradient-image);line-height:36px}.sm-dox a span.sub-arrow{top:50%;margin-top:-2px;right:12px;width:0;height:0;border-width:4px;border-style:solid dashed dashed dashed;border-color:var(--nav-text-normal-color) transparent transparent transparent;background:transparent;border-radius:0}.sm-dox a,.sm-dox a:focus,.sm-dox a:active,.sm-dox a:hover,.sm-dox a.highlighted{padding:0px 12px;background-image:var(--nav-separator-image);background-repeat:no-repeat;background-position:right;border-radius:0 !important}.sm-dox a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox a:hover span.sub-arrow{border-color:var(--nav-text-hover-color) transparent transparent transparent}.sm-dox a.has-submenu{padding-right:24px}.sm-dox li{border-top:0}.sm-dox>li>ul:before,.sm-dox>li>ul:after{content:'';position:absolute;top:-18px;left:30px;width:0;height:0;overflow:hidden;border-width:9px;border-style:dashed dashed solid dashed;border-color:transparent transparent #bbb transparent}.sm-dox>li>ul:after{top:-16px;left:31px;border-width:8px;border-color:transparent transparent var(--nav-menu-background-color) transparent}.sm-dox ul{border:1px solid #bbb;padding:5px 0;background:var(--nav-menu-background-color);border-radius:5px !important;box-shadow:0 5px 9px rgba(0,0,0,0.2)}.sm-dox ul a span.sub-arrow{right:8px;top:50%;margin-top:-5px;border-width:5px;border-color:transparent transparent transparent var(--nav-menu-foreground-color);border-style:dashed dashed dashed solid}.sm-dox ul a,.sm-dox ul a:hover,.sm-dox ul a:focus,.sm-dox ul a:active,.sm-dox ul a.highlighted{color:var(--nav-menu-foreground-color);background-image:none;border:0 !important;color:var(--nav-menu-foreground-color);background-image:none}.sm-dox ul a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox ul a:hover span.sub-arrow{border-color:transparent transparent transparent var(--nav-text-hover-color)}.sm-dox span.scroll-up,.sm-dox span.scroll-down{position:absolute;display:none;visibility:hidden;overflow:hidden;background:var(--nav-menu-background-color);height:36px}.sm-dox span.scroll-up:hover,.sm-dox span.scroll-down:hover{background:#eee}.sm-dox span.scroll-up:hover span.scroll-up-arrow,.sm-dox span.scroll-up:hover span.scroll-down-arrow{border-color:transparent transparent #D23600 transparent}.sm-dox span.scroll-down:hover span.scroll-down-arrow{border-color:#D23600 transparent transparent transparent}.sm-dox span.scroll-up-arrow,.sm-dox span.scroll-down-arrow{position:absolute;top:0;left:50%;margin-left:-6px;width:0;height:0;overflow:hidden;border-width:6px;border-style:dashed dashed solid dashed;border-color:transparent transparent var(--nav-menu-foreground-color) transparent}.sm-dox span.scroll-down-arrow{top:8px;border-style:solid dashed dashed dashed;border-color:var(--nav-menu-foreground-color) transparent transparent transparent}.sm-dox.sm-rtl a.has-submenu{padding-right:12px;padding-left:24px}.sm-dox.sm-rtl a span.sub-arrow{right:auto;left:12px}.sm-dox.sm-rtl.sm-vertical a.has-submenu{padding:10px 20px}.sm-dox.sm-rtl.sm-vertical a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-rtl>li>ul:before{left:auto;right:30px}.sm-dox.sm-rtl>li>ul:after{left:auto;right:31px}.sm-dox.sm-rtl ul a.has-submenu{padding:10px 20px !important}.sm-dox.sm-rtl ul a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-vertical{padding:10px 0;border-radius:5px}.sm-dox.sm-vertical a{padding:10px 20px}.sm-dox.sm-vertical a:hover,.sm-dox.sm-vertical a:focus,.sm-dox.sm-vertical a:active,.sm-dox.sm-vertical a.highlighted{background:#fff}.sm-dox.sm-vertical a.disabled{background-image:var(--nav-gradient-image)}.sm-dox.sm-vertical a span.sub-arrow{right:8px;top:50%;margin-top:-5px;border-width:5px;border-style:dashed dashed dashed solid;border-color:transparent transparent transparent #555}.sm-dox.sm-vertical>li>ul:before,.sm-dox.sm-vertical>li>ul:after{display:none}.sm-dox.sm-vertical ul a{padding:10px 20px}.sm-dox.sm-vertical ul a:hover,.sm-dox.sm-vertical ul a:focus,.sm-dox.sm-vertical ul a:active,.sm-dox.sm-vertical ul a.highlighted{background:#eee}.sm-dox.sm-vertical ul a.disabled{background:var(--nav-menu-background-color)}} diff --git a/doc/html/test_2hello_8c.html b/doc/html/test_2hello_8c.html new file mode 100644 index 0000000..b47aa9d --- /dev/null +++ b/doc/html/test_2hello_8c.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: test/hello.c File Reference + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    hello.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <stdio.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <stddef.h>
    +#include <assert.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    minimal example filesystem using high-level API

    +

    Compile with:

    gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file COPYING.
    +
    */
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #include <fuse.h>
    +
    #include <stdio.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <stddef.h>
    +
    #include <assert.h>
    +
    +
    /*
    +
    * Command line options
    +
    *
    +
    * We can't set default values for the char* fields here because
    +
    * fuse_opt_parse would attempt to free() them when the user specifies
    +
    * different values on the command line.
    +
    */
    +
    static struct options {
    +
    const char *filename;
    +
    const char *contents;
    +
    int show_help;
    +
    } options;
    +
    +
    #define OPTION(t, p) \
    +
    { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--name=%s", filename),
    +
    OPTION("--contents=%s", contents),
    +
    OPTION("-h", show_help),
    +
    OPTION("--help", show_help),
    + +
    };
    +
    +
    static void *hello_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->kernel_cache = 1;
    +
    +
    /* Test setting flags the old way */
    +
    fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
    +
    fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ);
    +
    +
    return NULL;
    +
    }
    +
    +
    static int hello_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res = 0;
    +
    +
    memset(stbuf, 0, sizeof(struct stat));
    +
    if (strcmp(path, "/") == 0) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 2;
    +
    } else if (strcmp(path+1, options.filename) == 0) {
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = strlen(options.contents);
    +
    } else
    +
    res = -ENOENT;
    +
    +
    return res;
    +
    }
    +
    +
    static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    (void) offset;
    +
    (void) fi;
    +
    (void) flags;
    +
    +
    if (strcmp(path, "/") != 0)
    +
    return -ENOENT;
    +
    +
    filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    +
    return 0;
    +
    }
    +
    +
    static int hello_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    if (strcmp(path+1, options.filename) != 0)
    +
    return -ENOENT;
    +
    +
    if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    return -EACCES;
    +
    +
    return 0;
    +
    }
    +
    +
    static int hello_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    size_t len;
    +
    (void) fi;
    +
    if(strcmp(path+1, options.filename) != 0)
    +
    return -ENOENT;
    +
    +
    len = strlen(options.contents);
    +
    if (offset < len) {
    +
    if (offset + size > len)
    +
    size = len - offset;
    +
    memcpy(buf, options.contents + offset, size);
    +
    } else
    +
    size = 0;
    +
    +
    return size;
    +
    }
    +
    +
    static const struct fuse_operations hello_oper = {
    +
    .init = hello_init,
    +
    .getattr = hello_getattr,
    +
    .readdir = hello_readdir,
    +
    .open = hello_open,
    +
    .read = hello_read,
    +
    };
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --name=<s> Name of the \"hello\" file\n"
    +
    " (default: \"hello\")\n"
    +
    " --contents=<s> Contents \"hello\" file\n"
    +
    " (default \"Hello, World!\\n\")\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    int ret;
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    +
    /* Set defaults -- we have to use strdup so that
    +
    fuse_opt_parse can free the defaults if other
    +
    values are specified */
    +
    options.filename = strdup("hello");
    +
    options.contents = strdup("Hello World!\n");
    +
    +
    /* Parse options */
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    /* When --help is specified, first print our own file-system
    +
    specific help text, then signal fuse_main to show
    +
    additional help (by adding `--help` to the options again)
    +
    without usage: line (by setting argv[0] to the empty
    +
    string) */
    +
    if (options.show_help) {
    +
    show_help(argv[0]);
    +
    assert(fuse_opt_add_arg(&args, "--help") == 0);
    +
    args.argv[0][0] = '\0';
    +
    }
    +
    +
    ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
    + +
    return ret;
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    #define FUSE_CAP_ASYNC_READ
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    int32_t kernel_cache
    Definition fuse.h:245
    + + + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    + +
    unsigned long offset
    Definition fuse_opt.h:85
    +
    +

    Definition in file hello.c.

    +
    + + + + diff --git a/doc/html/test_2hello_8c_source.html b/doc/html/test_2hello_8c_source.html new file mode 100644 index 0000000..026b1df --- /dev/null +++ b/doc/html/test_2hello_8c_source.html @@ -0,0 +1,251 @@ + + + + + + + +libfuse: test/hello.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    hello.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4 *
    +
    5 * This program can be distributed under the terms of the GNU GPLv2.
    +
    6 * See the file COPYING.
    +
    7 */
    +
    8
    +
    21#define FUSE_USE_VERSION 31
    +
    22
    +
    23#include <fuse.h>
    +
    24#include <stdio.h>
    +
    25#include <string.h>
    +
    26#include <errno.h>
    +
    27#include <fcntl.h>
    +
    28#include <stddef.h>
    +
    29#include <assert.h>
    +
    30
    +
    31/*
    +
    32 * Command line options
    +
    33 *
    +
    34 * We can't set default values for the char* fields here because
    +
    35 * fuse_opt_parse would attempt to free() them when the user specifies
    +
    36 * different values on the command line.
    +
    37 */
    +
    38static struct options {
    +
    39 const char *filename;
    +
    40 const char *contents;
    +
    41 int show_help;
    +
    42} options;
    +
    43
    +
    44#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    45static const struct fuse_opt option_spec[] = {
    +
    46 OPTION("--name=%s", filename), OPTION("--contents=%s", contents),
    +
    47 OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END
    +
    48};
    +
    49
    +
    50static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
    +
    51{
    +
    52 (void)conn;
    +
    53 cfg->kernel_cache = 1;
    +
    54
    +
    55 /* Test setting flags the old way */
    + +
    57 conn->want &= ~FUSE_CAP_ASYNC_READ;
    +
    58
    +
    59 return NULL;
    +
    60}
    +
    61
    +
    62static int hello_getattr(const char *path, struct stat *stbuf,
    +
    63 struct fuse_file_info *fi)
    +
    64{
    +
    65 (void)fi;
    +
    66 int res = 0;
    +
    67
    +
    68 memset(stbuf, 0, sizeof(struct stat));
    +
    69 if (strcmp(path, "/") == 0) {
    +
    70 stbuf->st_mode = S_IFDIR | 0755;
    +
    71 stbuf->st_nlink = 2;
    +
    72 } else if (strcmp(path + 1, options.filename) == 0) {
    +
    73 stbuf->st_mode = S_IFREG | 0444;
    +
    74 stbuf->st_nlink = 1;
    +
    75 stbuf->st_size = strlen(options.contents);
    +
    76 } else
    +
    77 res = -ENOENT;
    +
    78
    +
    79 return res;
    +
    80}
    +
    81
    +
    82static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    83 off_t offset, struct fuse_file_info *fi,
    +
    84 enum fuse_readdir_flags flags)
    +
    85{
    +
    86 (void)offset;
    +
    87 (void)fi;
    +
    88 (void)flags;
    +
    89
    +
    90 if (strcmp(path, "/") != 0)
    +
    91 return -ENOENT;
    +
    92
    +
    93 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    94 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    95 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    96
    +
    97 return 0;
    +
    98}
    +
    99
    +
    100static int hello_open(const char *path, struct fuse_file_info *fi)
    +
    101{
    +
    102 if (strcmp(path + 1, options.filename) != 0)
    +
    103 return -ENOENT;
    +
    104
    +
    105 if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    106 return -EACCES;
    +
    107
    +
    108 return 0;
    +
    109}
    +
    110
    +
    111static int hello_read(const char *path, char *buf, size_t size, off_t offset,
    +
    112 struct fuse_file_info *fi)
    +
    113{
    +
    114 size_t len;
    +
    115 (void)fi;
    +
    116 if (strcmp(path + 1, options.filename) != 0)
    +
    117 return -ENOENT;
    +
    118
    +
    119 len = strlen(options.contents);
    +
    120 if (offset < len) {
    +
    121 if (offset + size > len)
    +
    122 size = len - offset;
    +
    123 memcpy(buf, options.contents + offset, size);
    +
    124 } else
    +
    125 size = 0;
    +
    126
    +
    127 return size;
    +
    128}
    +
    129
    +
    130static const struct fuse_operations hello_oper = {
    +
    131 .init = hello_init,
    +
    132 .getattr = hello_getattr,
    +
    133 .readdir = hello_readdir,
    +
    134 .open = hello_open,
    +
    135 .read = hello_read,
    +
    136};
    +
    137
    +
    138static void show_help(const char *progname)
    +
    139{
    +
    140 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    141 printf("File-system specific options:\n"
    +
    142 " --name=<s> Name of the \"hello\" file\n"
    +
    143 " (default: \"hello\")\n"
    +
    144 " --contents=<s> Contents \"hello\" file\n"
    +
    145 " (default \"Hello, World!\\n\")\n"
    +
    146 "\n");
    +
    147}
    +
    148
    +
    149int main(int argc, char *argv[])
    +
    150{
    +
    151 int ret;
    +
    152 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    153
    +
    154 /* Set defaults -- we have to use strdup so that
    +
    155 * fuse_opt_parse can free the defaults if other
    +
    156 * values are specified
    +
    157 */
    +
    158 options.filename = strdup("hello");
    +
    159 options.contents = strdup("Hello World!\n");
    +
    160
    +
    161 /* Parse options */
    +
    162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    163 return 1;
    +
    164
    +
    165 /* When --help is specified, first print our own file-system
    +
    166 * specific help text, then signal fuse_main to show
    +
    167 * additional help (by adding `--help` to the options again)
    +
    168 * without usage: line (by setting argv[0] to the empty
    +
    169 * string)
    +
    170 */
    +
    171 if (options.show_help) {
    +
    172 show_help(argv[0]);
    +
    173 assert(fuse_opt_add_arg(&args, "--help") == 0);
    +
    174 args.argv[0][0] = '\0';
    +
    175 }
    +
    176
    +
    177 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
    +
    178 fuse_opt_free_args(&args);
    +
    179 return ret;
    +
    180}
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:87
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:68
    +
    fuse_readdir_flags
    Definition fuse.h:42
    +
    #define FUSE_CAP_ASYNC_READ
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    int32_t kernel_cache
    Definition fuse.h:245
    + + + + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    + +
    unsigned long offset
    Definition fuse_opt.h:85
    +
    + + + + diff --git a/doc/html/test_2readdir__inode_8c_source.html b/doc/html/test_2readdir__inode_8c_source.html new file mode 100644 index 0000000..bf625c6 --- /dev/null +++ b/doc/html/test_2readdir__inode_8c_source.html @@ -0,0 +1,117 @@ + + + + + + + +libfuse: test/readdir_inode.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    readdir_inode.c
    +
    +
    +
    1/*
    +
    2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
    +
    3 * Skips '.' and '..' because readdir is not required to return them and
    +
    4 * some of our examples don't. However if they are returned, their d_type
    +
    5 * should be valid.
    +
    6 */
    +
    7
    +
    8#include <stdio.h>
    +
    9#include <string.h>
    +
    10#include <sys/types.h>
    +
    11#include <dirent.h>
    +
    12#include <errno.h>
    +
    13
    +
    14int main(int argc, char* argv[])
    +
    15{
    +
    16 DIR* dirp;
    +
    17 struct dirent* dent;
    +
    18
    +
    19 if (argc != 2) {
    +
    20 fprintf(stderr, "Usage: readdir_inode dir\n");
    +
    21 return 1;
    +
    22 }
    +
    23
    +
    24 dirp = opendir(argv[1]);
    +
    25 if (dirp == NULL) {
    +
    26 perror("failed to open directory");
    +
    27 return 2;
    +
    28 }
    +
    29
    +
    30 errno = 0;
    +
    31 dent = readdir(dirp);
    +
    32 while (dent != NULL) {
    +
    33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
    +
    34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
    +
    35 (int)dent->d_type, dent->d_name);
    +
    36 if ((long long)dent->d_ino < 0)
    +
    37 fprintf(stderr,"%s : bad d_ino %llu\n",
    +
    38 dent->d_name, (unsigned long long)dent->d_ino);
    +
    39 if ((dent->d_type < 1) || (dent->d_type > 15))
    +
    40 fprintf(stderr,"%s : bad d_type %d\n",
    +
    41 dent->d_name, (int)dent->d_type);
    +
    42 } else {
    +
    43 if (dent->d_type != DT_DIR)
    +
    44 fprintf(stderr,"%s : bad d_type %d\n",
    +
    45 dent->d_name, (int)dent->d_type);
    +
    46 }
    +
    47 dent = readdir(dirp);
    +
    48 }
    +
    49 if (errno != 0) {
    +
    50 perror("failed to read directory entry");
    +
    51 return 3;
    +
    52 }
    +
    53
    +
    54 closedir(dirp);
    +
    55
    +
    56 return 0;
    +
    57}
    +
    + + + + diff --git a/doc/html/test_2release__unlink__race_8c_source.html b/doc/html/test_2release__unlink__race_8c_source.html new file mode 100644 index 0000000..ce7932f --- /dev/null +++ b/doc/html/test_2release__unlink__race_8c_source.html @@ -0,0 +1,183 @@ + + + + + + + +libfuse: test/release_unlink_race.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    release_unlink_race.c
    +
    +
    +
    1/*
    +
    2 This program can be distributed under the terms of the GNU GPLv2.
    +
    3 See the file COPYING.
    +
    4*/
    +
    5
    +
    6#define FUSE_USE_VERSION 31
    +
    7
    +
    8#define _GNU_SOURCE
    +
    9
    +
    10#include <fuse.h>
    +
    11
    +
    12#include <stdio.h>
    +
    13#include <stdlib.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16
    +
    17static void *xmp_init(struct fuse_conn_info *conn,
    +
    18 struct fuse_config *cfg)
    +
    19{
    +
    20 (void) conn;
    +
    21
    +
    22 cfg->use_ino = 1;
    +
    23 cfg->nullpath_ok = 1;
    +
    24 cfg->entry_timeout = 0;
    +
    25 cfg->attr_timeout = 0;
    +
    26 cfg->negative_timeout = 0;
    +
    27
    +
    28 return NULL;
    +
    29}
    +
    30
    +
    31static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    32 struct fuse_file_info *fi)
    +
    33{
    +
    34 int res;
    +
    35
    +
    36 (void) path;
    +
    37
    +
    38 if(fi)
    +
    39 res = fstat(fi->fh, stbuf);
    +
    40 else
    +
    41 res = lstat(path, stbuf);
    +
    42 if (res == -1)
    +
    43 return -errno;
    +
    44
    +
    45 return 0;
    +
    46}
    +
    47
    +
    48static int xmp_unlink(const char *path)
    +
    49{
    +
    50 int res;
    +
    51
    +
    52 res = unlink(path);
    +
    53 if (res == -1)
    +
    54 return -errno;
    +
    55
    +
    56 return 0;
    +
    57}
    +
    58
    +
    59static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    60{
    +
    61 int res;
    +
    62
    +
    63 if (flags)
    +
    64 return -EINVAL;
    +
    65
    +
    66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    67
    +
    68 res = rename(from, to);
    +
    69 if (res == -1)
    +
    70 return -errno;
    +
    71
    +
    72 return 0;
    +
    73}
    +
    74
    +
    75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    76{
    +
    77 int fd;
    +
    78
    +
    79 fd = open(path, fi->flags, mode);
    +
    80 if (fd == -1)
    +
    81 return -errno;
    +
    82
    +
    83 fi->fh = fd;
    +
    84 return 0;
    +
    85}
    +
    86
    +
    87static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    88{
    +
    89 (void) path;
    +
    90
    +
    91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    92
    +
    93 close(fi->fh);
    +
    94
    +
    95 return 0;
    +
    96}
    +
    97
    +
    98static const struct fuse_operations xmp_oper = {
    +
    99 .init = xmp_init,
    +
    100 .getattr = xmp_getattr,
    +
    101 .unlink = xmp_unlink,
    +
    102 .rename = xmp_rename,
    +
    103 .create = xmp_create,
    +
    104 .release = xmp_release,
    +
    105};
    +
    106
    +
    107int main(int argc, char *argv[])
    +
    108{
    +
    109 umask(0);
    +
    110 return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    111}
    + +
    int32_t nullpath_ok
    Definition fuse.h:273
    +
    int32_t use_ino
    Definition fuse.h:198
    +
    double entry_timeout
    Definition fuse.h:127
    +
    double negative_timeout
    Definition fuse.h:137
    +
    double attr_timeout
    Definition fuse.h:143
    + + + + + +
    void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
    Definition fuse.h:641
    +
    + + + + diff --git a/doc/html/test_2stracedecode_8c_source.html b/doc/html/test_2stracedecode_8c_source.html new file mode 100644 index 0000000..8f898ca --- /dev/null +++ b/doc/html/test_2stracedecode_8c_source.html @@ -0,0 +1,259 @@ + + + + + + + +libfuse: test/stracedecode.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    stracedecode.c
    +
    +
    +
    1#include <stdio.h>
    +
    2#include <string.h>
    +
    3#include "fuse_kernel.h"
    +
    4
    +
    5static struct {
    +
    6 const char *name;
    +
    7} fuse_ll_ops[] = {
    +
    8 [FUSE_LOOKUP] = { "LOOKUP" },
    +
    9 [FUSE_FORGET] = { "FORGET" },
    +
    10 [FUSE_GETATTR] = { "GETATTR" },
    +
    11 [FUSE_SETATTR] = { "SETATTR" },
    +
    12 [FUSE_READLINK] = { "READLINK" },
    +
    13 [FUSE_SYMLINK] = { "SYMLINK" },
    +
    14 [FUSE_MKNOD] = { "MKNOD" },
    +
    15 [FUSE_MKDIR] = { "MKDIR" },
    +
    16 [FUSE_UNLINK] = { "UNLINK" },
    +
    17 [FUSE_RMDIR] = { "RMDIR" },
    +
    18 [FUSE_RENAME] = { "RENAME" },
    +
    19 [FUSE_LINK] = { "LINK" },
    +
    20 [FUSE_OPEN] = { "OPEN" },
    +
    21 [FUSE_READ] = { "READ" },
    +
    22 [FUSE_WRITE] = { "WRITE" },
    +
    23 [FUSE_STATFS] = { "STATFS" },
    +
    24 [FUSE_RELEASE] = { "RELEASE" },
    +
    25 [FUSE_FSYNC] = { "FSYNC" },
    +
    26 [FUSE_SETXATTR] = { "SETXATTR" },
    +
    27 [FUSE_GETXATTR] = { "GETXATTR" },
    +
    28 [FUSE_LISTXATTR] = { "LISTXATTR" },
    +
    29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
    +
    30 [FUSE_FLUSH] = { "FLUSH" },
    +
    31 [FUSE_INIT] = { "INIT" },
    +
    32 [FUSE_OPENDIR] = { "OPENDIR" },
    +
    33 [FUSE_READDIR] = { "READDIR" },
    +
    34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
    +
    35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
    +
    36 [FUSE_GETLK] = { "GETLK" },
    +
    37 [FUSE_SETLK] = { "SETLK" },
    +
    38 [FUSE_SETLKW] = { "SETLKW" },
    +
    39 [FUSE_ACCESS] = { "ACCESS" },
    +
    40 [FUSE_CREATE] = { "CREATE" },
    +
    41 [FUSE_TMPFILE] = { "TMPFILE" },
    +
    42 [FUSE_INTERRUPT] = { "INTERRUPT" },
    +
    43 [FUSE_BMAP] = { "BMAP" },
    +
    44 [FUSE_DESTROY] = { "DESTROY" },
    +
    45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
    +
    46};
    +
    47
    +
    48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
    +
    49
    +
    50static const char *opname(enum fuse_opcode opcode)
    +
    51{
    +
    52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
    +
    53 return "???";
    +
    54 else
    +
    55 return fuse_ll_ops[opcode].name;
    +
    56}
    +
    57
    +
    58
    +
    59static void process_buf(int dir, char *buf, int len)
    +
    60{
    +
    61 static unsigned long long prevuniq = -1;
    +
    62 static int prevopcode;
    +
    63
    +
    64 if (!dir) {
    +
    65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
    +
    66 buf += sizeof(struct fuse_in_header);
    +
    67
    +
    68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
    +
    69 (unsigned long long) in->unique,
    +
    70 opname((enum fuse_opcode) in->opcode), in->opcode,
    +
    71 (unsigned long) in->nodeid, in->len, len);
    +
    72
    +
    73 switch (in->opcode) {
    +
    74 case FUSE_READ: {
    +
    75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
    +
    76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
    +
    77 arg->fh, arg->offset, arg->size, arg->read_flags,
    +
    78 arg->lock_owner, arg->flags);
    +
    79 break;
    +
    80 }
    +
    81 case FUSE_WRITE: {
    +
    82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
    +
    83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
    +
    84 arg->fh, arg->offset, arg->size, arg->write_flags,
    +
    85 arg->lock_owner, arg->flags);
    +
    86 break;
    +
    87 }
    +
    88 }
    +
    89 prevuniq = in->unique;
    +
    90 prevopcode = in->opcode;
    +
    91 } else {
    +
    92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
    +
    93 buf += sizeof(struct fuse_out_header);
    +
    94
    +
    95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
    +
    96 (unsigned long long) out->unique, out->error,
    +
    97 strerror(-out->error), out->len, len);
    +
    98
    +
    99 if (out->unique == prevuniq) {
    +
    100 switch (prevopcode) {
    +
    101 case FUSE_GETATTR: {
    +
    102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
    +
    103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    104 arg->attr_valid, arg->attr_valid_nsec,
    +
    105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    106 break;
    +
    107 }
    +
    108 case FUSE_LOOKUP: {
    +
    109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
    +
    110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
    +
    112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    113 break;
    +
    114 }
    +
    115 }
    +
    116 }
    +
    117 }
    +
    118
    +
    119}
    +
    120
    +
    121int main(void)
    +
    122{
    +
    123 FILE *in = stdin;
    +
    124 while (1) {
    +
    125 int dir;
    +
    126 int res;
    +
    127 char buf[1048576];
    +
    128 unsigned len = 0;
    +
    129
    +
    130 memset(buf, 0, sizeof(buf));
    +
    131 while (1) {
    +
    132 char str[32];
    +
    133
    +
    134 res = fscanf(in, "%30s", str);
    +
    135 if (res != 1 && feof(in))
    +
    136 return 0;
    +
    137
    +
    138 if (res == 0)
    +
    139 continue;
    +
    140
    +
    141 if (strncmp(str, "read(", 5) == 0) {
    +
    142 dir = 0;
    +
    143 break;
    +
    144 } else if (strncmp(str, "writev(", 7) == 0) {
    +
    145 dir = 1;
    +
    146 break;
    +
    147 }
    +
    148 }
    +
    149
    +
    150 while (1) {
    +
    151 int c = getc(in);
    +
    152 if (c == '"') {
    +
    153 while (1) {
    +
    154 int val;
    +
    155
    +
    156 c = getc(in);
    +
    157 if (c == EOF) {
    +
    158 fprintf(stderr, "eof in string\n");
    +
    159 break;
    +
    160 }
    +
    161 if (c == '\n') {
    +
    162 fprintf(stderr, "eol in string\n");
    +
    163 break;
    +
    164 }
    +
    165 if (c == '"')
    +
    166 break;
    +
    167 if (c != '\\') {
    +
    168 val = c;
    +
    169 } else {
    +
    170 c = getc(in);
    +
    171 switch (c) {
    +
    172 case 'n': val = '\n'; break;
    +
    173 case 'r': val = '\r'; break;
    +
    174 case 't': val = '\t'; break;
    +
    175 case '"': val = '"'; break;
    +
    176 case '\\': val = '\\'; break;
    +
    177 case 'x':
    +
    178 res = scanf("%x", &val);
    +
    179 if (res != 1) {
    +
    180 fprintf(stderr, "parse error\n");
    +
    181 continue;
    +
    182 }
    +
    183 break;
    +
    184 default:
    +
    185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
    +
    186 continue;
    +
    187 }
    +
    188 }
    +
    189 buf[len++] = val;
    +
    190 }
    +
    191 }
    +
    192 if (c == '\n')
    +
    193 break;
    +
    194 }
    +
    195 process_buf(dir, buf, len);
    +
    196 memset(buf, 0, len);
    +
    197 len = 0;
    +
    198 }
    +
    199}
    +
    + + + + diff --git a/doc/html/test_2test__setattr_8c_source.html b/doc/html/test_2test__setattr_8c_source.html new file mode 100644 index 0000000..25ccc11 --- /dev/null +++ b/doc/html/test_2test__setattr_8c_source.html @@ -0,0 +1,272 @@ + + + + + + + +libfuse: test/test_setattr.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_setattr.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    9
    +
    10#define FUSE_USE_VERSION 30
    +
    11
    +
    12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    13#include <fuse.h>
    +
    14
    +
    15#include <fuse_config.h>
    +
    16#include <fuse_lowlevel.h>
    +
    17#include <stdio.h>
    +
    18#include <stdlib.h>
    +
    19#include <string.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <assert.h>
    +
    23#include <stddef.h>
    +
    24#include <unistd.h>
    +
    25#include <pthread.h>
    +
    26
    +
    27#ifndef __linux__
    +
    28#include <limits.h>
    +
    29#else
    +
    30#include <linux/limits.h>
    +
    31#endif
    +
    32
    +
    33#define FILE_INO 2
    +
    34#define FILE_NAME "truncate_me"
    +
    35
    +
    36static int got_fh;
    +
    37static mode_t file_mode = S_IFREG | 0644;
    +
    38
    +
    39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    40 stbuf->st_ino = ino;
    +
    41 if (ino == FUSE_ROOT_ID) {
    +
    42 stbuf->st_mode = S_IFDIR | 0755;
    +
    43 stbuf->st_nlink = 1;
    +
    44 }
    +
    45
    +
    46 else if (ino == FILE_INO) {
    +
    47 stbuf->st_mode = file_mode;
    +
    48 stbuf->st_nlink = 1;
    +
    49 stbuf->st_size = 0;
    +
    50 }
    +
    51
    +
    52 else
    +
    53 return -1;
    +
    54
    +
    55 return 0;
    +
    56}
    +
    57
    +
    58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    59 const char *name) {
    +
    60 struct fuse_entry_param e;
    +
    61 memset(&e, 0, sizeof(e));
    +
    62
    +
    63 if (parent != FUSE_ROOT_ID)
    +
    64 goto err_out;
    +
    65 else if (strcmp(name, FILE_NAME) == 0)
    +
    66 e.ino = FILE_INO;
    +
    67 else
    +
    68 goto err_out;
    +
    69
    +
    70 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    71 goto err_out;
    +
    72 fuse_reply_entry(req, &e);
    +
    73 return;
    +
    74
    +
    75err_out:
    +
    76 fuse_reply_err(req, ENOENT);
    +
    77}
    +
    78
    +
    79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    80 struct fuse_file_info *fi) {
    +
    81 struct stat stbuf;
    +
    82
    +
    83 (void) fi;
    +
    84
    +
    85 memset(&stbuf, 0, sizeof(stbuf));
    +
    86 if (tfs_stat(ino, &stbuf) != 0)
    +
    87 fuse_reply_err(req, ENOENT);
    +
    88 else
    +
    89 fuse_reply_attr(req, &stbuf, 5);
    +
    90}
    +
    91
    +
    92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    93 struct fuse_file_info *fi) {
    +
    94 if (ino == FUSE_ROOT_ID)
    +
    95 fuse_reply_err(req, EISDIR);
    +
    96 else {
    +
    97 assert(ino == FILE_INO);
    +
    98 fi->fh = FILE_INO;
    +
    99 fuse_reply_open(req, fi);
    +
    100 }
    +
    101}
    +
    102
    +
    103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    104 int to_set, struct fuse_file_info *fi) {
    +
    105 if(ino != FILE_INO ||
    +
    106 !(to_set & FUSE_SET_ATTR_MODE)) {
    +
    107 fuse_reply_err(req, EINVAL);
    +
    108 return;
    +
    109 }
    +
    110
    +
    111 if(fi == NULL)
    +
    112 fprintf(stderr, "setattr with fi == NULL\n");
    +
    113 else if (fi->fh != FILE_INO)
    +
    114 fprintf(stderr, "setattr with wrong fi->fh\n");
    +
    115 else {
    +
    116 fprintf(stderr, "setattr ok\n");
    +
    117 got_fh = 1;
    +
    118 file_mode = attr->st_mode;
    +
    119 }
    +
    120
    +
    121 tfs_getattr(req, ino, fi);
    +
    122}
    +
    123
    +
    124static struct fuse_lowlevel_ops tfs_oper = {
    +
    125 .lookup = tfs_lookup,
    +
    126 .getattr = tfs_getattr,
    +
    127 .open = tfs_open,
    +
    128 .setattr = tfs_setattr,
    +
    129};
    +
    130
    +
    131static void* run_fs(void *data) {
    +
    132 struct fuse_session *se = (struct fuse_session*) data;
    +
    133 assert(fuse_session_loop(se) == 0);
    +
    134 return NULL;
    +
    135}
    +
    136
    +
    137static void test_fs(char *mountpoint) {
    +
    138 char fname[PATH_MAX];
    +
    139 int fd;
    +
    140
    +
    141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
    +
    142 mountpoint) > 0);
    +
    143 fd = open(fname, O_WRONLY);
    +
    144 if (fd == -1) {
    +
    145 perror(fname);
    +
    146 assert(0);
    +
    147 }
    +
    148
    +
    149 assert(fchmod(fd, 0600) == 0);
    +
    150 close(fd);
    +
    151}
    +
    152
    +
    153int main(int argc, char *argv[]) {
    +
    154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    155 struct fuse_session *se;
    +
    156 struct fuse_cmdline_opts fuse_opts;
    +
    157 pthread_t fs_thread;
    +
    158
    +
    159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    160#ifndef __FreeBSD__
    +
    161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    162#endif
    +
    163 se = fuse_session_new(&args, &tfs_oper,
    +
    164 sizeof(tfs_oper), NULL);
    +
    165 assert (se != NULL);
    +
    166 assert(fuse_set_signal_handlers(se) == 0);
    +
    167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    168
    +
    169 /* Start file-system thread */
    +
    170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    171
    +
    172 /* Do test */
    +
    173 test_fs(fuse_opts.mountpoint);
    +
    174
    +
    175 /* Stop file system */
    +
    176 assert(pthread_cancel(fs_thread) == 0);
    +
    177
    + +
    179 assert(got_fh == 1);
    + + +
    182
    +
    183 printf("Test completed successfully.\n");
    +
    184 return 0;
    +
    185}
    +
    186
    +
    187
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_ROOT_ID
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    +
    fuse_ino_t ino
    + + + +
    void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    + + + + diff --git a/doc/html/test_2test__syscalls_8c_source.html b/doc/html/test_2test__syscalls_8c_source.html new file mode 100644 index 0000000..e8907fd --- /dev/null +++ b/doc/html/test_2test__syscalls_8c_source.html @@ -0,0 +1,2258 @@ + + + + + + + +libfuse: test/test_syscalls.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_syscalls.c
    +
    +
    +
    1#define _GNU_SOURCE
    +
    2#include "fuse_config.h"
    +
    3
    +
    4#include <stdio.h>
    +
    5#include <stdlib.h>
    +
    6#include <stdarg.h>
    +
    7#include <string.h>
    +
    8#include <unistd.h>
    +
    9#include <fcntl.h>
    +
    10#include <dirent.h>
    +
    11#include <utime.h>
    +
    12#include <errno.h>
    +
    13#include <assert.h>
    +
    14#include <sys/socket.h>
    +
    15#include <sys/types.h>
    +
    16#include <sys/stat.h>
    +
    17#include <sys/un.h>
    +
    18
    +
    19#ifndef ALLPERMS
    +
    20# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
    +
    21#endif
    +
    22
    +
    23
    +
    24static const char *basepath;
    +
    25static const char *basepath_r;
    +
    26static char testfile[1024];
    +
    27static char testfile2[1024];
    +
    28static char testdir[1024];
    +
    29static char testdir2[1024];
    +
    30static char testsock[1024];
    +
    31static char subfile[1280];
    +
    32
    +
    33static char testfile_r[1024];
    +
    34static char testfile2_r[1024];
    +
    35static char testdir_r[1024];
    +
    36static char testdir2_r[1024];
    +
    37static char subfile_r[1280];
    +
    38
    +
    39static char testname[256];
    +
    40static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
    +
    41static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
    +
    42static const char *testdir_files[] = { "f1", "f2", NULL};
    +
    43static long seekdir_offsets[4];
    +
    44static char zerodata[4096];
    +
    45static int testdatalen = sizeof(testdata) - 1;
    +
    46static int testdata2len = sizeof(testdata2) - 1;
    +
    47static unsigned int testnum = 0;
    +
    48static unsigned int select_test = 0;
    +
    49static unsigned int skip_test = 0;
    +
    50static unsigned int unlinked_test = 0;
    +
    51
    +
    52#define MAX_ENTRIES 1024
    +
    53#define MAX_TESTS 100
    +
    54
    +
    55static struct test {
    +
    56 int fd;
    +
    57 struct stat stat;
    +
    58} tests[MAX_TESTS];
    +
    59
    +
    60static void test_perror(const char *func, const char *msg)
    +
    61{
    +
    62 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
    +
    63 strerror(errno));
    +
    64}
    +
    65
    +
    66static void test_error(const char *func, const char *msg, ...)
    +
    67 __attribute__ ((format (printf, 2, 3)));
    +
    68
    +
    69static void __start_test(const char *fmt, ...)
    +
    70 __attribute__ ((format (printf, 1, 2)));
    +
    71
    +
    72static void test_error(const char *func, const char *msg, ...)
    +
    73{
    +
    74 va_list ap;
    +
    75 fprintf(stderr, "%s %s() - ", testname, func);
    +
    76 va_start(ap, msg);
    +
    77 vfprintf(stderr, msg, ap);
    +
    78 va_end(ap);
    +
    79 fprintf(stderr, "\n");
    +
    80}
    +
    81
    +
    82static int is_dot_or_dotdot(const char *name) {
    +
    83 return name[0] == '.' &&
    +
    84 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
    +
    85}
    +
    86
    +
    87static void success(void)
    +
    88{
    +
    89 fprintf(stderr, "%s OK\n", testname);
    +
    90}
    +
    91
    +
    92#define this_test (&tests[testnum-1])
    +
    93#define next_test (&tests[testnum])
    +
    94
    +
    95static void __start_test(const char *fmt, ...)
    +
    96{
    +
    97 unsigned int n;
    +
    98 va_list ap;
    +
    99 n = sprintf(testname, "%3i [", testnum);
    +
    100 va_start(ap, fmt);
    +
    101 n += vsprintf(testname + n, fmt, ap);
    +
    102 va_end(ap);
    +
    103 sprintf(testname + n, "]");
    +
    104 // Use dedicated testfile per test
    +
    105 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
    +
    106 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
    +
    107 if (testnum > MAX_TESTS) {
    +
    108 fprintf(stderr, "%s - too many tests\n", testname);
    +
    109 exit(1);
    +
    110 }
    +
    111 this_test->fd = -1;
    +
    112}
    +
    113
    +
    114#define start_test(msg, args...) { \
    +
    115 testnum++; \
    +
    116 if ((select_test && testnum != select_test) || \
    +
    117 (testnum == skip_test)) { \
    +
    118 return 0; \
    +
    119 } \
    +
    120 __start_test(msg, ##args); \
    +
    121}
    +
    122
    +
    123#define PERROR(msg) test_perror(__FUNCTION__, msg)
    +
    124#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
    +
    125
    +
    126#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
    +
    127
    +
    128static int st_check_size(struct stat *st, int len)
    +
    129{
    +
    130 if (st->st_size != len) {
    +
    131 ERROR("length %u instead of %u", (int) st->st_size,
    +
    132 (int) len);
    +
    133 return -1;
    +
    134 }
    +
    135 return 0;
    +
    136}
    +
    137
    +
    138static int check_size(const char *path, int len)
    +
    139{
    +
    140 struct stat stbuf;
    +
    141 int res = stat(path, &stbuf);
    +
    142 if (res == -1) {
    +
    143 PERROR("stat");
    +
    144 return -1;
    +
    145 }
    +
    146 return st_check_size(&stbuf, len);
    +
    147}
    +
    148
    +
    149static int check_testfile_size(const char *path, int len)
    +
    150{
    +
    151 this_test->stat.st_size = len;
    +
    152 return check_size(path, len);
    +
    153}
    +
    154
    +
    155static int st_check_type(struct stat *st, mode_t type)
    +
    156{
    +
    157 if ((st->st_mode & S_IFMT) != type) {
    +
    158 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
    +
    159 return -1;
    +
    160 }
    +
    161 return 0;
    +
    162}
    +
    163
    +
    164static int check_type(const char *path, mode_t type)
    +
    165{
    +
    166 struct stat stbuf;
    +
    167 int res = lstat(path, &stbuf);
    +
    168 if (res == -1) {
    +
    169 PERROR("lstat");
    +
    170 return -1;
    +
    171 }
    +
    172 return st_check_type(&stbuf, type);
    +
    173}
    +
    174
    +
    175static int st_check_mode(struct stat *st, mode_t mode)
    +
    176{
    +
    177 if ((st->st_mode & ALLPERMS) != mode) {
    +
    178 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
    +
    179 mode);
    +
    180 return -1;
    +
    181 }
    +
    182 return 0;
    +
    183}
    +
    184
    +
    185static int check_mode(const char *path, mode_t mode)
    +
    186{
    +
    187 struct stat stbuf;
    +
    188 int res = lstat(path, &stbuf);
    +
    189 if (res == -1) {
    +
    190 PERROR("lstat");
    +
    191 return -1;
    +
    192 }
    +
    193 return st_check_mode(&stbuf, mode);
    +
    194}
    +
    195
    +
    196static int check_testfile_mode(const char *path, mode_t mode)
    +
    197{
    +
    198 this_test->stat.st_mode &= ~ALLPERMS;
    +
    199 this_test->stat.st_mode |= mode;
    +
    200 return check_mode(path, mode);
    +
    201}
    +
    202
    +
    203static int check_times(const char *path, time_t atime, time_t mtime)
    +
    204{
    +
    205 int err = 0;
    +
    206 struct stat stbuf;
    +
    207 int res = lstat(path, &stbuf);
    +
    208 if (res == -1) {
    +
    209 PERROR("lstat");
    +
    210 return -1;
    +
    211 }
    +
    212 if (stbuf.st_atime != atime) {
    +
    213 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    214 err--;
    +
    215 }
    +
    216 if (stbuf.st_mtime != mtime) {
    +
    217 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    218 err--;
    +
    219 }
    +
    220 if (err)
    +
    221 return -1;
    +
    222
    +
    223 return 0;
    +
    224}
    +
    225
    +
    226#if 0
    +
    227static int fcheck_times(int fd, time_t atime, time_t mtime)
    +
    228{
    +
    229 int err = 0;
    +
    230 struct stat stbuf;
    +
    231 int res = fstat(fd, &stbuf);
    +
    232 if (res == -1) {
    +
    233 PERROR("fstat");
    +
    234 return -1;
    +
    235 }
    +
    236 if (stbuf.st_atime != atime) {
    +
    237 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    238 err--;
    +
    239 }
    +
    240 if (stbuf.st_mtime != mtime) {
    +
    241 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    242 err--;
    +
    243 }
    +
    244 if (err)
    +
    245 return -1;
    +
    246
    +
    247 return 0;
    +
    248}
    +
    249#endif
    +
    250
    +
    251static int st_check_nlink(struct stat *st, nlink_t nlink)
    +
    252{
    +
    253 if (st->st_nlink != nlink) {
    +
    254 ERROR("nlink %li instead of %li", (long) st->st_nlink,
    +
    255 (long) nlink);
    +
    256 return -1;
    +
    257 }
    +
    258 return 0;
    +
    259}
    +
    260
    +
    261static int check_nlink(const char *path, nlink_t nlink)
    +
    262{
    +
    263 struct stat stbuf;
    +
    264 int res = lstat(path, &stbuf);
    +
    265 if (res == -1) {
    +
    266 PERROR("lstat");
    +
    267 return -1;
    +
    268 }
    +
    269 return st_check_nlink(&stbuf, nlink);
    +
    270}
    +
    271
    +
    272static int fcheck_stat(int fd, int flags, struct stat *st)
    +
    273{
    +
    274 struct stat stbuf;
    +
    275 int res = fstat(fd, &stbuf);
    +
    276 if (res == -1) {
    +
    277 if (flags & O_PATH) {
    +
    278 // With O_PATH fd, the server does not have to keep
    +
    279 // the inode alive so FUSE inode may be stale or bad
    +
    280 if (errno == ESTALE || errno == EIO ||
    +
    281 errno == ENOENT || errno == EBADF)
    +
    282 return 0;
    +
    283 }
    +
    284 PERROR("fstat");
    +
    285 return -1;
    +
    286 }
    +
    287
    +
    288 int err = 0;
    +
    289 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
    +
    290 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
    +
    291 err += st_check_size(&stbuf, st->st_size);
    +
    292 err += st_check_nlink(&stbuf, st->st_nlink);
    +
    293
    +
    294 return err;
    +
    295}
    +
    296
    +
    297static int check_nonexist(const char *path)
    +
    298{
    +
    299 struct stat stbuf;
    +
    300 int res = lstat(path, &stbuf);
    +
    301 if (res == 0) {
    +
    302 ERROR("file should not exist");
    +
    303 return -1;
    +
    304 }
    +
    305 if (errno != ENOENT) {
    +
    306 ERROR("file should not exist: %s", strerror(errno));
    +
    307 return -1;
    +
    308 }
    +
    309 return 0;
    +
    310}
    +
    311
    +
    312static int check_buffer(const char *buf, const char *data, unsigned len)
    +
    313{
    +
    314 if (memcmp(buf, data, len) != 0) {
    +
    315 ERROR("data mismatch");
    +
    316 return -1;
    +
    317 }
    +
    318 return 0;
    +
    319}
    +
    320
    +
    321static int check_data(const char *path, const char *data, int offset,
    +
    322 unsigned len)
    +
    323{
    +
    324 char buf[4096];
    +
    325 int res;
    +
    326 int fd = open(path, O_RDONLY);
    +
    327 if (fd == -1) {
    +
    328 PERROR("open");
    +
    329 return -1;
    +
    330 }
    +
    331 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    332 PERROR("lseek");
    +
    333 close(fd);
    +
    334 return -1;
    +
    335 }
    +
    336 while (len) {
    +
    337 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    338 res = read(fd, buf, rdlen);
    +
    339 if (res == -1) {
    +
    340 PERROR("read");
    +
    341 close(fd);
    +
    342 return -1;
    +
    343 }
    +
    344 if (res != rdlen) {
    +
    345 ERROR("short read: %u instead of %u", res, rdlen);
    +
    346 close(fd);
    +
    347 return -1;
    +
    348 }
    +
    349 if (check_buffer(buf, data, rdlen) != 0) {
    +
    350 close(fd);
    +
    351 return -1;
    +
    352 }
    +
    353 data += rdlen;
    +
    354 len -= rdlen;
    +
    355 }
    +
    356 res = close(fd);
    +
    357 if (res == -1) {
    +
    358 PERROR("close");
    +
    359 return -1;
    +
    360 }
    +
    361 return 0;
    +
    362}
    +
    363
    +
    364static int fcheck_data(int fd, const char *data, int offset,
    +
    365 unsigned len)
    +
    366{
    +
    367 char buf[4096];
    +
    368 int res;
    +
    369 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    370 PERROR("lseek");
    +
    371 return -1;
    +
    372 }
    +
    373 while (len) {
    +
    374 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    375 res = read(fd, buf, rdlen);
    +
    376 if (res == -1) {
    +
    377 PERROR("read");
    +
    378 return -1;
    +
    379 }
    +
    380 if (res != rdlen) {
    +
    381 ERROR("short read: %u instead of %u", res, rdlen);
    +
    382 return -1;
    +
    383 }
    +
    384 if (check_buffer(buf, data, rdlen) != 0) {
    +
    385 return -1;
    +
    386 }
    +
    387 data += rdlen;
    +
    388 len -= rdlen;
    +
    389 }
    +
    390 return 0;
    +
    391}
    +
    392
    +
    393static int check_dir_contents(const char *path, const char **contents)
    +
    394{
    +
    395 int i;
    +
    396 int res;
    +
    397 int err = 0;
    +
    398 int found[MAX_ENTRIES];
    +
    399 const char *cont[MAX_ENTRIES];
    +
    400 DIR *dp;
    +
    401
    +
    402 for (i = 0; contents[i]; i++) {
    +
    403 assert(i < MAX_ENTRIES - 3);
    +
    404 found[i] = 0;
    +
    405 cont[i] = contents[i];
    +
    406 }
    +
    407 cont[i] = NULL;
    +
    408
    +
    409 dp = opendir(path);
    +
    410 if (dp == NULL) {
    +
    411 PERROR("opendir");
    +
    412 return -1;
    +
    413 }
    +
    414 memset(found, 0, sizeof(found));
    +
    415 while(1) {
    +
    416 struct dirent *de;
    +
    417 errno = 0;
    +
    418 de = readdir(dp);
    +
    419 if (de == NULL) {
    +
    420 if (errno) {
    +
    421 PERROR("readdir");
    +
    422 closedir(dp);
    +
    423 return -1;
    +
    424 }
    +
    425 break;
    +
    426 }
    +
    427 if (is_dot_or_dotdot(de->d_name))
    +
    428 continue;
    +
    429 for (i = 0; cont[i] != NULL; i++) {
    +
    430 assert(i < MAX_ENTRIES);
    +
    431 if (strcmp(cont[i], de->d_name) == 0) {
    +
    432 if (found[i]) {
    +
    433 ERROR("duplicate entry <%s>",
    +
    434 de->d_name);
    +
    435 err--;
    +
    436 } else
    +
    437 found[i] = 1;
    +
    438 break;
    +
    439 }
    +
    440 }
    +
    441 if (!cont[i]) {
    +
    442 ERROR("unexpected entry <%s>", de->d_name);
    +
    443 err --;
    +
    444 }
    +
    445 }
    +
    446 for (i = 0; cont[i] != NULL; i++) {
    +
    447 if (!found[i]) {
    +
    448 ERROR("missing entry <%s>", cont[i]);
    +
    449 err--;
    +
    450 }
    +
    451 }
    +
    452 res = closedir(dp);
    +
    453 if (res == -1) {
    +
    454 PERROR("closedir");
    +
    455 return -1;
    +
    456 }
    +
    457 if (err)
    +
    458 return -1;
    +
    459
    +
    460 return 0;
    +
    461}
    +
    462
    +
    463static int create_file(const char *path, const char *data, int len)
    +
    464{
    +
    465 int res;
    +
    466 int fd;
    +
    467
    +
    468 unlink(path);
    +
    469 fd = creat(path, 0644);
    +
    470 if (fd == -1) {
    +
    471 PERROR("creat");
    +
    472 return -1;
    +
    473 }
    +
    474 if (len) {
    +
    475 res = write(fd, data, len);
    +
    476 if (res == -1) {
    +
    477 PERROR("write");
    +
    478 close(fd);
    +
    479 return -1;
    +
    480 }
    +
    481 if (res != len) {
    +
    482 ERROR("write is short: %u instead of %u", res, len);
    +
    483 close(fd);
    +
    484 return -1;
    +
    485 }
    +
    486 }
    +
    487 res = close(fd);
    +
    488 if (res == -1) {
    +
    489 PERROR("close");
    +
    490 return -1;
    +
    491 }
    +
    492 res = check_type(path, S_IFREG);
    +
    493 if (res == -1)
    +
    494 return -1;
    +
    495 res = check_mode(path, 0644);
    +
    496 if (res == -1)
    +
    497 return -1;
    +
    498 res = check_nlink(path, 1);
    +
    499 if (res == -1)
    +
    500 return -1;
    +
    501 res = check_size(path, len);
    +
    502 if (res == -1)
    +
    503 return -1;
    +
    504
    +
    505 if (len) {
    +
    506 res = check_data(path, data, 0, len);
    +
    507 if (res == -1)
    +
    508 return -1;
    +
    509 }
    +
    510
    +
    511 return 0;
    +
    512}
    +
    513
    +
    514static int create_path_fd(const char *path, const char *data, int len)
    +
    515{
    +
    516 int path_fd;
    +
    517 int res;
    +
    518
    +
    519 res = create_file(path, data, len);
    +
    520 if (res == -1)
    +
    521 return -1;
    +
    522
    +
    523 path_fd = open(path, O_PATH);
    +
    524 if (path_fd == -1)
    +
    525 PERROR("open(O_PATH)");
    +
    526
    +
    527 return path_fd;
    +
    528}
    +
    529
    +
    530// Can be called once per test
    +
    531static int create_testfile(const char *path, const char *data, int len)
    +
    532{
    +
    533 struct test *t = this_test;
    +
    534 struct stat *st = &t->stat;
    +
    535 int res, fd;
    +
    536
    +
    537 if (t->fd > 0) {
    +
    538 ERROR("testfile already created");
    +
    539 return -1;
    +
    540 }
    +
    541
    +
    542 fd = create_path_fd(path, data, len);
    +
    543 if (fd == -1)
    +
    544 return -1;
    +
    545
    +
    546 t->fd = fd;
    +
    547
    +
    548 res = fstat(fd, st);
    +
    549 if (res == -1) {
    +
    550 PERROR("fstat");
    +
    551 return -1;
    +
    552 }
    +
    553
    +
    554 return 0;
    +
    555}
    +
    556
    +
    557static int check_unlinked_testfile(int fd)
    +
    558{
    +
    559 struct stat *st = &this_test->stat;
    +
    560
    +
    561 st->st_nlink = 0;
    +
    562 return fcheck_stat(fd, O_PATH, st);
    +
    563}
    +
    564
    +
    565// Check recorded testfiles after all tests completed
    +
    566static int check_unlinked_testfiles(void)
    +
    567{
    +
    568 int fd;
    +
    569 int res, err = 0;
    +
    570 int num = testnum;
    +
    571
    +
    572 if (!unlinked_test)
    +
    573 return 0;
    +
    574
    +
    575 testnum = 0;
    +
    576 while (testnum < num) {
    +
    577 fd = next_test->fd;
    +
    578 start_test("check_unlinked_testfile");
    +
    579 if (fd == -1)
    +
    580 continue;
    +
    581
    +
    582 err += check_unlinked_testfile(fd);
    +
    583 res = close(fd);
    +
    584 if (res == -1) {
    +
    585 PERROR("close(test_fd)");
    +
    586 err--;
    +
    587 }
    +
    588 }
    +
    589
    +
    590 if (err) {
    +
    591 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
    +
    592 return 1;
    +
    593 }
    +
    594
    +
    595 return err;
    +
    596}
    +
    597
    +
    598static int cleanup_dir(const char *path, const char **dir_files, int quiet)
    +
    599{
    +
    600 int i;
    +
    601 int err = 0;
    +
    602
    +
    603 for (i = 0; dir_files[i]; i++) {
    +
    604 int res;
    +
    605 char fpath[1280];
    +
    606 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    607 res = unlink(fpath);
    +
    608 if (res == -1 && !quiet) {
    +
    609 PERROR("unlink");
    +
    610 err --;
    +
    611 }
    +
    612 }
    +
    613 if (err)
    +
    614 return -1;
    +
    615
    +
    616 return 0;
    +
    617}
    +
    618
    +
    619static int create_dir(const char *path, const char **dir_files)
    +
    620{
    +
    621 int res;
    +
    622 int i;
    +
    623
    +
    624 rmdir(path);
    +
    625 res = mkdir(path, 0755);
    +
    626 if (res == -1) {
    +
    627 PERROR("mkdir");
    +
    628 return -1;
    +
    629 }
    +
    630 res = check_type(path, S_IFDIR);
    +
    631 if (res == -1)
    +
    632 return -1;
    +
    633 res = check_mode(path, 0755);
    +
    634 if (res == -1)
    +
    635 return -1;
    +
    636
    +
    637 for (i = 0; dir_files[i]; i++) {
    +
    638 char fpath[1280];
    +
    639 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    640 res = create_file(fpath, "", 0);
    +
    641 if (res == -1) {
    +
    642 cleanup_dir(path, dir_files, 1);
    +
    643 return -1;
    +
    644 }
    +
    645 }
    +
    646 res = check_dir_contents(path, dir_files);
    +
    647 if (res == -1) {
    +
    648 cleanup_dir(path, dir_files, 1);
    +
    649 return -1;
    +
    650 }
    +
    651
    +
    652 return 0;
    +
    653}
    +
    654
    +
    655static int test_truncate(int len)
    +
    656{
    +
    657 const char *data = testdata;
    +
    658 int datalen = testdatalen;
    +
    659 int res;
    +
    660
    +
    661 start_test("truncate(%u)", (int) len);
    +
    662 res = create_testfile(testfile, data, datalen);
    +
    663 if (res == -1)
    +
    664 return -1;
    +
    665
    +
    666 res = truncate(testfile, len);
    +
    667 if (res == -1) {
    +
    668 PERROR("truncate");
    +
    669 return -1;
    +
    670 }
    +
    671 res = check_testfile_size(testfile, len);
    +
    672 if (res == -1)
    +
    673 return -1;
    +
    674
    +
    675 if (len > 0) {
    +
    676 if (len <= datalen) {
    +
    677 res = check_data(testfile, data, 0, len);
    +
    678 if (res == -1)
    +
    679 return -1;
    +
    680 } else {
    +
    681 res = check_data(testfile, data, 0, datalen);
    +
    682 if (res == -1)
    +
    683 return -1;
    +
    684 res = check_data(testfile, zerodata, datalen,
    +
    685 len - datalen);
    +
    686 if (res == -1)
    +
    687 return -1;
    +
    688 }
    +
    689 }
    +
    690 res = unlink(testfile);
    +
    691 if (res == -1) {
    +
    692 PERROR("unlink");
    +
    693 return -1;
    +
    694 }
    +
    695 res = check_nonexist(testfile);
    +
    696 if (res == -1)
    +
    697 return -1;
    +
    698
    +
    699 success();
    +
    700 return 0;
    +
    701}
    +
    702
    +
    703static int test_ftruncate(int len, int mode)
    +
    704{
    +
    705 const char *data = testdata;
    +
    706 int datalen = testdatalen;
    +
    707 int res;
    +
    708 int fd;
    +
    709
    +
    710 start_test("ftruncate(%u) mode: 0%03o", len, mode);
    +
    711 res = create_testfile(testfile, data, datalen);
    +
    712 if (res == -1)
    +
    713 return -1;
    +
    714
    +
    715 fd = open(testfile, O_WRONLY);
    +
    716 if (fd == -1) {
    +
    717 PERROR("open");
    +
    718 return -1;
    +
    719 }
    +
    720
    +
    721 res = fchmod(fd, mode);
    +
    722 if (res == -1) {
    +
    723 PERROR("fchmod");
    +
    724 close(fd);
    +
    725 return -1;
    +
    726 }
    +
    727 res = check_testfile_mode(testfile, mode);
    +
    728 if (res == -1) {
    +
    729 close(fd);
    +
    730 return -1;
    +
    731 }
    +
    732 res = ftruncate(fd, len);
    +
    733 if (res == -1) {
    +
    734 PERROR("ftruncate");
    +
    735 close(fd);
    +
    736 return -1;
    +
    737 }
    +
    738 close(fd);
    +
    739 res = check_testfile_size(testfile, len);
    +
    740 if (res == -1)
    +
    741 return -1;
    +
    742
    +
    743 if (len > 0) {
    +
    744 if (len <= datalen) {
    +
    745 res = check_data(testfile, data, 0, len);
    +
    746 if (res == -1)
    +
    747 return -1;
    +
    748 } else {
    +
    749 res = check_data(testfile, data, 0, datalen);
    +
    750 if (res == -1)
    +
    751 return -1;
    +
    752 res = check_data(testfile, zerodata, datalen,
    +
    753 len - datalen);
    +
    754 if (res == -1)
    +
    755 return -1;
    +
    756 }
    +
    757 }
    +
    758 res = unlink(testfile);
    +
    759 if (res == -1) {
    +
    760 PERROR("unlink");
    +
    761 return -1;
    +
    762 }
    +
    763 res = check_nonexist(testfile);
    +
    764 if (res == -1)
    +
    765 return -1;
    +
    766
    +
    767 success();
    +
    768 return 0;
    +
    769}
    +
    770
    +
    771static int test_seekdir(void)
    +
    772{
    +
    773 int i;
    +
    774 int res;
    +
    775 DIR *dp;
    +
    776 struct dirent *de = NULL;
    +
    777
    +
    778 start_test("seekdir");
    +
    779 res = create_dir(testdir, testdir_files);
    +
    780 if (res == -1)
    +
    781 return res;
    +
    782
    +
    783 dp = opendir(testdir);
    +
    784 if (dp == NULL) {
    +
    785 PERROR("opendir");
    +
    786 return -1;
    +
    787 }
    +
    788
    +
    789 /* Remember dir offsets */
    +
    790 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
    +
    791 seekdir_offsets[i] = telldir(dp);
    +
    792 errno = 0;
    +
    793 de = readdir(dp);
    +
    794 if (de == NULL) {
    +
    795 if (errno) {
    +
    796 PERROR("readdir");
    +
    797 goto fail;
    +
    798 }
    +
    799 break;
    +
    800 }
    +
    801 }
    +
    802
    +
    803 /* Walk until the end of directory */
    +
    804 while (de)
    +
    805 de = readdir(dp);
    +
    806
    +
    807 /* Start from the last valid dir offset and seek backwards */
    +
    808 for (i--; i >= 0; i--) {
    +
    809 seekdir(dp, seekdir_offsets[i]);
    +
    810 de = readdir(dp);
    +
    811 if (de == NULL) {
    +
    812 ERROR("Unexpected end of directory after seekdir()");
    +
    813 goto fail;
    +
    814 }
    +
    815 }
    +
    816
    +
    817 closedir(dp);
    +
    818 res = cleanup_dir(testdir, testdir_files, 0);
    +
    819 if (!res)
    +
    820 success();
    +
    821 return res;
    +
    822fail:
    +
    823 closedir(dp);
    +
    824 cleanup_dir(testdir, testdir_files, 1);
    +
    825 return -1;
    +
    826}
    +
    827
    +
    828#ifdef HAVE_COPY_FILE_RANGE
    +
    829static int test_copy_file_range(void)
    +
    830{
    +
    831 const char *data = testdata;
    +
    832 int datalen = testdatalen;
    +
    833 int err = 0;
    +
    834 int res;
    +
    835 int fd_in, fd_out;
    +
    836 off_t pos_in = 0, pos_out = 0;
    +
    837
    +
    838 start_test("copy_file_range");
    +
    839 unlink(testfile);
    +
    840 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
    +
    841 if (fd_in == -1) {
    +
    842 PERROR("creat");
    +
    843 return -1;
    +
    844 }
    +
    845 res = write(fd_in, data, datalen);
    +
    846 if (res == -1) {
    +
    847 PERROR("write");
    +
    848 close(fd_in);
    +
    849 return -1;
    +
    850 }
    +
    851 if (res != datalen) {
    +
    852 ERROR("write is short: %u instead of %u", res, datalen);
    +
    853 close(fd_in);
    +
    854 return -1;
    +
    855 }
    +
    856
    +
    857 unlink(testfile2);
    +
    858 fd_out = creat(testfile2, 0644);
    +
    859 if (fd_out == -1) {
    +
    860 PERROR("creat");
    +
    861 close(fd_in);
    +
    862 return -1;
    +
    863 }
    +
    864 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
    +
    865 if (res == -1) {
    +
    866 PERROR("copy_file_range");
    +
    867 close(fd_in);
    +
    868 close(fd_out);
    +
    869 return -1;
    +
    870 }
    +
    871 if (res != datalen) {
    +
    872 ERROR("copy is short: %u instead of %u", res, datalen);
    +
    873 close(fd_in);
    +
    874 close(fd_out);
    +
    875 return -1;
    +
    876 }
    +
    877
    +
    878 res = close(fd_in);
    +
    879 if (res == -1) {
    +
    880 PERROR("close");
    +
    881 close(fd_out);
    +
    882 return -1;
    +
    883 }
    +
    884 res = close(fd_out);
    +
    885 if (res == -1) {
    +
    886 PERROR("close");
    +
    887 return -1;
    +
    888 }
    +
    889
    +
    890 err = check_data(testfile2, data, 0, datalen);
    +
    891
    +
    892 res = unlink(testfile);
    +
    893 if (res == -1) {
    +
    894 PERROR("unlink");
    +
    895 return -1;
    +
    896 }
    +
    897 res = check_nonexist(testfile);
    +
    898 if (res == -1)
    +
    899 return -1;
    +
    900 if (err)
    +
    901 return -1;
    +
    902
    +
    903 res = unlink(testfile2);
    +
    904 if (res == -1) {
    +
    905 PERROR("unlink");
    +
    906 return -1;
    +
    907 }
    +
    908 res = check_nonexist(testfile2);
    +
    909 if (res == -1)
    +
    910 return -1;
    +
    911 if (err)
    +
    912 return -1;
    +
    913
    +
    914 success();
    +
    915 return 0;
    +
    916}
    +
    917#else
    +
    918static int test_copy_file_range(void)
    +
    919{
    +
    920 return 0;
    +
    921}
    +
    922#endif
    +
    923
    +
    924static int test_utime(void)
    +
    925{
    +
    926 struct utimbuf utm;
    +
    927 time_t atime = 987631200;
    +
    928 time_t mtime = 123116400;
    +
    929 int res;
    +
    930
    +
    931 start_test("utime");
    +
    932 res = create_testfile(testfile, NULL, 0);
    +
    933 if (res == -1)
    +
    934 return -1;
    +
    935
    +
    936 utm.actime = atime;
    +
    937 utm.modtime = mtime;
    +
    938 res = utime(testfile, &utm);
    +
    939 if (res == -1) {
    +
    940 PERROR("utime");
    +
    941 return -1;
    +
    942 }
    +
    943 res = check_times(testfile, atime, mtime);
    +
    944 if (res == -1) {
    +
    945 return -1;
    +
    946 }
    +
    947 res = unlink(testfile);
    +
    948 if (res == -1) {
    +
    949 PERROR("unlink");
    +
    950 return -1;
    +
    951 }
    +
    952 res = check_nonexist(testfile);
    +
    953 if (res == -1)
    +
    954 return -1;
    +
    955
    +
    956 success();
    +
    957 return 0;
    +
    958}
    +
    959
    +
    960static int test_create(void)
    +
    961{
    +
    962 const char *data = testdata;
    +
    963 int datalen = testdatalen;
    +
    964 int err = 0;
    +
    965 int res;
    +
    966 int fd;
    +
    967
    +
    968 start_test("create");
    +
    969 unlink(testfile);
    +
    970 fd = creat(testfile, 0644);
    +
    971 if (fd == -1) {
    +
    972 PERROR("creat");
    +
    973 return -1;
    +
    974 }
    +
    975 res = write(fd, data, datalen);
    +
    976 if (res == -1) {
    +
    977 PERROR("write");
    +
    978 close(fd);
    +
    979 return -1;
    +
    980 }
    +
    981 if (res != datalen) {
    +
    982 ERROR("write is short: %u instead of %u", res, datalen);
    +
    983 close(fd);
    +
    984 return -1;
    +
    985 }
    +
    986 res = close(fd);
    +
    987 if (res == -1) {
    +
    988 PERROR("close");
    +
    989 return -1;
    +
    990 }
    +
    991 res = check_type(testfile, S_IFREG);
    +
    992 if (res == -1)
    +
    993 return -1;
    +
    994 err += check_mode(testfile, 0644);
    +
    995 err += check_nlink(testfile, 1);
    +
    996 err += check_size(testfile, datalen);
    +
    997 err += check_data(testfile, data, 0, datalen);
    +
    998 res = unlink(testfile);
    +
    999 if (res == -1) {
    +
    1000 PERROR("unlink");
    +
    1001 return -1;
    +
    1002 }
    +
    1003 res = check_nonexist(testfile);
    +
    1004 if (res == -1)
    +
    1005 return -1;
    +
    1006 if (err)
    +
    1007 return -1;
    +
    1008
    +
    1009 success();
    +
    1010 return 0;
    +
    1011}
    +
    1012
    +
    1013static int test_create_unlink(void)
    +
    1014{
    +
    1015 const char *data = testdata;
    +
    1016 int datalen = testdatalen;
    +
    1017 int err = 0;
    +
    1018 int res;
    +
    1019 int fd;
    +
    1020
    +
    1021 start_test("create+unlink");
    +
    1022 unlink(testfile);
    +
    1023 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
    +
    1024 if (fd == -1) {
    +
    1025 PERROR("creat");
    +
    1026 return -1;
    +
    1027 }
    +
    1028 res = unlink(testfile);
    +
    1029 if (res == -1) {
    +
    1030 PERROR("unlink");
    +
    1031 close(fd);
    +
    1032 return -1;
    +
    1033 }
    +
    1034 res = check_nonexist(testfile);
    +
    1035 if (res == -1) {
    +
    1036 close(fd);
    +
    1037 return -1;
    +
    1038 }
    +
    1039 res = write(fd, data, datalen);
    +
    1040 if (res == -1) {
    +
    1041 PERROR("write");
    +
    1042 close(fd);
    +
    1043 return -1;
    +
    1044 }
    +
    1045 if (res != datalen) {
    +
    1046 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1047 close(fd);
    +
    1048 return -1;
    +
    1049 }
    +
    1050 struct stat st = {
    +
    1051 .st_mode = S_IFREG | 0644,
    +
    1052 .st_size = datalen,
    +
    1053 };
    +
    1054 err = fcheck_stat(fd, O_RDWR, &st);
    +
    1055 err += fcheck_data(fd, data, 0, datalen);
    +
    1056 res = close(fd);
    +
    1057 if (res == -1) {
    +
    1058 PERROR("close");
    +
    1059 err--;
    +
    1060 }
    +
    1061 if (err)
    +
    1062 return -1;
    +
    1063
    +
    1064 success();
    +
    1065 return 0;
    +
    1066}
    +
    1067
    +
    1068static int test_mknod(void)
    +
    1069{
    +
    1070 int err = 0;
    +
    1071 int res;
    +
    1072
    +
    1073 start_test("mknod");
    +
    1074 unlink(testfile);
    +
    1075 res = mknod(testfile, 0644, 0);
    +
    1076 if (res == -1) {
    +
    1077 PERROR("mknod");
    +
    1078 return -1;
    +
    1079 }
    +
    1080 res = check_type(testfile, S_IFREG);
    +
    1081 if (res == -1)
    +
    1082 return -1;
    +
    1083 err += check_mode(testfile, 0644);
    +
    1084 err += check_nlink(testfile, 1);
    +
    1085 err += check_size(testfile, 0);
    +
    1086 res = unlink(testfile);
    +
    1087 if (res == -1) {
    +
    1088 PERROR("unlink");
    +
    1089 return -1;
    +
    1090 }
    +
    1091 res = check_nonexist(testfile);
    +
    1092 if (res == -1)
    +
    1093 return -1;
    +
    1094 if (err)
    +
    1095 return -1;
    +
    1096
    +
    1097 success();
    +
    1098 return 0;
    +
    1099}
    +
    1100
    +
    1101#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
    +
    1102
    +
    1103static int do_test_open(int exist, int flags, const char *flags_str, int mode)
    +
    1104{
    +
    1105 char buf[4096];
    +
    1106 const char *data = testdata;
    +
    1107 int datalen = testdatalen;
    +
    1108 unsigned currlen = 0;
    +
    1109 int err = 0;
    +
    1110 int res;
    +
    1111 int fd;
    +
    1112 off_t off;
    +
    1113
    +
    1114 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
    +
    1115 unlink(testfile);
    +
    1116 if (exist) {
    +
    1117 res = create_file(testfile_r, testdata2, testdata2len);
    +
    1118 if (res == -1)
    +
    1119 return -1;
    +
    1120
    +
    1121 currlen = testdata2len;
    +
    1122 }
    +
    1123
    +
    1124 fd = open(testfile, flags, mode);
    +
    1125 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
    +
    1126 if (fd != -1) {
    +
    1127 ERROR("open should have failed");
    +
    1128 close(fd);
    +
    1129 return -1;
    +
    1130 } else if (errno == EEXIST)
    +
    1131 goto succ;
    +
    1132 }
    +
    1133 if (!(flags & O_CREAT) && !exist) {
    +
    1134 if (fd != -1) {
    +
    1135 ERROR("open should have failed");
    +
    1136 close(fd);
    +
    1137 return -1;
    +
    1138 } else if (errno == ENOENT)
    +
    1139 goto succ;
    +
    1140 }
    +
    1141 if (fd == -1) {
    +
    1142 PERROR("open");
    +
    1143 return -1;
    +
    1144 }
    +
    1145
    +
    1146 if (flags & O_TRUNC)
    +
    1147 currlen = 0;
    +
    1148
    +
    1149 err += check_type(testfile, S_IFREG);
    +
    1150 if (exist)
    +
    1151 err += check_mode(testfile, 0644);
    +
    1152 else
    +
    1153 err += check_mode(testfile, mode);
    +
    1154 err += check_nlink(testfile, 1);
    +
    1155 err += check_size(testfile, currlen);
    +
    1156 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
    +
    1157 err += check_data(testfile, testdata2, 0, testdata2len);
    +
    1158
    +
    1159 res = write(fd, data, datalen);
    +
    1160 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1161 if (res == -1) {
    +
    1162 PERROR("write");
    +
    1163 err --;
    +
    1164 } else if (res != datalen) {
    +
    1165 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1166 err --;
    +
    1167 } else {
    +
    1168 if (datalen > (int) currlen)
    +
    1169 currlen = datalen;
    +
    1170
    +
    1171 err += check_size(testfile, currlen);
    +
    1172
    +
    1173 if (mode & S_IRUSR) {
    +
    1174 err += check_data(testfile, data, 0, datalen);
    +
    1175 if (exist && !(flags & O_TRUNC) &&
    +
    1176 testdata2len > datalen)
    +
    1177 err += check_data(testfile,
    +
    1178 testdata2 + datalen,
    +
    1179 datalen,
    +
    1180 testdata2len - datalen);
    +
    1181 }
    +
    1182 }
    +
    1183 } else {
    +
    1184 if (res != -1) {
    +
    1185 ERROR("write should have failed");
    +
    1186 err --;
    +
    1187 } else if (errno != EBADF) {
    +
    1188 PERROR("write");
    +
    1189 err --;
    +
    1190 }
    +
    1191 }
    +
    1192 off = lseek(fd, SEEK_SET, 0);
    +
    1193 if (off == (off_t) -1) {
    +
    1194 PERROR("lseek");
    +
    1195 err--;
    +
    1196 } else if (off != 0) {
    +
    1197 ERROR("offset should have returned 0");
    +
    1198 err --;
    +
    1199 }
    +
    1200 res = read(fd, buf, sizeof(buf));
    +
    1201 if ((flags & O_ACCMODE) != O_WRONLY) {
    +
    1202 if (res == -1) {
    +
    1203 PERROR("read");
    +
    1204 err--;
    +
    1205 } else {
    +
    1206 int readsize =
    +
    1207 currlen < sizeof(buf) ? currlen : sizeof(buf);
    +
    1208 if (res != readsize) {
    +
    1209 ERROR("read is short: %i instead of %u",
    +
    1210 res, readsize);
    +
    1211 err--;
    +
    1212 } else {
    +
    1213 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1214 err += check_buffer(buf, data, datalen);
    +
    1215 if (exist && !(flags & O_TRUNC) &&
    +
    1216 testdata2len > datalen)
    +
    1217 err += check_buffer(buf + datalen,
    +
    1218 testdata2 + datalen,
    +
    1219 testdata2len - datalen);
    +
    1220 } else if (exist)
    +
    1221 err += check_buffer(buf, testdata2,
    +
    1222 testdata2len);
    +
    1223 }
    +
    1224 }
    +
    1225 } else {
    +
    1226 if (res != -1) {
    +
    1227 ERROR("read should have failed");
    +
    1228 err --;
    +
    1229 } else if (errno != EBADF) {
    +
    1230 PERROR("read");
    +
    1231 err --;
    +
    1232 }
    +
    1233 }
    +
    1234
    +
    1235 res = close(fd);
    +
    1236 if (res == -1) {
    +
    1237 PERROR("close");
    +
    1238 return -1;
    +
    1239 }
    +
    1240 res = unlink(testfile);
    +
    1241 if (res == -1) {
    +
    1242 PERROR("unlink");
    +
    1243 return -1;
    +
    1244 }
    +
    1245 res = check_nonexist(testfile);
    +
    1246 if (res == -1)
    +
    1247 return -1;
    +
    1248 res = check_nonexist(testfile_r);
    +
    1249 if (res == -1)
    +
    1250 return -1;
    +
    1251 if (err)
    +
    1252 return -1;
    +
    1253
    +
    1254succ:
    +
    1255 success();
    +
    1256 return 0;
    +
    1257}
    +
    1258
    +
    1259#define test_open_acc(flags, mode, err) \
    +
    1260 do_test_open_acc(flags, #flags, mode, err)
    +
    1261
    +
    1262static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
    +
    1263{
    +
    1264 const char *data = testdata;
    +
    1265 int datalen = testdatalen;
    +
    1266 int res;
    +
    1267 int fd;
    +
    1268
    +
    1269 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
    +
    1270 strerror(err));
    +
    1271 unlink(testfile);
    +
    1272 res = create_testfile(testfile, data, datalen);
    +
    1273 if (res == -1)
    +
    1274 return -1;
    +
    1275
    +
    1276 res = chmod(testfile, mode);
    +
    1277 if (res == -1) {
    +
    1278 PERROR("chmod");
    +
    1279 return -1;
    +
    1280 }
    +
    1281
    +
    1282 res = check_testfile_mode(testfile, mode);
    +
    1283 if (res == -1)
    +
    1284 return -1;
    +
    1285
    +
    1286 fd = open(testfile, flags);
    +
    1287 if (fd == -1) {
    +
    1288 if (err != errno) {
    +
    1289 PERROR("open");
    +
    1290 return -1;
    +
    1291 }
    +
    1292 } else {
    +
    1293 if (err) {
    +
    1294 ERROR("open should have failed");
    +
    1295 close(fd);
    +
    1296 return -1;
    +
    1297 }
    +
    1298 close(fd);
    +
    1299 }
    +
    1300
    +
    1301 res = unlink(testfile);
    +
    1302 if (res == -1) {
    +
    1303 PERROR("unlink");
    +
    1304 return -1;
    +
    1305 }
    +
    1306 res = check_nonexist(testfile);
    +
    1307 if (res == -1)
    +
    1308 return -1;
    +
    1309 res = check_nonexist(testfile_r);
    +
    1310 if (res == -1)
    +
    1311 return -1;
    +
    1312
    +
    1313 success();
    +
    1314 return 0;
    +
    1315}
    +
    1316
    +
    1317static int test_symlink(void)
    +
    1318{
    +
    1319 char buf[1024];
    +
    1320 const char *data = testdata;
    +
    1321 int datalen = testdatalen;
    +
    1322 int linklen = strlen(testfile);
    +
    1323 int err = 0;
    +
    1324 int res;
    +
    1325
    +
    1326 start_test("symlink");
    +
    1327 res = create_testfile(testfile, data, datalen);
    +
    1328 if (res == -1)
    +
    1329 return -1;
    +
    1330
    +
    1331 unlink(testfile2);
    +
    1332 res = symlink(testfile, testfile2);
    +
    1333 if (res == -1) {
    +
    1334 PERROR("symlink");
    +
    1335 return -1;
    +
    1336 }
    +
    1337 res = check_type(testfile2, S_IFLNK);
    +
    1338 if (res == -1)
    +
    1339 return -1;
    +
    1340 err += check_mode(testfile2, 0777);
    +
    1341 err += check_nlink(testfile2, 1);
    +
    1342 res = readlink(testfile2, buf, sizeof(buf));
    +
    1343 if (res == -1) {
    +
    1344 PERROR("readlink");
    +
    1345 err--;
    +
    1346 }
    +
    1347 if (res != linklen) {
    +
    1348 ERROR("short readlink: %u instead of %u", res, linklen);
    +
    1349 err--;
    +
    1350 }
    +
    1351 if (memcmp(buf, testfile, linklen) != 0) {
    +
    1352 ERROR("link mismatch");
    +
    1353 err--;
    +
    1354 }
    +
    1355 err += check_size(testfile2, datalen);
    +
    1356 err += check_data(testfile2, data, 0, datalen);
    +
    1357 res = unlink(testfile2);
    +
    1358 if (res == -1) {
    +
    1359 PERROR("unlink");
    +
    1360 return -1;
    +
    1361 }
    +
    1362 res = check_nonexist(testfile2);
    +
    1363 if (res == -1)
    +
    1364 return -1;
    +
    1365 if (err)
    +
    1366 return -1;
    +
    1367
    +
    1368 res = unlink(testfile);
    +
    1369 if (res == -1) {
    +
    1370 PERROR("unlink");
    +
    1371 return -1;
    +
    1372 }
    +
    1373 res = check_nonexist(testfile);
    +
    1374 if (res == -1)
    +
    1375 return -1;
    +
    1376
    +
    1377 success();
    +
    1378 return 0;
    +
    1379}
    +
    1380
    +
    1381static int test_link(void)
    +
    1382{
    +
    1383 const char *data = testdata;
    +
    1384 int datalen = testdatalen;
    +
    1385 int err = 0;
    +
    1386 int res;
    +
    1387
    +
    1388 start_test("link");
    +
    1389 res = create_testfile(testfile, data, datalen);
    +
    1390 if (res == -1)
    +
    1391 return -1;
    +
    1392
    +
    1393 unlink(testfile2);
    +
    1394 res = link(testfile, testfile2);
    +
    1395 if (res == -1) {
    +
    1396 PERROR("link");
    +
    1397 return -1;
    +
    1398 }
    +
    1399 res = check_type(testfile2, S_IFREG);
    +
    1400 if (res == -1)
    +
    1401 return -1;
    +
    1402 err += check_mode(testfile2, 0644);
    +
    1403 err += check_nlink(testfile2, 2);
    +
    1404 err += check_size(testfile2, datalen);
    +
    1405 err += check_data(testfile2, data, 0, datalen);
    +
    1406 res = unlink(testfile);
    +
    1407 if (res == -1) {
    +
    1408 PERROR("unlink");
    +
    1409 return -1;
    +
    1410 }
    +
    1411 res = check_nonexist(testfile);
    +
    1412 if (res == -1)
    +
    1413 return -1;
    +
    1414
    +
    1415 err += check_nlink(testfile2, 1);
    +
    1416 res = unlink(testfile2);
    +
    1417 if (res == -1) {
    +
    1418 PERROR("unlink");
    +
    1419 return -1;
    +
    1420 }
    +
    1421 res = check_nonexist(testfile2);
    +
    1422 if (res == -1)
    +
    1423 return -1;
    +
    1424 if (err)
    +
    1425 return -1;
    +
    1426
    +
    1427 success();
    +
    1428 return 0;
    +
    1429}
    +
    1430
    +
    1431static int test_link2(void)
    +
    1432{
    +
    1433 const char *data = testdata;
    +
    1434 int datalen = testdatalen;
    +
    1435 int err = 0;
    +
    1436 int res;
    +
    1437
    +
    1438 start_test("link-unlink-link");
    +
    1439 res = create_testfile(testfile, data, datalen);
    +
    1440 if (res == -1)
    +
    1441 return -1;
    +
    1442
    +
    1443 unlink(testfile2);
    +
    1444 res = link(testfile, testfile2);
    +
    1445 if (res == -1) {
    +
    1446 PERROR("link");
    +
    1447 return -1;
    +
    1448 }
    +
    1449 res = unlink(testfile);
    +
    1450 if (res == -1) {
    +
    1451 PERROR("unlink");
    +
    1452 return -1;
    +
    1453 }
    +
    1454 res = check_nonexist(testfile);
    +
    1455 if (res == -1)
    +
    1456 return -1;
    +
    1457 res = link(testfile2, testfile);
    +
    1458 if (res == -1) {
    +
    1459 PERROR("link");
    +
    1460 }
    +
    1461 res = check_type(testfile, S_IFREG);
    +
    1462 if (res == -1)
    +
    1463 return -1;
    +
    1464 err += check_mode(testfile, 0644);
    +
    1465 err += check_nlink(testfile, 2);
    +
    1466 err += check_size(testfile, datalen);
    +
    1467 err += check_data(testfile, data, 0, datalen);
    +
    1468
    +
    1469 res = unlink(testfile2);
    +
    1470 if (res == -1) {
    +
    1471 PERROR("unlink");
    +
    1472 return -1;
    +
    1473 }
    +
    1474 err += check_nlink(testfile, 1);
    +
    1475 res = unlink(testfile);
    +
    1476 if (res == -1) {
    +
    1477 PERROR("unlink");
    +
    1478 return -1;
    +
    1479 }
    +
    1480 res = check_nonexist(testfile);
    +
    1481 if (res == -1)
    +
    1482 return -1;
    +
    1483 if (err)
    +
    1484 return -1;
    +
    1485
    +
    1486 success();
    +
    1487 return 0;
    +
    1488}
    +
    1489
    +
    1490static int test_rename_file(void)
    +
    1491{
    +
    1492 const char *data = testdata;
    +
    1493 int datalen = testdatalen;
    +
    1494 int err = 0;
    +
    1495 int res;
    +
    1496
    +
    1497 start_test("rename file");
    +
    1498 res = create_testfile(testfile, data, datalen);
    +
    1499 if (res == -1)
    +
    1500 return -1;
    +
    1501
    +
    1502 unlink(testfile2);
    +
    1503 res = rename(testfile, testfile2);
    +
    1504 if (res == -1) {
    +
    1505 PERROR("rename");
    +
    1506 return -1;
    +
    1507 }
    +
    1508 res = check_nonexist(testfile);
    +
    1509 if (res == -1)
    +
    1510 return -1;
    +
    1511 res = check_type(testfile2, S_IFREG);
    +
    1512 if (res == -1)
    +
    1513 return -1;
    +
    1514 err += check_mode(testfile2, 0644);
    +
    1515 err += check_nlink(testfile2, 1);
    +
    1516 err += check_size(testfile2, datalen);
    +
    1517 err += check_data(testfile2, data, 0, datalen);
    +
    1518 res = unlink(testfile2);
    +
    1519 if (res == -1) {
    +
    1520 PERROR("unlink");
    +
    1521 return -1;
    +
    1522 }
    +
    1523 res = check_nonexist(testfile2);
    +
    1524 if (res == -1)
    +
    1525 return -1;
    +
    1526 if (err)
    +
    1527 return -1;
    +
    1528
    +
    1529 success();
    +
    1530 return 0;
    +
    1531}
    +
    1532
    +
    1533static int test_rename_dir(void)
    +
    1534{
    +
    1535 int err = 0;
    +
    1536 int res;
    +
    1537
    +
    1538 start_test("rename dir");
    +
    1539 res = create_dir(testdir, testdir_files);
    +
    1540 if (res == -1)
    +
    1541 return -1;
    +
    1542
    +
    1543 rmdir(testdir2);
    +
    1544 res = rename(testdir, testdir2);
    +
    1545 if (res == -1) {
    +
    1546 PERROR("rename");
    +
    1547 cleanup_dir(testdir, testdir_files, 1);
    +
    1548 return -1;
    +
    1549 }
    +
    1550 res = check_nonexist(testdir);
    +
    1551 if (res == -1) {
    +
    1552 cleanup_dir(testdir, testdir_files, 1);
    +
    1553 return -1;
    +
    1554 }
    +
    1555 res = check_type(testdir2, S_IFDIR);
    +
    1556 if (res == -1) {
    +
    1557 cleanup_dir(testdir2, testdir_files, 1);
    +
    1558 return -1;
    +
    1559 }
    +
    1560 err += check_mode(testdir2, 0755);
    +
    1561 err += check_dir_contents(testdir2, testdir_files);
    +
    1562 err += cleanup_dir(testdir2, testdir_files, 0);
    +
    1563 res = rmdir(testdir2);
    +
    1564 if (res == -1) {
    +
    1565 PERROR("rmdir");
    +
    1566 return -1;
    +
    1567 }
    +
    1568 res = check_nonexist(testdir2);
    +
    1569 if (res == -1)
    +
    1570 return -1;
    +
    1571 if (err)
    +
    1572 return -1;
    +
    1573
    +
    1574 success();
    +
    1575 return 0;
    +
    1576}
    +
    1577
    +
    1578static int test_rename_dir_loop(void)
    +
    1579{
    +
    1580#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
    +
    1581#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
    +
    1582
    +
    1583 char path[1280], path2[1280];
    +
    1584 int err = 0;
    +
    1585 int res;
    +
    1586
    +
    1587 start_test("rename dir loop");
    +
    1588
    +
    1589 res = create_dir(testdir, testdir_files);
    +
    1590 if (res == -1)
    +
    1591 return -1;
    +
    1592
    +
    1593 res = mkdir(PATH("a"), 0755);
    +
    1594 if (res == -1) {
    +
    1595 PERROR("mkdir");
    +
    1596 goto fail;
    +
    1597 }
    +
    1598
    +
    1599 res = rename(PATH("a"), PATH2("a"));
    +
    1600 if (res == -1) {
    +
    1601 PERROR("rename");
    +
    1602 goto fail;
    +
    1603 }
    +
    1604
    +
    1605 errno = 0;
    +
    1606 res = rename(PATH("a"), PATH2("a/b"));
    +
    1607 if (res == 0 || errno != EINVAL) {
    +
    1608 PERROR("rename");
    +
    1609 goto fail;
    +
    1610 }
    +
    1611
    +
    1612 res = mkdir(PATH("a/b"), 0755);
    +
    1613 if (res == -1) {
    +
    1614 PERROR("mkdir");
    +
    1615 goto fail;
    +
    1616 }
    +
    1617
    +
    1618 res = mkdir(PATH("a/b/c"), 0755);
    +
    1619 if (res == -1) {
    +
    1620 PERROR("mkdir");
    +
    1621 goto fail;
    +
    1622 }
    +
    1623
    +
    1624 errno = 0;
    +
    1625 res = rename(PATH("a"), PATH2("a/b/c"));
    +
    1626 if (res == 0 || errno != EINVAL) {
    +
    1627 PERROR("rename");
    +
    1628 goto fail;
    +
    1629 }
    +
    1630
    +
    1631 errno = 0;
    +
    1632 res = rename(PATH("a"), PATH2("a/b/c/a"));
    +
    1633 if (res == 0 || errno != EINVAL) {
    +
    1634 PERROR("rename");
    +
    1635 goto fail;
    +
    1636 }
    +
    1637
    +
    1638 errno = 0;
    +
    1639 res = rename(PATH("a/b/c"), PATH2("a"));
    +
    1640 if (res == 0 || errno != ENOTEMPTY) {
    +
    1641 PERROR("rename");
    +
    1642 goto fail;
    +
    1643 }
    +
    1644
    +
    1645 res = open(PATH("a/foo"), O_CREAT, 0644);
    +
    1646 if (res == -1) {
    +
    1647 PERROR("open");
    +
    1648 goto fail;
    +
    1649 }
    +
    1650 close(res);
    +
    1651
    +
    1652 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1653 if (res == -1) {
    +
    1654 PERROR("rename");
    +
    1655 goto fail;
    +
    1656 }
    +
    1657
    +
    1658 res = rename(PATH("a/bar"), PATH2("a/foo"));
    +
    1659 if (res == -1) {
    +
    1660 PERROR("rename");
    +
    1661 goto fail;
    +
    1662 }
    +
    1663
    +
    1664 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
    +
    1665 if (res == -1) {
    +
    1666 PERROR("rename");
    +
    1667 goto fail;
    +
    1668 }
    +
    1669
    +
    1670 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
    +
    1671 if (res == -1) {
    +
    1672 PERROR("rename");
    +
    1673 goto fail;
    +
    1674 }
    +
    1675
    +
    1676 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
    +
    1677 if (res == -1) {
    +
    1678 PERROR("rename");
    +
    1679 goto fail;
    +
    1680 }
    +
    1681
    +
    1682 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
    +
    1683 if (res == -1) {
    +
    1684 PERROR("rename");
    +
    1685 goto fail;
    +
    1686 }
    +
    1687
    +
    1688 res = open(PATH("a/bar"), O_CREAT, 0644);
    +
    1689 if (res == -1) {
    +
    1690 PERROR("open");
    +
    1691 goto fail;
    +
    1692 }
    +
    1693 close(res);
    +
    1694
    +
    1695 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1696 if (res == -1) {
    +
    1697 PERROR("rename");
    +
    1698 goto fail;
    +
    1699 }
    +
    1700
    +
    1701 unlink(PATH("a/bar"));
    +
    1702
    +
    1703 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1704 if (res == -1) {
    +
    1705 PERROR("rename");
    +
    1706 goto fail;
    +
    1707 }
    +
    1708
    +
    1709 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1710 if (res == -1) {
    +
    1711 PERROR("rename");
    +
    1712 goto fail;
    +
    1713 }
    +
    1714
    +
    1715 res = mkdir(PATH("a/d"), 0755);
    +
    1716 if (res == -1) {
    +
    1717 PERROR("mkdir");
    +
    1718 goto fail;
    +
    1719 }
    +
    1720
    +
    1721 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1722 if (res == -1) {
    +
    1723 PERROR("rename");
    +
    1724 goto fail;
    +
    1725 }
    +
    1726
    +
    1727 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1728 if (res == -1) {
    +
    1729 PERROR("rename");
    +
    1730 goto fail;
    +
    1731 }
    +
    1732
    +
    1733 res = mkdir(PATH("a/d"), 0755);
    +
    1734 if (res == -1) {
    +
    1735 PERROR("mkdir");
    +
    1736 goto fail;
    +
    1737 }
    +
    1738
    +
    1739 res = mkdir(PATH("a/d/e"), 0755);
    +
    1740 if (res == -1) {
    +
    1741 PERROR("mkdir");
    +
    1742 goto fail;
    +
    1743 }
    +
    1744
    +
    1745 errno = 0;
    +
    1746 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1747 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
    +
    1748 PERROR("rename");
    +
    1749 goto fail;
    +
    1750 }
    +
    1751
    +
    1752 rmdir(PATH("a/d/e"));
    +
    1753 rmdir(PATH("a/d"));
    +
    1754
    +
    1755 rmdir(PATH("a/b/c"));
    +
    1756 rmdir(PATH("a/b"));
    +
    1757 rmdir(PATH("a"));
    +
    1758
    +
    1759 err += cleanup_dir(testdir, testdir_files, 0);
    +
    1760 res = rmdir(testdir);
    +
    1761 if (res == -1) {
    +
    1762 PERROR("rmdir");
    +
    1763 goto fail;
    +
    1764 }
    +
    1765 res = check_nonexist(testdir);
    +
    1766 if (res == -1)
    +
    1767 return -1;
    +
    1768 if (err)
    +
    1769 return -1;
    +
    1770
    +
    1771 success();
    +
    1772 return 0;
    +
    1773
    +
    1774fail:
    +
    1775 unlink(PATH("a/bar"));
    +
    1776
    +
    1777 rmdir(PATH("a/d/e"));
    +
    1778 rmdir(PATH("a/d"));
    +
    1779
    +
    1780 rmdir(PATH("a/b/c"));
    +
    1781 rmdir(PATH("a/b"));
    +
    1782 rmdir(PATH("a"));
    +
    1783
    +
    1784 cleanup_dir(testdir, testdir_files, 1);
    +
    1785 rmdir(testdir);
    +
    1786
    +
    1787 return -1;
    +
    1788
    +
    1789#undef PATH2
    +
    1790#undef PATH
    +
    1791}
    +
    1792
    +
    1793static int test_mkfifo(void)
    +
    1794{
    +
    1795 int res;
    +
    1796 int err = 0;
    +
    1797
    +
    1798 start_test("mkfifo");
    +
    1799 unlink(testfile);
    +
    1800 res = mkfifo(testfile, 0644);
    +
    1801 if (res == -1) {
    +
    1802 PERROR("mkfifo");
    +
    1803 return -1;
    +
    1804 }
    +
    1805 res = check_type(testfile, S_IFIFO);
    +
    1806 if (res == -1)
    +
    1807 return -1;
    +
    1808 err += check_mode(testfile, 0644);
    +
    1809 err += check_nlink(testfile, 1);
    +
    1810 res = unlink(testfile);
    +
    1811 if (res == -1) {
    +
    1812 PERROR("unlink");
    +
    1813 return -1;
    +
    1814 }
    +
    1815 res = check_nonexist(testfile);
    +
    1816 if (res == -1)
    +
    1817 return -1;
    +
    1818 if (err)
    +
    1819 return -1;
    +
    1820
    +
    1821 success();
    +
    1822 return 0;
    +
    1823}
    +
    1824
    +
    1825static int test_mkdir(void)
    +
    1826{
    +
    1827 int res;
    +
    1828 int err = 0;
    +
    1829 const char *dir_contents[] = {NULL};
    +
    1830
    +
    1831 start_test("mkdir");
    +
    1832 rmdir(testdir);
    +
    1833 res = mkdir(testdir, 0755);
    +
    1834 if (res == -1) {
    +
    1835 PERROR("mkdir");
    +
    1836 return -1;
    +
    1837 }
    +
    1838 res = check_type(testdir, S_IFDIR);
    +
    1839 if (res == -1)
    +
    1840 return -1;
    +
    1841 err += check_mode(testdir, 0755);
    +
    1842 /* Some file systems (like btrfs) don't track link
    +
    1843 count for directories */
    +
    1844 //err += check_nlink(testdir, 2);
    +
    1845 err += check_dir_contents(testdir, dir_contents);
    +
    1846 res = rmdir(testdir);
    +
    1847 if (res == -1) {
    +
    1848 PERROR("rmdir");
    +
    1849 return -1;
    +
    1850 }
    +
    1851 res = check_nonexist(testdir);
    +
    1852 if (res == -1)
    +
    1853 return -1;
    +
    1854 if (err)
    +
    1855 return -1;
    +
    1856
    +
    1857 success();
    +
    1858 return 0;
    +
    1859}
    +
    1860
    +
    1861static int test_socket(void)
    +
    1862{
    +
    1863 struct sockaddr_un su;
    +
    1864 int fd;
    +
    1865 int res;
    +
    1866 int err = 0;
    +
    1867 const size_t test_sock_len = strlen(testsock) + 1;
    +
    1868
    +
    1869 start_test("socket");
    +
    1870 if (test_sock_len > sizeof(su.sun_path)) {
    +
    1871 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
    +
    1872 strlen(testsock) + 1 - sizeof(su.sun_path));
    +
    1873 return -1;
    +
    1874 }
    +
    1875 unlink(testsock);
    +
    1876 fd = socket(AF_UNIX, SOCK_STREAM, 0);
    +
    1877 if (fd < 0) {
    +
    1878 PERROR("socket");
    +
    1879 return -1;
    +
    1880 }
    +
    1881 su.sun_family = AF_UNIX;
    +
    1882
    +
    1883 strncpy(su.sun_path, testsock, test_sock_len);
    +
    1884 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
    +
    1885 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
    +
    1886 if (res == -1) {
    +
    1887 PERROR("bind");
    +
    1888 return -1;
    +
    1889 }
    +
    1890
    +
    1891 res = check_type(testsock, S_IFSOCK);
    +
    1892 if (res == -1) {
    +
    1893 close(fd);
    +
    1894 return -1;
    +
    1895 }
    +
    1896 err += check_nlink(testsock, 1);
    +
    1897 close(fd);
    +
    1898 res = unlink(testsock);
    +
    1899 if (res == -1) {
    +
    1900 PERROR("unlink");
    +
    1901 return -1;
    +
    1902 }
    +
    1903 res = check_nonexist(testsock);
    +
    1904 if (res == -1)
    +
    1905 return -1;
    +
    1906 if (err)
    +
    1907 return -1;
    +
    1908
    +
    1909 success();
    +
    1910 return 0;
    +
    1911}
    +
    1912
    +
    1913#define test_create_ro_dir(flags) \
    +
    1914 do_test_create_ro_dir(flags, #flags)
    +
    1915
    +
    1916static int do_test_create_ro_dir(int flags, const char *flags_str)
    +
    1917{
    +
    1918 int res;
    +
    1919 int err = 0;
    +
    1920 int fd;
    +
    1921
    +
    1922 start_test("open(%s) in read-only directory", flags_str);
    +
    1923 rmdir(testdir);
    +
    1924 res = mkdir(testdir, 0555);
    +
    1925 if (res == -1) {
    +
    1926 PERROR("mkdir");
    +
    1927 return -1;
    +
    1928 }
    +
    1929 fd = open(subfile, flags, 0644);
    +
    1930 if (fd != -1) {
    +
    1931 close(fd);
    +
    1932 unlink(subfile);
    +
    1933 ERROR("open should have failed");
    +
    1934 err--;
    +
    1935 } else {
    +
    1936 res = check_nonexist(subfile);
    +
    1937 if (res == -1)
    +
    1938 err--;
    +
    1939 }
    +
    1940 unlink(subfile);
    +
    1941 res = rmdir(testdir);
    +
    1942 if (res == -1) {
    +
    1943 PERROR("rmdir");
    +
    1944 return -1;
    +
    1945 }
    +
    1946 res = check_nonexist(testdir);
    +
    1947 if (res == -1)
    +
    1948 return -1;
    +
    1949 if (err)
    +
    1950 return -1;
    +
    1951
    +
    1952 success();
    +
    1953 return 0;
    +
    1954}
    +
    1955
    +
    1956#ifndef __FreeBSD__
    +
    1957/* this tests open with O_TMPFILE
    +
    1958 note that this will only work with the fuse low level api
    +
    1959 you will get ENOTSUP with the high level api */
    +
    1960static int test_create_tmpfile(void)
    +
    1961{
    +
    1962 rmdir(testdir);
    +
    1963 int res = mkdir(testdir, 0777);
    +
    1964 if (res)
    +
    1965 return -1;
    +
    1966
    +
    1967 start_test("create tmpfile");
    +
    1968
    +
    1969 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    1970 if(fd == -1) {
    +
    1971 if (errno == ENOTSUP) {
    +
    1972 /* don't bother if we're working on an old kernel
    +
    1973 or on the high level API */
    +
    1974 return 0;
    +
    1975 }
    +
    1976
    +
    1977 PERROR("open O_TMPFILE | O_RDWR");
    +
    1978 return -1;
    +
    1979 }
    +
    1980 close(fd);
    +
    1981
    +
    1982 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
    +
    1983 if(fd == -1){
    +
    1984 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
    +
    1985 return -1;
    +
    1986 };
    +
    1987 close(fd);
    +
    1988
    +
    1989 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
    +
    1990 if (fd != -1) {
    +
    1991 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
    +
    1992 return -1;
    +
    1993 }
    +
    1994
    +
    1995 success();
    +
    1996 return 0;
    +
    1997}
    +
    1998
    +
    1999static int test_create_and_link_tmpfile(void)
    +
    2000{
    +
    2001 /* skip this test for now since the github runner will fail in the linkat call below */
    +
    2002 return 0;
    +
    2003
    +
    2004 rmdir(testdir);
    +
    2005 unlink(testfile);
    +
    2006
    +
    2007 int res = mkdir(testdir, 0777);
    +
    2008 if (res)
    +
    2009 return -1;
    +
    2010
    +
    2011 start_test("create and link tmpfile");
    +
    2012
    +
    2013 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
    +
    2014 if(fd == -1) {
    +
    2015 if (errno == ENOTSUP) {
    +
    2016 /* don't bother if we're working on an old kernel
    +
    2017 or on the high level API */
    +
    2018 return 0;
    +
    2019 }
    +
    2020 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
    +
    2021 return -1;
    +
    2022 }
    +
    2023
    +
    2024 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2025 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
    +
    2026 return -1;
    +
    2027 }
    +
    2028 close(fd);
    +
    2029
    +
    2030 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    2031 if(fd == -1) {
    +
    2032 PERROR("open O_TMPFILE");
    +
    2033 return -1;
    +
    2034 }
    +
    2035
    +
    2036 if (check_nonexist(testfile)) {
    +
    2037 return -1;
    +
    2038 }
    +
    2039
    +
    2040 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2041 PERROR("linkat tempfile");
    +
    2042 return -1;
    +
    2043 }
    +
    2044 close(fd);
    +
    2045
    +
    2046 if (check_nlink(testfile, 1)) {
    +
    2047 return -1;
    +
    2048 }
    +
    2049 unlink(testfile);
    +
    2050
    +
    2051 success();
    +
    2052 return 0;
    +
    2053}
    +
    2054#endif
    +
    2055
    +
    2056int main(int argc, char *argv[])
    +
    2057{
    +
    2058 int err = 0;
    +
    2059 int a;
    +
    2060 int is_root;
    +
    2061
    +
    2062 umask(0);
    +
    2063 if (argc < 2 || argc > 4) {
    +
    2064 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
    +
    2065 return 1;
    +
    2066 }
    +
    2067 basepath = argv[1];
    +
    2068 basepath_r = basepath;
    +
    2069 for (a = 2; a < argc; a++) {
    +
    2070 char *endptr;
    +
    2071 char *arg = argv[a];
    +
    2072 if (arg[0] == ':') {
    +
    2073 basepath_r = arg + 1;
    +
    2074 } else {
    +
    2075 if (arg[0] == '-') {
    +
    2076 arg++;
    +
    2077 if (arg[0] == 'u') {
    +
    2078 unlinked_test = 1;
    +
    2079 endptr = arg + 1;
    +
    2080 } else {
    +
    2081 skip_test = strtoul(arg, &endptr, 10);
    +
    2082 }
    +
    2083 } else {
    +
    2084 select_test = strtoul(arg, &endptr, 10);
    +
    2085 }
    +
    2086 if (arg[0] == '\0' || *endptr != '\0') {
    +
    2087 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
    +
    2088 return 1;
    +
    2089 }
    +
    2090 }
    +
    2091 }
    +
    2092 assert(strlen(basepath) < 512);
    +
    2093 assert(strlen(basepath_r) < 512);
    +
    2094 if (basepath[0] != '/') {
    +
    2095 fprintf(stderr, "testdir must be an absolute path\n");
    +
    2096 return 1;
    +
    2097 }
    +
    2098
    +
    2099 sprintf(testfile, "%s/testfile", basepath);
    +
    2100 sprintf(testfile2, "%s/testfile2", basepath);
    +
    2101 sprintf(testdir, "%s/testdir", basepath);
    +
    2102 sprintf(testdir2, "%s/testdir2", basepath);
    +
    2103 sprintf(subfile, "%s/subfile", testdir2);
    +
    2104 sprintf(testsock, "%s/testsock", basepath);
    +
    2105
    +
    2106 sprintf(testfile_r, "%s/testfile", basepath_r);
    +
    2107 sprintf(testfile2_r, "%s/testfile2", basepath_r);
    +
    2108 sprintf(testdir_r, "%s/testdir", basepath_r);
    +
    2109 sprintf(testdir2_r, "%s/testdir2", basepath_r);
    +
    2110 sprintf(subfile_r, "%s/subfile", testdir2_r);
    +
    2111
    +
    2112 is_root = (geteuid() == 0);
    +
    2113
    +
    2114 err += test_create();
    +
    2115 err += test_create_unlink();
    +
    2116 err += test_symlink();
    +
    2117 err += test_link();
    +
    2118 err += test_link2();
    +
    2119 err += test_mknod();
    +
    2120 err += test_mkfifo();
    +
    2121 err += test_mkdir();
    +
    2122 err += test_rename_file();
    +
    2123 err += test_rename_dir();
    +
    2124 err += test_rename_dir_loop();
    +
    2125 err += test_seekdir();
    +
    2126 err += test_socket();
    +
    2127 err += test_utime();
    +
    2128 err += test_truncate(0);
    +
    2129 err += test_truncate(testdatalen / 2);
    +
    2130 err += test_truncate(testdatalen);
    +
    2131 err += test_truncate(testdatalen + 100);
    +
    2132 err += test_ftruncate(0, 0600);
    +
    2133 err += test_ftruncate(testdatalen / 2, 0600);
    +
    2134 err += test_ftruncate(testdatalen, 0600);
    +
    2135 err += test_ftruncate(testdatalen + 100, 0600);
    +
    2136 err += test_ftruncate(0, 0400);
    +
    2137 err += test_ftruncate(0, 0200);
    +
    2138 err += test_ftruncate(0, 0000);
    +
    2139 err += test_open(0, O_RDONLY, 0);
    +
    2140 err += test_open(1, O_RDONLY, 0);
    +
    2141 err += test_open(1, O_RDWR, 0);
    +
    2142 err += test_open(1, O_WRONLY, 0);
    +
    2143 err += test_open(0, O_RDWR | O_CREAT, 0600);
    +
    2144 err += test_open(1, O_RDWR | O_CREAT, 0600);
    +
    2145 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2146 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2147 err += test_open(0, O_RDONLY | O_CREAT, 0600);
    +
    2148 err += test_open(0, O_RDONLY | O_CREAT, 0400);
    +
    2149 err += test_open(0, O_RDONLY | O_CREAT, 0200);
    +
    2150 err += test_open(0, O_RDONLY | O_CREAT, 0000);
    +
    2151 err += test_open(0, O_WRONLY | O_CREAT, 0600);
    +
    2152 err += test_open(0, O_WRONLY | O_CREAT, 0400);
    +
    2153 err += test_open(0, O_WRONLY | O_CREAT, 0200);
    +
    2154 err += test_open(0, O_WRONLY | O_CREAT, 0000);
    +
    2155 err += test_open(0, O_RDWR | O_CREAT, 0400);
    +
    2156 err += test_open(0, O_RDWR | O_CREAT, 0200);
    +
    2157 err += test_open(0, O_RDWR | O_CREAT, 0000);
    +
    2158 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2159 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2160 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2161 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2162 err += test_open_acc(O_RDONLY, 0600, 0);
    +
    2163 err += test_open_acc(O_WRONLY, 0600, 0);
    +
    2164 err += test_open_acc(O_RDWR, 0600, 0);
    +
    2165 err += test_open_acc(O_RDONLY, 0400, 0);
    +
    2166 err += test_open_acc(O_WRONLY, 0200, 0);
    +
    2167 if(!is_root) {
    +
    2168 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
    +
    2169 err += test_open_acc(O_WRONLY, 0400, EACCES);
    +
    2170 err += test_open_acc(O_RDWR, 0400, EACCES);
    +
    2171 err += test_open_acc(O_RDONLY, 0200, EACCES);
    +
    2172 err += test_open_acc(O_RDWR, 0200, EACCES);
    +
    2173 err += test_open_acc(O_RDONLY, 0000, EACCES);
    +
    2174 err += test_open_acc(O_WRONLY, 0000, EACCES);
    +
    2175 err += test_open_acc(O_RDWR, 0000, EACCES);
    +
    2176 }
    +
    2177 err += test_create_ro_dir(O_CREAT);
    +
    2178 err += test_create_ro_dir(O_CREAT | O_EXCL);
    +
    2179 err += test_create_ro_dir(O_CREAT | O_WRONLY);
    +
    2180 err += test_create_ro_dir(O_CREAT | O_TRUNC);
    +
    2181 err += test_copy_file_range();
    +
    2182#ifndef __FreeBSD__
    +
    2183 err += test_create_tmpfile();
    +
    2184 err += test_create_and_link_tmpfile();
    +
    2185#endif
    +
    2186
    +
    2187 unlink(testfile2);
    +
    2188 unlink(testsock);
    +
    2189 rmdir(testdir);
    +
    2190 rmdir(testdir2);
    +
    2191
    +
    2192 if (err) {
    +
    2193 fprintf(stderr, "%i tests failed\n", -err);
    +
    2194 return 1;
    +
    2195 }
    +
    2196
    +
    2197 return check_unlinked_testfiles();
    +
    2198}
    +
    + + + + diff --git a/doc/html/test_2test__want__conversion_8c_source.html b/doc/html/test_2test__want__conversion_8c_source.html new file mode 100644 index 0000000..a345d97 --- /dev/null +++ b/doc/html/test_2test__want__conversion_8c_source.html @@ -0,0 +1,237 @@ + + + + + + + +libfuse: test/test_want_conversion.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_want_conversion.c
    +
    +
    +
    1#include "util.h"
    +
    2#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
    +
    3
    +
    4#include "fuse_i.h"
    +
    5#include <stdio.h>
    +
    6#include <assert.h>
    +
    7#include <inttypes.h>
    +
    8#include <stdbool.h>
    +
    9
    +
    10static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
    +
    11{
    +
    12 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 "\n", prefix,
    +
    13 conn->want, conn->want_ext);
    +
    14}
    +
    15
    +
    16static void application_init_old_style(struct fuse_conn_info *conn)
    +
    17{
    +
    18 /* Simulate application init the old style */
    + +
    20 conn->want &= ~FUSE_CAP_SPLICE_READ;
    +
    21}
    +
    22
    +
    23static void application_init_new_style(struct fuse_conn_info *conn)
    +
    24{
    +
    25 /* Simulate application init the new style */
    +
    26 fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
    +
    27 fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ);
    +
    28}
    +
    29
    +
    30static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
    +
    31{
    +
    32 uint64_t want_ext_default = conn->want_ext;
    +
    33 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
    +
    34 int rc;
    +
    35
    +
    36 /* High-level init */
    +
    37 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
    +
    38
    +
    39 conn->want = want_default;
    +
    40
    +
    41 if (new_style)
    +
    42 application_init_new_style(conn);
    +
    43 else
    +
    44 application_init_old_style(conn);
    +
    45
    +
    46 rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
    +
    47 assert(rc == 0);
    +
    48}
    +
    49
    +
    50static void test_do_init(struct fuse_conn_info *conn, bool new_style)
    +
    51{
    +
    52 /* Initial setup */
    + + + + +
    57 conn->capable = fuse_lower_32_bits(conn->capable_ext);
    +
    58 conn->want_ext = conn->capable_ext;
    +
    59
    +
    60 print_conn_info("Initial state", conn);
    +
    61
    +
    62 uint64_t want_ext_default = conn->want_ext;
    +
    63 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
    +
    64 int rc;
    +
    65
    +
    66 conn->want = want_default;
    +
    67 conn->capable = fuse_lower_32_bits(conn->capable_ext);
    +
    68
    +
    69 test_fuse_fs_init(conn, new_style);
    +
    70
    +
    71 rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
    +
    72 assert(rc == 0);
    +
    73
    +
    74 /* Verify all expected flags are set */
    +
    75 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
    +
    76 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
    +
    77 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
    +
    78 assert(conn->want_ext & FUSE_CAP_POSIX_LOCKS);
    +
    79 assert(conn->want_ext & FUSE_CAP_FLOCK_LOCKS);
    +
    80 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
    +
    81 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
    +
    82 /* Verify no other flags are set */
    +
    83 assert(conn->want_ext ==
    + + + +
    87
    +
    88 print_conn_info("After init", conn);
    +
    89}
    +
    90
    +
    91static void test_want_conversion_basic(void)
    +
    92{
    +
    93 struct fuse_conn_info conn = { 0 };
    +
    94
    +
    95 printf("\nTesting basic want conversion:\n");
    +
    96 test_do_init(&conn, false);
    +
    97 test_do_init(&conn, true);
    +
    98 print_conn_info("After init", &conn);
    +
    99}
    +
    100
    +
    101static void test_want_conversion_conflict(void)
    +
    102{
    +
    103 struct fuse_conn_info conn = { 0 };
    +
    104 int rc;
    +
    105
    +
    106 printf("\nTesting want conversion conflict:\n");
    +
    107
    +
    108 /* Test conflicting values */
    +
    109 /* Initialize like fuse_lowlevel.c does */
    + + + +
    113 conn.capable = fuse_lower_32_bits(conn.capable_ext);
    +
    114 conn.want_ext = conn.capable_ext;
    +
    115 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    116 print_conn_info("Test conflict initial", &conn);
    +
    117
    +
    118 /* Initialize default values like in basic test */
    +
    119 uint64_t want_ext_default_ll = conn.want_ext;
    +
    120 uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll);
    +
    121
    +
    122 /* Simulate application init modifying capabilities */
    +
    123 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
    +
    124 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
    +
    125
    +
    126 rc = convert_to_conn_want_ext(&conn, want_ext_default_ll,
    +
    127 want_default_ll);
    +
    128 assert(rc == -EINVAL);
    +
    129 print_conn_info("Test conflict after", &conn);
    +
    130
    +
    131 printf("Want conversion conflict test passed\n");
    +
    132}
    +
    133
    +
    134static void test_want_conversion_high_bits(void)
    +
    135{
    +
    136 struct fuse_conn_info conn = { 0 };
    +
    137 int rc;
    +
    138
    +
    139 printf("\nTesting want conversion high bits preservation:\n");
    +
    140
    +
    141 /* Test high bits preservation */
    +
    142 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
    +
    143 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    144 print_conn_info("Test high bits initial", &conn);
    +
    145
    +
    146 uint64_t want_ext_default_ll = conn.want_ext;
    +
    147 uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll);
    +
    148
    +
    149 rc = convert_to_conn_want_ext(&conn, want_ext_default_ll,
    +
    150 want_default_ll);
    +
    151 assert(rc == 0);
    +
    152 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
    +
    153 print_conn_info("Test high bits after", &conn);
    +
    154
    +
    155 printf("Want conversion high bits test passed\n");
    +
    156}
    +
    157
    +
    158int main(void)
    +
    159{
    +
    160 test_want_conversion_basic();
    +
    161 test_want_conversion_conflict();
    +
    162 test_want_conversion_high_bits();
    +
    163 return 0;
    +
    164}
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_FLOCK_LOCKS
    + + +
    uint64_t capable_ext
    +
    uint32_t capable
    +
    uint64_t want_ext
    +
    + + + + diff --git a/doc/html/test_2test__write__cache_8c_source.html b/doc/html/test_2test__write__cache_8c_source.html new file mode 100644 index 0000000..f6e97f7 --- /dev/null +++ b/doc/html/test_2test__write__cache_8c_source.html @@ -0,0 +1,402 @@ + + + + + + + +libfuse: test/test_write_cache.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_write_cache.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    9
    +
    10#define FUSE_USE_VERSION 30
    +
    11
    +
    12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    13#include <fuse.h>
    +
    14
    +
    15#include <fuse_config.h>
    +
    16#include <fuse_lowlevel.h>
    +
    17#include <stdio.h>
    +
    18#include <stdlib.h>
    +
    19#include <string.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <assert.h>
    +
    23#include <stddef.h>
    +
    24#include <unistd.h>
    +
    25#include <sys/stat.h>
    +
    26#include <pthread.h>
    +
    27#include <stdatomic.h>
    +
    28
    +
    29#ifndef __linux__
    +
    30#include <limits.h>
    +
    31#else
    +
    32#include <linux/limits.h>
    +
    33#endif
    +
    34
    +
    35#define FILE_INO 2
    +
    36#define FILE_NAME "write_me"
    +
    37
    +
    38/* Command line parsing */
    +
    39struct options {
    +
    40 int writeback;
    +
    41 int data_size;
    +
    42 int delay_ms;
    +
    43} options = {
    +
    44 .writeback = 0,
    +
    45 .data_size = 2048,
    +
    46 .delay_ms = 0,
    +
    47};
    +
    48
    +
    49#define WRITE_SYSCALLS 64
    +
    50
    +
    51#define OPTION(t, p) \
    +
    52 { t, offsetof(struct options, p), 1 }
    +
    53static const struct fuse_opt option_spec[] = {
    +
    54 OPTION("writeback_cache", writeback),
    +
    55 OPTION("--data-size=%d", data_size),
    +
    56 OPTION("--delay_ms=%d", delay_ms),
    + +
    58};
    +
    59static int got_write;
    +
    60static atomic_int write_cnt;
    +
    61
    +
    62pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    +
    63pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    64static int write_start, write_done;
    +
    65
    +
    66static void tfs_init (void *userdata, struct fuse_conn_info *conn)
    +
    67{
    +
    68 (void) userdata;
    +
    69
    +
    70 if(options.writeback) {
    +
    71 assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE));
    +
    72 fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
    +
    73 }
    +
    74}
    +
    75
    +
    76static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    77 stbuf->st_ino = ino;
    +
    78 if (ino == FUSE_ROOT_ID) {
    +
    79 stbuf->st_mode = S_IFDIR | 0755;
    +
    80 stbuf->st_nlink = 1;
    +
    81 }
    +
    82
    +
    83 else if (ino == FILE_INO) {
    +
    84 stbuf->st_mode = S_IFREG | 0222;
    +
    85 stbuf->st_nlink = 1;
    +
    86 stbuf->st_size = 0;
    +
    87 }
    +
    88
    +
    89 else
    +
    90 return -1;
    +
    91
    +
    92 return 0;
    +
    93}
    +
    94
    +
    95static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    96 const char *name) {
    +
    97 struct fuse_entry_param e;
    +
    98 memset(&e, 0, sizeof(e));
    +
    99
    +
    100 if (parent != FUSE_ROOT_ID)
    +
    101 goto err_out;
    +
    102 else if (strcmp(name, FILE_NAME) == 0)
    +
    103 e.ino = FILE_INO;
    +
    104 else
    +
    105 goto err_out;
    +
    106
    +
    107 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    108 goto err_out;
    +
    109 fuse_reply_entry(req, &e);
    +
    110 return;
    +
    111
    +
    112err_out:
    +
    113 fuse_reply_err(req, ENOENT);
    +
    114}
    +
    115
    +
    116static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    117 struct fuse_file_info *fi) {
    +
    118 struct stat stbuf;
    +
    119
    +
    120 (void) fi;
    +
    121
    +
    122 memset(&stbuf, 0, sizeof(stbuf));
    +
    123 if (tfs_stat(ino, &stbuf) != 0)
    +
    124 fuse_reply_err(req, ENOENT);
    +
    125 else
    +
    126 fuse_reply_attr(req, &stbuf, 5);
    +
    127}
    +
    128
    +
    129static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    130 struct fuse_file_info *fi) {
    +
    131 if (ino == FUSE_ROOT_ID)
    +
    132 fuse_reply_err(req, EISDIR);
    +
    133 else {
    +
    134 assert(ino == FILE_INO);
    +
    135 /* Test close(rofd) does not block waiting for pending writes */
    +
    136 fi->noflush = !options.writeback && options.delay_ms &&
    +
    137 (fi->flags & O_ACCMODE) == O_RDONLY;
    +
    138 fuse_reply_open(req, fi);
    +
    139 }
    +
    140}
    +
    141
    +
    142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
    +
    143 size_t size, off_t off, struct fuse_file_info *fi) {
    +
    144 (void) fi; (void) buf; (void) off;
    +
    145 size_t expected;
    +
    146
    +
    147 assert(ino == FILE_INO);
    +
    148 expected = options.data_size;
    +
    149 if(options.writeback)
    +
    150 expected *= 2;
    +
    151
    +
    152 write_cnt++;
    +
    153
    +
    154 if(size != expected && !options.writeback)
    +
    155 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
    +
    156 expected, size);
    +
    157 else
    +
    158 got_write = 1;
    +
    159
    +
    160 /* Simulate waiting for pending writes */
    +
    161 if (options.delay_ms) {
    +
    162 pthread_mutex_lock(&lock);
    +
    163 write_start = 1;
    +
    164 pthread_cond_signal(&cond);
    +
    165 pthread_mutex_unlock(&lock);
    +
    166
    +
    167 usleep(options.delay_ms * 1000);
    +
    168
    +
    169 pthread_mutex_lock(&lock);
    +
    170 write_done = 1;
    +
    171 pthread_cond_signal(&cond);
    +
    172 pthread_mutex_unlock(&lock);
    +
    173 }
    +
    174
    +
    175 fuse_reply_write(req, size);
    +
    176}
    +
    177
    +
    178static struct fuse_lowlevel_ops tfs_oper = {
    +
    179 .init = tfs_init,
    +
    180 .lookup = tfs_lookup,
    +
    181 .getattr = tfs_getattr,
    +
    182 .open = tfs_open,
    +
    183 .write = tfs_write,
    +
    184};
    +
    185
    +
    186static void* close_rofd(void *data) {
    +
    187 int rofd = (int)(long) data;
    +
    188
    +
    189 /* Wait for first write to start */
    +
    190 pthread_mutex_lock(&lock);
    +
    191 while (!write_start && !write_done)
    +
    192 pthread_cond_wait(&cond, &lock);
    +
    193 pthread_mutex_unlock(&lock);
    +
    194
    +
    195 close(rofd);
    +
    196 printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done);
    +
    197
    +
    198 /* First write should not have been completed */
    +
    199 if (write_done)
    +
    200 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
    +
    201
    +
    202 return NULL;
    +
    203}
    +
    204
    +
    205static void* run_fs(void *data) {
    +
    206 struct fuse_session *se = (struct fuse_session*) data;
    +
    207 assert(fuse_session_loop(se) == 0);
    +
    208 return NULL;
    +
    209}
    +
    210
    +
    211static void test_fs(char *mountpoint) {
    +
    212 char fname[PATH_MAX];
    +
    213 char *buf;
    +
    214 const size_t iosize = options.data_size;
    +
    215 const size_t dsize = options.data_size * WRITE_SYSCALLS;
    +
    216 int fd, rofd;
    +
    217 pthread_t rofd_thread;
    +
    218 off_t off = 0;
    +
    219
    +
    220 buf = malloc(dsize);
    +
    221 assert(buf != NULL);
    +
    222 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
    +
    223 assert(read(fd, buf, dsize) == dsize);
    +
    224 close(fd);
    +
    225
    +
    226 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
    +
    227 mountpoint) > 0);
    +
    228 fd = open(fname, O_WRONLY);
    +
    229 if (fd == -1) {
    +
    230 perror(fname);
    +
    231 assert(0);
    +
    232 }
    +
    233
    +
    234 if (options.delay_ms) {
    +
    235 /* Verify that close(rofd) does not block waiting for pending writes */
    +
    236 rofd = open(fname, O_RDONLY);
    +
    237 assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0);
    +
    238 /* Give close_rofd time to start */
    +
    239 usleep(options.delay_ms * 1000);
    +
    240 }
    +
    241
    +
    242 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
    +
    243 assert(pwrite(fd, buf + off, iosize, off) == iosize);
    +
    244 off += iosize;
    +
    245 assert(off <= dsize);
    +
    246 }
    +
    247 free(buf);
    +
    248 close(fd);
    +
    249
    +
    250 if (options.delay_ms) {
    +
    251 printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done);
    +
    252 assert(pthread_join(rofd_thread, NULL) == 0);
    +
    253 }
    +
    254}
    +
    255
    +
    256int main(int argc, char *argv[]) {
    +
    257 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    258 struct fuse_session *se;
    +
    259 struct fuse_cmdline_opts fuse_opts;
    +
    260 pthread_t fs_thread;
    +
    261
    +
    262 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
    +
    263 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    264#ifndef __FreeBSD__
    +
    265 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    266#endif
    +
    267 se = fuse_session_new(&args, &tfs_oper,
    +
    268 sizeof(tfs_oper), NULL);
    +
    269 fuse_opt_free_args(&args);
    +
    270 assert (se != NULL);
    +
    271 assert(fuse_set_signal_handlers(se) == 0);
    +
    272 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    273
    +
    274 /* Start file-system thread */
    +
    275 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    276
    +
    277 /* Write test data */
    +
    278 test_fs(fuse_opts.mountpoint);
    +
    279 free(fuse_opts.mountpoint);
    +
    280
    +
    281 /* Stop file system */
    + + +
    284 assert(pthread_join(fs_thread, NULL) == 0);
    +
    285
    +
    286 assert(got_write == 1);
    +
    287
    +
    288 /*
    +
    289 * when writeback cache is enabled, kernel side can merge requests, but
    +
    290 * memory pressure, system 'sync' might trigger data flushes before - flush
    +
    291 * might happen in between write syscalls - merging subpage writes into
    +
    292 * a single page and pages into large fuse requests might or might not work.
    +
    293 * Though we can expect that that at least some (but maybe all) write
    +
    294 * system calls can be merged.
    +
    295 */
    +
    296 if (options.writeback)
    +
    297 assert(write_cnt < WRITE_SYSCALLS);
    +
    298 else
    +
    299 assert(write_cnt == WRITE_SYSCALLS);
    +
    300
    + + +
    303
    +
    304 printf("Test completed successfully.\n");
    +
    305 return 0;
    +
    306}
    +
    307
    +
    308
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    +
    #define FUSE_ROOT_ID
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    +
    fuse_ino_t ino
    + +
    uint32_t noflush
    + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    + + + + diff --git a/doc/html/test_2wrong__command_8c_source.html b/doc/html/test_2wrong__command_8c_source.html new file mode 100644 index 0000000..2ce3098 --- /dev/null +++ b/doc/html/test_2wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: test/wrong_command.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    wrong_command.c
    +
    +
    +
    1#include <stdio.h>
    +
    2
    +
    3int main(void) {
    +
    4#ifdef MESON_IS_SUBPROJECT
    +
    5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
    +
    6 "If you wish to run them try:\n"
    +
    7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
    +
    8 return 77; /* report as a skipped test */
    +
    9#else
    +
    10 fprintf(stderr, "\x1B[31m\e[1m"
    +
    11 "This is not the command you are looking for.\n"
    +
    12 "You probably want to run 'python3 -m pytest test/' instead"
    +
    13 "\e[0m\n");
    +
    14 return 1;
    +
    15#endif
    +
    16}
    +
    + + + + diff --git a/doc/html/test__setattr_8c_source.html b/doc/html/test__setattr_8c_source.html new file mode 100644 index 0000000..7abe3ea --- /dev/null +++ b/doc/html/test__setattr_8c_source.html @@ -0,0 +1,274 @@ + + + + + + + +libfuse: test/test_setattr.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_setattr.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    9
    +
    10#define FUSE_USE_VERSION 30
    +
    11
    +
    12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    13#include <fuse.h>
    +
    14
    +
    15#include <fuse_config.h>
    +
    16#include <fuse_lowlevel.h>
    +
    17#include <stdio.h>
    +
    18#include <stdlib.h>
    +
    19#include <string.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <assert.h>
    +
    23#include <stddef.h>
    +
    24#include <unistd.h>
    +
    25#include <pthread.h>
    +
    26
    +
    27#ifndef __linux__
    +
    28#include <limits.h>
    +
    29#else
    +
    30#include <linux/limits.h>
    +
    31#endif
    +
    32
    +
    33#define FILE_INO 2
    +
    34#define FILE_NAME "truncate_me"
    +
    35
    +
    36static int got_fh;
    +
    37static mode_t file_mode = S_IFREG | 0644;
    +
    38
    +
    39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    40 stbuf->st_ino = ino;
    +
    41 if (ino == FUSE_ROOT_ID) {
    +
    42 stbuf->st_mode = S_IFDIR | 0755;
    +
    43 stbuf->st_nlink = 1;
    +
    44 }
    +
    45
    +
    46 else if (ino == FILE_INO) {
    +
    47 stbuf->st_mode = file_mode;
    +
    48 stbuf->st_nlink = 1;
    +
    49 stbuf->st_size = 0;
    +
    50 }
    +
    51
    +
    52 else
    +
    53 return -1;
    +
    54
    +
    55 return 0;
    +
    56}
    +
    57
    +
    58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    59 const char *name) {
    +
    60 struct fuse_entry_param e;
    +
    61 memset(&e, 0, sizeof(e));
    +
    62
    +
    63 if (parent != FUSE_ROOT_ID)
    +
    64 goto err_out;
    +
    65 else if (strcmp(name, FILE_NAME) == 0)
    +
    66 e.ino = FILE_INO;
    +
    67 else
    +
    68 goto err_out;
    +
    69
    +
    70 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    71 goto err_out;
    +
    72 fuse_reply_entry(req, &e);
    +
    73 return;
    +
    74
    +
    75err_out:
    +
    76 fuse_reply_err(req, ENOENT);
    +
    77}
    +
    78
    +
    79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    80 struct fuse_file_info *fi) {
    +
    81 struct stat stbuf;
    +
    82
    +
    83 (void) fi;
    +
    84
    +
    85 memset(&stbuf, 0, sizeof(stbuf));
    +
    86 if (tfs_stat(ino, &stbuf) != 0)
    +
    87 fuse_reply_err(req, ENOENT);
    +
    88 else
    +
    89 fuse_reply_attr(req, &stbuf, 5);
    +
    90}
    +
    91
    +
    92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    93 struct fuse_file_info *fi) {
    +
    94 if (ino == FUSE_ROOT_ID)
    +
    95 fuse_reply_err(req, EISDIR);
    +
    96 else {
    +
    97 assert(ino == FILE_INO);
    +
    98 fi->fh = FILE_INO;
    +
    99 fuse_reply_open(req, fi);
    +
    100 }
    +
    101}
    +
    102
    +
    103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    104 int to_set, struct fuse_file_info *fi) {
    +
    105 if(ino != FILE_INO ||
    +
    106 !(to_set & FUSE_SET_ATTR_MODE)) {
    +
    107 fuse_reply_err(req, EINVAL);
    +
    108 return;
    +
    109 }
    +
    110
    +
    111 if(fi == NULL)
    +
    112 fprintf(stderr, "setattr with fi == NULL\n");
    +
    113 else if (fi->fh != FILE_INO)
    +
    114 fprintf(stderr, "setattr with wrong fi->fh\n");
    +
    115 else {
    +
    116 fprintf(stderr, "setattr ok\n");
    +
    117 got_fh = 1;
    +
    118 file_mode = attr->st_mode;
    +
    119 }
    +
    120
    +
    121 tfs_getattr(req, ino, fi);
    +
    122}
    +
    123
    +
    124static struct fuse_lowlevel_ops tfs_oper = {
    +
    125 .lookup = tfs_lookup,
    +
    126 .getattr = tfs_getattr,
    +
    127 .open = tfs_open,
    +
    128 .setattr = tfs_setattr,
    +
    129};
    +
    130
    +
    131static void* run_fs(void *data) {
    +
    132 struct fuse_session *se = (struct fuse_session*) data;
    +
    133 assert(fuse_session_loop(se) == 0);
    +
    134 return NULL;
    +
    135}
    +
    136
    +
    137static void test_fs(char *mountpoint) {
    +
    138 char fname[PATH_MAX];
    +
    139 int fd;
    +
    140
    +
    141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
    +
    142 mountpoint) > 0);
    +
    143 fd = open(fname, O_WRONLY);
    +
    144 if (fd == -1) {
    +
    145 perror(fname);
    +
    146 assert(0);
    +
    147 }
    +
    148
    +
    149 assert(fchmod(fd, 0600) == 0);
    +
    150 close(fd);
    +
    151}
    +
    152
    +
    153int main(int argc, char *argv[]) {
    +
    154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    155 struct fuse_session *se;
    +
    156 struct fuse_cmdline_opts fuse_opts;
    +
    157 pthread_t fs_thread;
    +
    158
    +
    159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    160#ifndef __FreeBSD__
    +
    161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    162#endif
    +
    163 se = fuse_session_new(&args, &tfs_oper,
    +
    164 sizeof(tfs_oper), NULL);
    +
    165 assert (se != NULL);
    +
    166 assert(fuse_set_signal_handlers(se) == 0);
    +
    167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    168
    +
    169 /* Start file-system thread */
    +
    170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    171
    +
    172 /* Do test */
    +
    173 test_fs(fuse_opts.mountpoint);
    +
    174
    +
    175 /* Stop file system */
    +
    176 assert(pthread_cancel(fs_thread) == 0);
    +
    177
    + +
    179 assert(got_fh == 1);
    + + +
    182
    +
    183 printf("Test completed successfully.\n");
    +
    184 return 0;
    +
    185}
    +
    186
    +
    187
    + +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    +
    fuse_ino_t ino
    + + + +
    void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    + + + + diff --git a/doc/html/test__syscalls_8c_source.html b/doc/html/test__syscalls_8c_source.html new file mode 100644 index 0000000..e8907fd --- /dev/null +++ b/doc/html/test__syscalls_8c_source.html @@ -0,0 +1,2258 @@ + + + + + + + +libfuse: test/test_syscalls.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_syscalls.c
    +
    +
    +
    1#define _GNU_SOURCE
    +
    2#include "fuse_config.h"
    +
    3
    +
    4#include <stdio.h>
    +
    5#include <stdlib.h>
    +
    6#include <stdarg.h>
    +
    7#include <string.h>
    +
    8#include <unistd.h>
    +
    9#include <fcntl.h>
    +
    10#include <dirent.h>
    +
    11#include <utime.h>
    +
    12#include <errno.h>
    +
    13#include <assert.h>
    +
    14#include <sys/socket.h>
    +
    15#include <sys/types.h>
    +
    16#include <sys/stat.h>
    +
    17#include <sys/un.h>
    +
    18
    +
    19#ifndef ALLPERMS
    +
    20# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
    +
    21#endif
    +
    22
    +
    23
    +
    24static const char *basepath;
    +
    25static const char *basepath_r;
    +
    26static char testfile[1024];
    +
    27static char testfile2[1024];
    +
    28static char testdir[1024];
    +
    29static char testdir2[1024];
    +
    30static char testsock[1024];
    +
    31static char subfile[1280];
    +
    32
    +
    33static char testfile_r[1024];
    +
    34static char testfile2_r[1024];
    +
    35static char testdir_r[1024];
    +
    36static char testdir2_r[1024];
    +
    37static char subfile_r[1280];
    +
    38
    +
    39static char testname[256];
    +
    40static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
    +
    41static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
    +
    42static const char *testdir_files[] = { "f1", "f2", NULL};
    +
    43static long seekdir_offsets[4];
    +
    44static char zerodata[4096];
    +
    45static int testdatalen = sizeof(testdata) - 1;
    +
    46static int testdata2len = sizeof(testdata2) - 1;
    +
    47static unsigned int testnum = 0;
    +
    48static unsigned int select_test = 0;
    +
    49static unsigned int skip_test = 0;
    +
    50static unsigned int unlinked_test = 0;
    +
    51
    +
    52#define MAX_ENTRIES 1024
    +
    53#define MAX_TESTS 100
    +
    54
    +
    55static struct test {
    +
    56 int fd;
    +
    57 struct stat stat;
    +
    58} tests[MAX_TESTS];
    +
    59
    +
    60static void test_perror(const char *func, const char *msg)
    +
    61{
    +
    62 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
    +
    63 strerror(errno));
    +
    64}
    +
    65
    +
    66static void test_error(const char *func, const char *msg, ...)
    +
    67 __attribute__ ((format (printf, 2, 3)));
    +
    68
    +
    69static void __start_test(const char *fmt, ...)
    +
    70 __attribute__ ((format (printf, 1, 2)));
    +
    71
    +
    72static void test_error(const char *func, const char *msg, ...)
    +
    73{
    +
    74 va_list ap;
    +
    75 fprintf(stderr, "%s %s() - ", testname, func);
    +
    76 va_start(ap, msg);
    +
    77 vfprintf(stderr, msg, ap);
    +
    78 va_end(ap);
    +
    79 fprintf(stderr, "\n");
    +
    80}
    +
    81
    +
    82static int is_dot_or_dotdot(const char *name) {
    +
    83 return name[0] == '.' &&
    +
    84 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
    +
    85}
    +
    86
    +
    87static void success(void)
    +
    88{
    +
    89 fprintf(stderr, "%s OK\n", testname);
    +
    90}
    +
    91
    +
    92#define this_test (&tests[testnum-1])
    +
    93#define next_test (&tests[testnum])
    +
    94
    +
    95static void __start_test(const char *fmt, ...)
    +
    96{
    +
    97 unsigned int n;
    +
    98 va_list ap;
    +
    99 n = sprintf(testname, "%3i [", testnum);
    +
    100 va_start(ap, fmt);
    +
    101 n += vsprintf(testname + n, fmt, ap);
    +
    102 va_end(ap);
    +
    103 sprintf(testname + n, "]");
    +
    104 // Use dedicated testfile per test
    +
    105 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
    +
    106 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
    +
    107 if (testnum > MAX_TESTS) {
    +
    108 fprintf(stderr, "%s - too many tests\n", testname);
    +
    109 exit(1);
    +
    110 }
    +
    111 this_test->fd = -1;
    +
    112}
    +
    113
    +
    114#define start_test(msg, args...) { \
    +
    115 testnum++; \
    +
    116 if ((select_test && testnum != select_test) || \
    +
    117 (testnum == skip_test)) { \
    +
    118 return 0; \
    +
    119 } \
    +
    120 __start_test(msg, ##args); \
    +
    121}
    +
    122
    +
    123#define PERROR(msg) test_perror(__FUNCTION__, msg)
    +
    124#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
    +
    125
    +
    126#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
    +
    127
    +
    128static int st_check_size(struct stat *st, int len)
    +
    129{
    +
    130 if (st->st_size != len) {
    +
    131 ERROR("length %u instead of %u", (int) st->st_size,
    +
    132 (int) len);
    +
    133 return -1;
    +
    134 }
    +
    135 return 0;
    +
    136}
    +
    137
    +
    138static int check_size(const char *path, int len)
    +
    139{
    +
    140 struct stat stbuf;
    +
    141 int res = stat(path, &stbuf);
    +
    142 if (res == -1) {
    +
    143 PERROR("stat");
    +
    144 return -1;
    +
    145 }
    +
    146 return st_check_size(&stbuf, len);
    +
    147}
    +
    148
    +
    149static int check_testfile_size(const char *path, int len)
    +
    150{
    +
    151 this_test->stat.st_size = len;
    +
    152 return check_size(path, len);
    +
    153}
    +
    154
    +
    155static int st_check_type(struct stat *st, mode_t type)
    +
    156{
    +
    157 if ((st->st_mode & S_IFMT) != type) {
    +
    158 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
    +
    159 return -1;
    +
    160 }
    +
    161 return 0;
    +
    162}
    +
    163
    +
    164static int check_type(const char *path, mode_t type)
    +
    165{
    +
    166 struct stat stbuf;
    +
    167 int res = lstat(path, &stbuf);
    +
    168 if (res == -1) {
    +
    169 PERROR("lstat");
    +
    170 return -1;
    +
    171 }
    +
    172 return st_check_type(&stbuf, type);
    +
    173}
    +
    174
    +
    175static int st_check_mode(struct stat *st, mode_t mode)
    +
    176{
    +
    177 if ((st->st_mode & ALLPERMS) != mode) {
    +
    178 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
    +
    179 mode);
    +
    180 return -1;
    +
    181 }
    +
    182 return 0;
    +
    183}
    +
    184
    +
    185static int check_mode(const char *path, mode_t mode)
    +
    186{
    +
    187 struct stat stbuf;
    +
    188 int res = lstat(path, &stbuf);
    +
    189 if (res == -1) {
    +
    190 PERROR("lstat");
    +
    191 return -1;
    +
    192 }
    +
    193 return st_check_mode(&stbuf, mode);
    +
    194}
    +
    195
    +
    196static int check_testfile_mode(const char *path, mode_t mode)
    +
    197{
    +
    198 this_test->stat.st_mode &= ~ALLPERMS;
    +
    199 this_test->stat.st_mode |= mode;
    +
    200 return check_mode(path, mode);
    +
    201}
    +
    202
    +
    203static int check_times(const char *path, time_t atime, time_t mtime)
    +
    204{
    +
    205 int err = 0;
    +
    206 struct stat stbuf;
    +
    207 int res = lstat(path, &stbuf);
    +
    208 if (res == -1) {
    +
    209 PERROR("lstat");
    +
    210 return -1;
    +
    211 }
    +
    212 if (stbuf.st_atime != atime) {
    +
    213 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    214 err--;
    +
    215 }
    +
    216 if (stbuf.st_mtime != mtime) {
    +
    217 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    218 err--;
    +
    219 }
    +
    220 if (err)
    +
    221 return -1;
    +
    222
    +
    223 return 0;
    +
    224}
    +
    225
    +
    226#if 0
    +
    227static int fcheck_times(int fd, time_t atime, time_t mtime)
    +
    228{
    +
    229 int err = 0;
    +
    230 struct stat stbuf;
    +
    231 int res = fstat(fd, &stbuf);
    +
    232 if (res == -1) {
    +
    233 PERROR("fstat");
    +
    234 return -1;
    +
    235 }
    +
    236 if (stbuf.st_atime != atime) {
    +
    237 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    238 err--;
    +
    239 }
    +
    240 if (stbuf.st_mtime != mtime) {
    +
    241 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    242 err--;
    +
    243 }
    +
    244 if (err)
    +
    245 return -1;
    +
    246
    +
    247 return 0;
    +
    248}
    +
    249#endif
    +
    250
    +
    251static int st_check_nlink(struct stat *st, nlink_t nlink)
    +
    252{
    +
    253 if (st->st_nlink != nlink) {
    +
    254 ERROR("nlink %li instead of %li", (long) st->st_nlink,
    +
    255 (long) nlink);
    +
    256 return -1;
    +
    257 }
    +
    258 return 0;
    +
    259}
    +
    260
    +
    261static int check_nlink(const char *path, nlink_t nlink)
    +
    262{
    +
    263 struct stat stbuf;
    +
    264 int res = lstat(path, &stbuf);
    +
    265 if (res == -1) {
    +
    266 PERROR("lstat");
    +
    267 return -1;
    +
    268 }
    +
    269 return st_check_nlink(&stbuf, nlink);
    +
    270}
    +
    271
    +
    272static int fcheck_stat(int fd, int flags, struct stat *st)
    +
    273{
    +
    274 struct stat stbuf;
    +
    275 int res = fstat(fd, &stbuf);
    +
    276 if (res == -1) {
    +
    277 if (flags & O_PATH) {
    +
    278 // With O_PATH fd, the server does not have to keep
    +
    279 // the inode alive so FUSE inode may be stale or bad
    +
    280 if (errno == ESTALE || errno == EIO ||
    +
    281 errno == ENOENT || errno == EBADF)
    +
    282 return 0;
    +
    283 }
    +
    284 PERROR("fstat");
    +
    285 return -1;
    +
    286 }
    +
    287
    +
    288 int err = 0;
    +
    289 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
    +
    290 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
    +
    291 err += st_check_size(&stbuf, st->st_size);
    +
    292 err += st_check_nlink(&stbuf, st->st_nlink);
    +
    293
    +
    294 return err;
    +
    295}
    +
    296
    +
    297static int check_nonexist(const char *path)
    +
    298{
    +
    299 struct stat stbuf;
    +
    300 int res = lstat(path, &stbuf);
    +
    301 if (res == 0) {
    +
    302 ERROR("file should not exist");
    +
    303 return -1;
    +
    304 }
    +
    305 if (errno != ENOENT) {
    +
    306 ERROR("file should not exist: %s", strerror(errno));
    +
    307 return -1;
    +
    308 }
    +
    309 return 0;
    +
    310}
    +
    311
    +
    312static int check_buffer(const char *buf, const char *data, unsigned len)
    +
    313{
    +
    314 if (memcmp(buf, data, len) != 0) {
    +
    315 ERROR("data mismatch");
    +
    316 return -1;
    +
    317 }
    +
    318 return 0;
    +
    319}
    +
    320
    +
    321static int check_data(const char *path, const char *data, int offset,
    +
    322 unsigned len)
    +
    323{
    +
    324 char buf[4096];
    +
    325 int res;
    +
    326 int fd = open(path, O_RDONLY);
    +
    327 if (fd == -1) {
    +
    328 PERROR("open");
    +
    329 return -1;
    +
    330 }
    +
    331 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    332 PERROR("lseek");
    +
    333 close(fd);
    +
    334 return -1;
    +
    335 }
    +
    336 while (len) {
    +
    337 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    338 res = read(fd, buf, rdlen);
    +
    339 if (res == -1) {
    +
    340 PERROR("read");
    +
    341 close(fd);
    +
    342 return -1;
    +
    343 }
    +
    344 if (res != rdlen) {
    +
    345 ERROR("short read: %u instead of %u", res, rdlen);
    +
    346 close(fd);
    +
    347 return -1;
    +
    348 }
    +
    349 if (check_buffer(buf, data, rdlen) != 0) {
    +
    350 close(fd);
    +
    351 return -1;
    +
    352 }
    +
    353 data += rdlen;
    +
    354 len -= rdlen;
    +
    355 }
    +
    356 res = close(fd);
    +
    357 if (res == -1) {
    +
    358 PERROR("close");
    +
    359 return -1;
    +
    360 }
    +
    361 return 0;
    +
    362}
    +
    363
    +
    364static int fcheck_data(int fd, const char *data, int offset,
    +
    365 unsigned len)
    +
    366{
    +
    367 char buf[4096];
    +
    368 int res;
    +
    369 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    370 PERROR("lseek");
    +
    371 return -1;
    +
    372 }
    +
    373 while (len) {
    +
    374 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    375 res = read(fd, buf, rdlen);
    +
    376 if (res == -1) {
    +
    377 PERROR("read");
    +
    378 return -1;
    +
    379 }
    +
    380 if (res != rdlen) {
    +
    381 ERROR("short read: %u instead of %u", res, rdlen);
    +
    382 return -1;
    +
    383 }
    +
    384 if (check_buffer(buf, data, rdlen) != 0) {
    +
    385 return -1;
    +
    386 }
    +
    387 data += rdlen;
    +
    388 len -= rdlen;
    +
    389 }
    +
    390 return 0;
    +
    391}
    +
    392
    +
    393static int check_dir_contents(const char *path, const char **contents)
    +
    394{
    +
    395 int i;
    +
    396 int res;
    +
    397 int err = 0;
    +
    398 int found[MAX_ENTRIES];
    +
    399 const char *cont[MAX_ENTRIES];
    +
    400 DIR *dp;
    +
    401
    +
    402 for (i = 0; contents[i]; i++) {
    +
    403 assert(i < MAX_ENTRIES - 3);
    +
    404 found[i] = 0;
    +
    405 cont[i] = contents[i];
    +
    406 }
    +
    407 cont[i] = NULL;
    +
    408
    +
    409 dp = opendir(path);
    +
    410 if (dp == NULL) {
    +
    411 PERROR("opendir");
    +
    412 return -1;
    +
    413 }
    +
    414 memset(found, 0, sizeof(found));
    +
    415 while(1) {
    +
    416 struct dirent *de;
    +
    417 errno = 0;
    +
    418 de = readdir(dp);
    +
    419 if (de == NULL) {
    +
    420 if (errno) {
    +
    421 PERROR("readdir");
    +
    422 closedir(dp);
    +
    423 return -1;
    +
    424 }
    +
    425 break;
    +
    426 }
    +
    427 if (is_dot_or_dotdot(de->d_name))
    +
    428 continue;
    +
    429 for (i = 0; cont[i] != NULL; i++) {
    +
    430 assert(i < MAX_ENTRIES);
    +
    431 if (strcmp(cont[i], de->d_name) == 0) {
    +
    432 if (found[i]) {
    +
    433 ERROR("duplicate entry <%s>",
    +
    434 de->d_name);
    +
    435 err--;
    +
    436 } else
    +
    437 found[i] = 1;
    +
    438 break;
    +
    439 }
    +
    440 }
    +
    441 if (!cont[i]) {
    +
    442 ERROR("unexpected entry <%s>", de->d_name);
    +
    443 err --;
    +
    444 }
    +
    445 }
    +
    446 for (i = 0; cont[i] != NULL; i++) {
    +
    447 if (!found[i]) {
    +
    448 ERROR("missing entry <%s>", cont[i]);
    +
    449 err--;
    +
    450 }
    +
    451 }
    +
    452 res = closedir(dp);
    +
    453 if (res == -1) {
    +
    454 PERROR("closedir");
    +
    455 return -1;
    +
    456 }
    +
    457 if (err)
    +
    458 return -1;
    +
    459
    +
    460 return 0;
    +
    461}
    +
    462
    +
    463static int create_file(const char *path, const char *data, int len)
    +
    464{
    +
    465 int res;
    +
    466 int fd;
    +
    467
    +
    468 unlink(path);
    +
    469 fd = creat(path, 0644);
    +
    470 if (fd == -1) {
    +
    471 PERROR("creat");
    +
    472 return -1;
    +
    473 }
    +
    474 if (len) {
    +
    475 res = write(fd, data, len);
    +
    476 if (res == -1) {
    +
    477 PERROR("write");
    +
    478 close(fd);
    +
    479 return -1;
    +
    480 }
    +
    481 if (res != len) {
    +
    482 ERROR("write is short: %u instead of %u", res, len);
    +
    483 close(fd);
    +
    484 return -1;
    +
    485 }
    +
    486 }
    +
    487 res = close(fd);
    +
    488 if (res == -1) {
    +
    489 PERROR("close");
    +
    490 return -1;
    +
    491 }
    +
    492 res = check_type(path, S_IFREG);
    +
    493 if (res == -1)
    +
    494 return -1;
    +
    495 res = check_mode(path, 0644);
    +
    496 if (res == -1)
    +
    497 return -1;
    +
    498 res = check_nlink(path, 1);
    +
    499 if (res == -1)
    +
    500 return -1;
    +
    501 res = check_size(path, len);
    +
    502 if (res == -1)
    +
    503 return -1;
    +
    504
    +
    505 if (len) {
    +
    506 res = check_data(path, data, 0, len);
    +
    507 if (res == -1)
    +
    508 return -1;
    +
    509 }
    +
    510
    +
    511 return 0;
    +
    512}
    +
    513
    +
    514static int create_path_fd(const char *path, const char *data, int len)
    +
    515{
    +
    516 int path_fd;
    +
    517 int res;
    +
    518
    +
    519 res = create_file(path, data, len);
    +
    520 if (res == -1)
    +
    521 return -1;
    +
    522
    +
    523 path_fd = open(path, O_PATH);
    +
    524 if (path_fd == -1)
    +
    525 PERROR("open(O_PATH)");
    +
    526
    +
    527 return path_fd;
    +
    528}
    +
    529
    +
    530// Can be called once per test
    +
    531static int create_testfile(const char *path, const char *data, int len)
    +
    532{
    +
    533 struct test *t = this_test;
    +
    534 struct stat *st = &t->stat;
    +
    535 int res, fd;
    +
    536
    +
    537 if (t->fd > 0) {
    +
    538 ERROR("testfile already created");
    +
    539 return -1;
    +
    540 }
    +
    541
    +
    542 fd = create_path_fd(path, data, len);
    +
    543 if (fd == -1)
    +
    544 return -1;
    +
    545
    +
    546 t->fd = fd;
    +
    547
    +
    548 res = fstat(fd, st);
    +
    549 if (res == -1) {
    +
    550 PERROR("fstat");
    +
    551 return -1;
    +
    552 }
    +
    553
    +
    554 return 0;
    +
    555}
    +
    556
    +
    557static int check_unlinked_testfile(int fd)
    +
    558{
    +
    559 struct stat *st = &this_test->stat;
    +
    560
    +
    561 st->st_nlink = 0;
    +
    562 return fcheck_stat(fd, O_PATH, st);
    +
    563}
    +
    564
    +
    565// Check recorded testfiles after all tests completed
    +
    566static int check_unlinked_testfiles(void)
    +
    567{
    +
    568 int fd;
    +
    569 int res, err = 0;
    +
    570 int num = testnum;
    +
    571
    +
    572 if (!unlinked_test)
    +
    573 return 0;
    +
    574
    +
    575 testnum = 0;
    +
    576 while (testnum < num) {
    +
    577 fd = next_test->fd;
    +
    578 start_test("check_unlinked_testfile");
    +
    579 if (fd == -1)
    +
    580 continue;
    +
    581
    +
    582 err += check_unlinked_testfile(fd);
    +
    583 res = close(fd);
    +
    584 if (res == -1) {
    +
    585 PERROR("close(test_fd)");
    +
    586 err--;
    +
    587 }
    +
    588 }
    +
    589
    +
    590 if (err) {
    +
    591 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
    +
    592 return 1;
    +
    593 }
    +
    594
    +
    595 return err;
    +
    596}
    +
    597
    +
    598static int cleanup_dir(const char *path, const char **dir_files, int quiet)
    +
    599{
    +
    600 int i;
    +
    601 int err = 0;
    +
    602
    +
    603 for (i = 0; dir_files[i]; i++) {
    +
    604 int res;
    +
    605 char fpath[1280];
    +
    606 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    607 res = unlink(fpath);
    +
    608 if (res == -1 && !quiet) {
    +
    609 PERROR("unlink");
    +
    610 err --;
    +
    611 }
    +
    612 }
    +
    613 if (err)
    +
    614 return -1;
    +
    615
    +
    616 return 0;
    +
    617}
    +
    618
    +
    619static int create_dir(const char *path, const char **dir_files)
    +
    620{
    +
    621 int res;
    +
    622 int i;
    +
    623
    +
    624 rmdir(path);
    +
    625 res = mkdir(path, 0755);
    +
    626 if (res == -1) {
    +
    627 PERROR("mkdir");
    +
    628 return -1;
    +
    629 }
    +
    630 res = check_type(path, S_IFDIR);
    +
    631 if (res == -1)
    +
    632 return -1;
    +
    633 res = check_mode(path, 0755);
    +
    634 if (res == -1)
    +
    635 return -1;
    +
    636
    +
    637 for (i = 0; dir_files[i]; i++) {
    +
    638 char fpath[1280];
    +
    639 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    640 res = create_file(fpath, "", 0);
    +
    641 if (res == -1) {
    +
    642 cleanup_dir(path, dir_files, 1);
    +
    643 return -1;
    +
    644 }
    +
    645 }
    +
    646 res = check_dir_contents(path, dir_files);
    +
    647 if (res == -1) {
    +
    648 cleanup_dir(path, dir_files, 1);
    +
    649 return -1;
    +
    650 }
    +
    651
    +
    652 return 0;
    +
    653}
    +
    654
    +
    655static int test_truncate(int len)
    +
    656{
    +
    657 const char *data = testdata;
    +
    658 int datalen = testdatalen;
    +
    659 int res;
    +
    660
    +
    661 start_test("truncate(%u)", (int) len);
    +
    662 res = create_testfile(testfile, data, datalen);
    +
    663 if (res == -1)
    +
    664 return -1;
    +
    665
    +
    666 res = truncate(testfile, len);
    +
    667 if (res == -1) {
    +
    668 PERROR("truncate");
    +
    669 return -1;
    +
    670 }
    +
    671 res = check_testfile_size(testfile, len);
    +
    672 if (res == -1)
    +
    673 return -1;
    +
    674
    +
    675 if (len > 0) {
    +
    676 if (len <= datalen) {
    +
    677 res = check_data(testfile, data, 0, len);
    +
    678 if (res == -1)
    +
    679 return -1;
    +
    680 } else {
    +
    681 res = check_data(testfile, data, 0, datalen);
    +
    682 if (res == -1)
    +
    683 return -1;
    +
    684 res = check_data(testfile, zerodata, datalen,
    +
    685 len - datalen);
    +
    686 if (res == -1)
    +
    687 return -1;
    +
    688 }
    +
    689 }
    +
    690 res = unlink(testfile);
    +
    691 if (res == -1) {
    +
    692 PERROR("unlink");
    +
    693 return -1;
    +
    694 }
    +
    695 res = check_nonexist(testfile);
    +
    696 if (res == -1)
    +
    697 return -1;
    +
    698
    +
    699 success();
    +
    700 return 0;
    +
    701}
    +
    702
    +
    703static int test_ftruncate(int len, int mode)
    +
    704{
    +
    705 const char *data = testdata;
    +
    706 int datalen = testdatalen;
    +
    707 int res;
    +
    708 int fd;
    +
    709
    +
    710 start_test("ftruncate(%u) mode: 0%03o", len, mode);
    +
    711 res = create_testfile(testfile, data, datalen);
    +
    712 if (res == -1)
    +
    713 return -1;
    +
    714
    +
    715 fd = open(testfile, O_WRONLY);
    +
    716 if (fd == -1) {
    +
    717 PERROR("open");
    +
    718 return -1;
    +
    719 }
    +
    720
    +
    721 res = fchmod(fd, mode);
    +
    722 if (res == -1) {
    +
    723 PERROR("fchmod");
    +
    724 close(fd);
    +
    725 return -1;
    +
    726 }
    +
    727 res = check_testfile_mode(testfile, mode);
    +
    728 if (res == -1) {
    +
    729 close(fd);
    +
    730 return -1;
    +
    731 }
    +
    732 res = ftruncate(fd, len);
    +
    733 if (res == -1) {
    +
    734 PERROR("ftruncate");
    +
    735 close(fd);
    +
    736 return -1;
    +
    737 }
    +
    738 close(fd);
    +
    739 res = check_testfile_size(testfile, len);
    +
    740 if (res == -1)
    +
    741 return -1;
    +
    742
    +
    743 if (len > 0) {
    +
    744 if (len <= datalen) {
    +
    745 res = check_data(testfile, data, 0, len);
    +
    746 if (res == -1)
    +
    747 return -1;
    +
    748 } else {
    +
    749 res = check_data(testfile, data, 0, datalen);
    +
    750 if (res == -1)
    +
    751 return -1;
    +
    752 res = check_data(testfile, zerodata, datalen,
    +
    753 len - datalen);
    +
    754 if (res == -1)
    +
    755 return -1;
    +
    756 }
    +
    757 }
    +
    758 res = unlink(testfile);
    +
    759 if (res == -1) {
    +
    760 PERROR("unlink");
    +
    761 return -1;
    +
    762 }
    +
    763 res = check_nonexist(testfile);
    +
    764 if (res == -1)
    +
    765 return -1;
    +
    766
    +
    767 success();
    +
    768 return 0;
    +
    769}
    +
    770
    +
    771static int test_seekdir(void)
    +
    772{
    +
    773 int i;
    +
    774 int res;
    +
    775 DIR *dp;
    +
    776 struct dirent *de = NULL;
    +
    777
    +
    778 start_test("seekdir");
    +
    779 res = create_dir(testdir, testdir_files);
    +
    780 if (res == -1)
    +
    781 return res;
    +
    782
    +
    783 dp = opendir(testdir);
    +
    784 if (dp == NULL) {
    +
    785 PERROR("opendir");
    +
    786 return -1;
    +
    787 }
    +
    788
    +
    789 /* Remember dir offsets */
    +
    790 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
    +
    791 seekdir_offsets[i] = telldir(dp);
    +
    792 errno = 0;
    +
    793 de = readdir(dp);
    +
    794 if (de == NULL) {
    +
    795 if (errno) {
    +
    796 PERROR("readdir");
    +
    797 goto fail;
    +
    798 }
    +
    799 break;
    +
    800 }
    +
    801 }
    +
    802
    +
    803 /* Walk until the end of directory */
    +
    804 while (de)
    +
    805 de = readdir(dp);
    +
    806
    +
    807 /* Start from the last valid dir offset and seek backwards */
    +
    808 for (i--; i >= 0; i--) {
    +
    809 seekdir(dp, seekdir_offsets[i]);
    +
    810 de = readdir(dp);
    +
    811 if (de == NULL) {
    +
    812 ERROR("Unexpected end of directory after seekdir()");
    +
    813 goto fail;
    +
    814 }
    +
    815 }
    +
    816
    +
    817 closedir(dp);
    +
    818 res = cleanup_dir(testdir, testdir_files, 0);
    +
    819 if (!res)
    +
    820 success();
    +
    821 return res;
    +
    822fail:
    +
    823 closedir(dp);
    +
    824 cleanup_dir(testdir, testdir_files, 1);
    +
    825 return -1;
    +
    826}
    +
    827
    +
    828#ifdef HAVE_COPY_FILE_RANGE
    +
    829static int test_copy_file_range(void)
    +
    830{
    +
    831 const char *data = testdata;
    +
    832 int datalen = testdatalen;
    +
    833 int err = 0;
    +
    834 int res;
    +
    835 int fd_in, fd_out;
    +
    836 off_t pos_in = 0, pos_out = 0;
    +
    837
    +
    838 start_test("copy_file_range");
    +
    839 unlink(testfile);
    +
    840 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
    +
    841 if (fd_in == -1) {
    +
    842 PERROR("creat");
    +
    843 return -1;
    +
    844 }
    +
    845 res = write(fd_in, data, datalen);
    +
    846 if (res == -1) {
    +
    847 PERROR("write");
    +
    848 close(fd_in);
    +
    849 return -1;
    +
    850 }
    +
    851 if (res != datalen) {
    +
    852 ERROR("write is short: %u instead of %u", res, datalen);
    +
    853 close(fd_in);
    +
    854 return -1;
    +
    855 }
    +
    856
    +
    857 unlink(testfile2);
    +
    858 fd_out = creat(testfile2, 0644);
    +
    859 if (fd_out == -1) {
    +
    860 PERROR("creat");
    +
    861 close(fd_in);
    +
    862 return -1;
    +
    863 }
    +
    864 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
    +
    865 if (res == -1) {
    +
    866 PERROR("copy_file_range");
    +
    867 close(fd_in);
    +
    868 close(fd_out);
    +
    869 return -1;
    +
    870 }
    +
    871 if (res != datalen) {
    +
    872 ERROR("copy is short: %u instead of %u", res, datalen);
    +
    873 close(fd_in);
    +
    874 close(fd_out);
    +
    875 return -1;
    +
    876 }
    +
    877
    +
    878 res = close(fd_in);
    +
    879 if (res == -1) {
    +
    880 PERROR("close");
    +
    881 close(fd_out);
    +
    882 return -1;
    +
    883 }
    +
    884 res = close(fd_out);
    +
    885 if (res == -1) {
    +
    886 PERROR("close");
    +
    887 return -1;
    +
    888 }
    +
    889
    +
    890 err = check_data(testfile2, data, 0, datalen);
    +
    891
    +
    892 res = unlink(testfile);
    +
    893 if (res == -1) {
    +
    894 PERROR("unlink");
    +
    895 return -1;
    +
    896 }
    +
    897 res = check_nonexist(testfile);
    +
    898 if (res == -1)
    +
    899 return -1;
    +
    900 if (err)
    +
    901 return -1;
    +
    902
    +
    903 res = unlink(testfile2);
    +
    904 if (res == -1) {
    +
    905 PERROR("unlink");
    +
    906 return -1;
    +
    907 }
    +
    908 res = check_nonexist(testfile2);
    +
    909 if (res == -1)
    +
    910 return -1;
    +
    911 if (err)
    +
    912 return -1;
    +
    913
    +
    914 success();
    +
    915 return 0;
    +
    916}
    +
    917#else
    +
    918static int test_copy_file_range(void)
    +
    919{
    +
    920 return 0;
    +
    921}
    +
    922#endif
    +
    923
    +
    924static int test_utime(void)
    +
    925{
    +
    926 struct utimbuf utm;
    +
    927 time_t atime = 987631200;
    +
    928 time_t mtime = 123116400;
    +
    929 int res;
    +
    930
    +
    931 start_test("utime");
    +
    932 res = create_testfile(testfile, NULL, 0);
    +
    933 if (res == -1)
    +
    934 return -1;
    +
    935
    +
    936 utm.actime = atime;
    +
    937 utm.modtime = mtime;
    +
    938 res = utime(testfile, &utm);
    +
    939 if (res == -1) {
    +
    940 PERROR("utime");
    +
    941 return -1;
    +
    942 }
    +
    943 res = check_times(testfile, atime, mtime);
    +
    944 if (res == -1) {
    +
    945 return -1;
    +
    946 }
    +
    947 res = unlink(testfile);
    +
    948 if (res == -1) {
    +
    949 PERROR("unlink");
    +
    950 return -1;
    +
    951 }
    +
    952 res = check_nonexist(testfile);
    +
    953 if (res == -1)
    +
    954 return -1;
    +
    955
    +
    956 success();
    +
    957 return 0;
    +
    958}
    +
    959
    +
    960static int test_create(void)
    +
    961{
    +
    962 const char *data = testdata;
    +
    963 int datalen = testdatalen;
    +
    964 int err = 0;
    +
    965 int res;
    +
    966 int fd;
    +
    967
    +
    968 start_test("create");
    +
    969 unlink(testfile);
    +
    970 fd = creat(testfile, 0644);
    +
    971 if (fd == -1) {
    +
    972 PERROR("creat");
    +
    973 return -1;
    +
    974 }
    +
    975 res = write(fd, data, datalen);
    +
    976 if (res == -1) {
    +
    977 PERROR("write");
    +
    978 close(fd);
    +
    979 return -1;
    +
    980 }
    +
    981 if (res != datalen) {
    +
    982 ERROR("write is short: %u instead of %u", res, datalen);
    +
    983 close(fd);
    +
    984 return -1;
    +
    985 }
    +
    986 res = close(fd);
    +
    987 if (res == -1) {
    +
    988 PERROR("close");
    +
    989 return -1;
    +
    990 }
    +
    991 res = check_type(testfile, S_IFREG);
    +
    992 if (res == -1)
    +
    993 return -1;
    +
    994 err += check_mode(testfile, 0644);
    +
    995 err += check_nlink(testfile, 1);
    +
    996 err += check_size(testfile, datalen);
    +
    997 err += check_data(testfile, data, 0, datalen);
    +
    998 res = unlink(testfile);
    +
    999 if (res == -1) {
    +
    1000 PERROR("unlink");
    +
    1001 return -1;
    +
    1002 }
    +
    1003 res = check_nonexist(testfile);
    +
    1004 if (res == -1)
    +
    1005 return -1;
    +
    1006 if (err)
    +
    1007 return -1;
    +
    1008
    +
    1009 success();
    +
    1010 return 0;
    +
    1011}
    +
    1012
    +
    1013static int test_create_unlink(void)
    +
    1014{
    +
    1015 const char *data = testdata;
    +
    1016 int datalen = testdatalen;
    +
    1017 int err = 0;
    +
    1018 int res;
    +
    1019 int fd;
    +
    1020
    +
    1021 start_test("create+unlink");
    +
    1022 unlink(testfile);
    +
    1023 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
    +
    1024 if (fd == -1) {
    +
    1025 PERROR("creat");
    +
    1026 return -1;
    +
    1027 }
    +
    1028 res = unlink(testfile);
    +
    1029 if (res == -1) {
    +
    1030 PERROR("unlink");
    +
    1031 close(fd);
    +
    1032 return -1;
    +
    1033 }
    +
    1034 res = check_nonexist(testfile);
    +
    1035 if (res == -1) {
    +
    1036 close(fd);
    +
    1037 return -1;
    +
    1038 }
    +
    1039 res = write(fd, data, datalen);
    +
    1040 if (res == -1) {
    +
    1041 PERROR("write");
    +
    1042 close(fd);
    +
    1043 return -1;
    +
    1044 }
    +
    1045 if (res != datalen) {
    +
    1046 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1047 close(fd);
    +
    1048 return -1;
    +
    1049 }
    +
    1050 struct stat st = {
    +
    1051 .st_mode = S_IFREG | 0644,
    +
    1052 .st_size = datalen,
    +
    1053 };
    +
    1054 err = fcheck_stat(fd, O_RDWR, &st);
    +
    1055 err += fcheck_data(fd, data, 0, datalen);
    +
    1056 res = close(fd);
    +
    1057 if (res == -1) {
    +
    1058 PERROR("close");
    +
    1059 err--;
    +
    1060 }
    +
    1061 if (err)
    +
    1062 return -1;
    +
    1063
    +
    1064 success();
    +
    1065 return 0;
    +
    1066}
    +
    1067
    +
    1068static int test_mknod(void)
    +
    1069{
    +
    1070 int err = 0;
    +
    1071 int res;
    +
    1072
    +
    1073 start_test("mknod");
    +
    1074 unlink(testfile);
    +
    1075 res = mknod(testfile, 0644, 0);
    +
    1076 if (res == -1) {
    +
    1077 PERROR("mknod");
    +
    1078 return -1;
    +
    1079 }
    +
    1080 res = check_type(testfile, S_IFREG);
    +
    1081 if (res == -1)
    +
    1082 return -1;
    +
    1083 err += check_mode(testfile, 0644);
    +
    1084 err += check_nlink(testfile, 1);
    +
    1085 err += check_size(testfile, 0);
    +
    1086 res = unlink(testfile);
    +
    1087 if (res == -1) {
    +
    1088 PERROR("unlink");
    +
    1089 return -1;
    +
    1090 }
    +
    1091 res = check_nonexist(testfile);
    +
    1092 if (res == -1)
    +
    1093 return -1;
    +
    1094 if (err)
    +
    1095 return -1;
    +
    1096
    +
    1097 success();
    +
    1098 return 0;
    +
    1099}
    +
    1100
    +
    1101#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
    +
    1102
    +
    1103static int do_test_open(int exist, int flags, const char *flags_str, int mode)
    +
    1104{
    +
    1105 char buf[4096];
    +
    1106 const char *data = testdata;
    +
    1107 int datalen = testdatalen;
    +
    1108 unsigned currlen = 0;
    +
    1109 int err = 0;
    +
    1110 int res;
    +
    1111 int fd;
    +
    1112 off_t off;
    +
    1113
    +
    1114 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
    +
    1115 unlink(testfile);
    +
    1116 if (exist) {
    +
    1117 res = create_file(testfile_r, testdata2, testdata2len);
    +
    1118 if (res == -1)
    +
    1119 return -1;
    +
    1120
    +
    1121 currlen = testdata2len;
    +
    1122 }
    +
    1123
    +
    1124 fd = open(testfile, flags, mode);
    +
    1125 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
    +
    1126 if (fd != -1) {
    +
    1127 ERROR("open should have failed");
    +
    1128 close(fd);
    +
    1129 return -1;
    +
    1130 } else if (errno == EEXIST)
    +
    1131 goto succ;
    +
    1132 }
    +
    1133 if (!(flags & O_CREAT) && !exist) {
    +
    1134 if (fd != -1) {
    +
    1135 ERROR("open should have failed");
    +
    1136 close(fd);
    +
    1137 return -1;
    +
    1138 } else if (errno == ENOENT)
    +
    1139 goto succ;
    +
    1140 }
    +
    1141 if (fd == -1) {
    +
    1142 PERROR("open");
    +
    1143 return -1;
    +
    1144 }
    +
    1145
    +
    1146 if (flags & O_TRUNC)
    +
    1147 currlen = 0;
    +
    1148
    +
    1149 err += check_type(testfile, S_IFREG);
    +
    1150 if (exist)
    +
    1151 err += check_mode(testfile, 0644);
    +
    1152 else
    +
    1153 err += check_mode(testfile, mode);
    +
    1154 err += check_nlink(testfile, 1);
    +
    1155 err += check_size(testfile, currlen);
    +
    1156 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
    +
    1157 err += check_data(testfile, testdata2, 0, testdata2len);
    +
    1158
    +
    1159 res = write(fd, data, datalen);
    +
    1160 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1161 if (res == -1) {
    +
    1162 PERROR("write");
    +
    1163 err --;
    +
    1164 } else if (res != datalen) {
    +
    1165 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1166 err --;
    +
    1167 } else {
    +
    1168 if (datalen > (int) currlen)
    +
    1169 currlen = datalen;
    +
    1170
    +
    1171 err += check_size(testfile, currlen);
    +
    1172
    +
    1173 if (mode & S_IRUSR) {
    +
    1174 err += check_data(testfile, data, 0, datalen);
    +
    1175 if (exist && !(flags & O_TRUNC) &&
    +
    1176 testdata2len > datalen)
    +
    1177 err += check_data(testfile,
    +
    1178 testdata2 + datalen,
    +
    1179 datalen,
    +
    1180 testdata2len - datalen);
    +
    1181 }
    +
    1182 }
    +
    1183 } else {
    +
    1184 if (res != -1) {
    +
    1185 ERROR("write should have failed");
    +
    1186 err --;
    +
    1187 } else if (errno != EBADF) {
    +
    1188 PERROR("write");
    +
    1189 err --;
    +
    1190 }
    +
    1191 }
    +
    1192 off = lseek(fd, SEEK_SET, 0);
    +
    1193 if (off == (off_t) -1) {
    +
    1194 PERROR("lseek");
    +
    1195 err--;
    +
    1196 } else if (off != 0) {
    +
    1197 ERROR("offset should have returned 0");
    +
    1198 err --;
    +
    1199 }
    +
    1200 res = read(fd, buf, sizeof(buf));
    +
    1201 if ((flags & O_ACCMODE) != O_WRONLY) {
    +
    1202 if (res == -1) {
    +
    1203 PERROR("read");
    +
    1204 err--;
    +
    1205 } else {
    +
    1206 int readsize =
    +
    1207 currlen < sizeof(buf) ? currlen : sizeof(buf);
    +
    1208 if (res != readsize) {
    +
    1209 ERROR("read is short: %i instead of %u",
    +
    1210 res, readsize);
    +
    1211 err--;
    +
    1212 } else {
    +
    1213 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1214 err += check_buffer(buf, data, datalen);
    +
    1215 if (exist && !(flags & O_TRUNC) &&
    +
    1216 testdata2len > datalen)
    +
    1217 err += check_buffer(buf + datalen,
    +
    1218 testdata2 + datalen,
    +
    1219 testdata2len - datalen);
    +
    1220 } else if (exist)
    +
    1221 err += check_buffer(buf, testdata2,
    +
    1222 testdata2len);
    +
    1223 }
    +
    1224 }
    +
    1225 } else {
    +
    1226 if (res != -1) {
    +
    1227 ERROR("read should have failed");
    +
    1228 err --;
    +
    1229 } else if (errno != EBADF) {
    +
    1230 PERROR("read");
    +
    1231 err --;
    +
    1232 }
    +
    1233 }
    +
    1234
    +
    1235 res = close(fd);
    +
    1236 if (res == -1) {
    +
    1237 PERROR("close");
    +
    1238 return -1;
    +
    1239 }
    +
    1240 res = unlink(testfile);
    +
    1241 if (res == -1) {
    +
    1242 PERROR("unlink");
    +
    1243 return -1;
    +
    1244 }
    +
    1245 res = check_nonexist(testfile);
    +
    1246 if (res == -1)
    +
    1247 return -1;
    +
    1248 res = check_nonexist(testfile_r);
    +
    1249 if (res == -1)
    +
    1250 return -1;
    +
    1251 if (err)
    +
    1252 return -1;
    +
    1253
    +
    1254succ:
    +
    1255 success();
    +
    1256 return 0;
    +
    1257}
    +
    1258
    +
    1259#define test_open_acc(flags, mode, err) \
    +
    1260 do_test_open_acc(flags, #flags, mode, err)
    +
    1261
    +
    1262static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
    +
    1263{
    +
    1264 const char *data = testdata;
    +
    1265 int datalen = testdatalen;
    +
    1266 int res;
    +
    1267 int fd;
    +
    1268
    +
    1269 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
    +
    1270 strerror(err));
    +
    1271 unlink(testfile);
    +
    1272 res = create_testfile(testfile, data, datalen);
    +
    1273 if (res == -1)
    +
    1274 return -1;
    +
    1275
    +
    1276 res = chmod(testfile, mode);
    +
    1277 if (res == -1) {
    +
    1278 PERROR("chmod");
    +
    1279 return -1;
    +
    1280 }
    +
    1281
    +
    1282 res = check_testfile_mode(testfile, mode);
    +
    1283 if (res == -1)
    +
    1284 return -1;
    +
    1285
    +
    1286 fd = open(testfile, flags);
    +
    1287 if (fd == -1) {
    +
    1288 if (err != errno) {
    +
    1289 PERROR("open");
    +
    1290 return -1;
    +
    1291 }
    +
    1292 } else {
    +
    1293 if (err) {
    +
    1294 ERROR("open should have failed");
    +
    1295 close(fd);
    +
    1296 return -1;
    +
    1297 }
    +
    1298 close(fd);
    +
    1299 }
    +
    1300
    +
    1301 res = unlink(testfile);
    +
    1302 if (res == -1) {
    +
    1303 PERROR("unlink");
    +
    1304 return -1;
    +
    1305 }
    +
    1306 res = check_nonexist(testfile);
    +
    1307 if (res == -1)
    +
    1308 return -1;
    +
    1309 res = check_nonexist(testfile_r);
    +
    1310 if (res == -1)
    +
    1311 return -1;
    +
    1312
    +
    1313 success();
    +
    1314 return 0;
    +
    1315}
    +
    1316
    +
    1317static int test_symlink(void)
    +
    1318{
    +
    1319 char buf[1024];
    +
    1320 const char *data = testdata;
    +
    1321 int datalen = testdatalen;
    +
    1322 int linklen = strlen(testfile);
    +
    1323 int err = 0;
    +
    1324 int res;
    +
    1325
    +
    1326 start_test("symlink");
    +
    1327 res = create_testfile(testfile, data, datalen);
    +
    1328 if (res == -1)
    +
    1329 return -1;
    +
    1330
    +
    1331 unlink(testfile2);
    +
    1332 res = symlink(testfile, testfile2);
    +
    1333 if (res == -1) {
    +
    1334 PERROR("symlink");
    +
    1335 return -1;
    +
    1336 }
    +
    1337 res = check_type(testfile2, S_IFLNK);
    +
    1338 if (res == -1)
    +
    1339 return -1;
    +
    1340 err += check_mode(testfile2, 0777);
    +
    1341 err += check_nlink(testfile2, 1);
    +
    1342 res = readlink(testfile2, buf, sizeof(buf));
    +
    1343 if (res == -1) {
    +
    1344 PERROR("readlink");
    +
    1345 err--;
    +
    1346 }
    +
    1347 if (res != linklen) {
    +
    1348 ERROR("short readlink: %u instead of %u", res, linklen);
    +
    1349 err--;
    +
    1350 }
    +
    1351 if (memcmp(buf, testfile, linklen) != 0) {
    +
    1352 ERROR("link mismatch");
    +
    1353 err--;
    +
    1354 }
    +
    1355 err += check_size(testfile2, datalen);
    +
    1356 err += check_data(testfile2, data, 0, datalen);
    +
    1357 res = unlink(testfile2);
    +
    1358 if (res == -1) {
    +
    1359 PERROR("unlink");
    +
    1360 return -1;
    +
    1361 }
    +
    1362 res = check_nonexist(testfile2);
    +
    1363 if (res == -1)
    +
    1364 return -1;
    +
    1365 if (err)
    +
    1366 return -1;
    +
    1367
    +
    1368 res = unlink(testfile);
    +
    1369 if (res == -1) {
    +
    1370 PERROR("unlink");
    +
    1371 return -1;
    +
    1372 }
    +
    1373 res = check_nonexist(testfile);
    +
    1374 if (res == -1)
    +
    1375 return -1;
    +
    1376
    +
    1377 success();
    +
    1378 return 0;
    +
    1379}
    +
    1380
    +
    1381static int test_link(void)
    +
    1382{
    +
    1383 const char *data = testdata;
    +
    1384 int datalen = testdatalen;
    +
    1385 int err = 0;
    +
    1386 int res;
    +
    1387
    +
    1388 start_test("link");
    +
    1389 res = create_testfile(testfile, data, datalen);
    +
    1390 if (res == -1)
    +
    1391 return -1;
    +
    1392
    +
    1393 unlink(testfile2);
    +
    1394 res = link(testfile, testfile2);
    +
    1395 if (res == -1) {
    +
    1396 PERROR("link");
    +
    1397 return -1;
    +
    1398 }
    +
    1399 res = check_type(testfile2, S_IFREG);
    +
    1400 if (res == -1)
    +
    1401 return -1;
    +
    1402 err += check_mode(testfile2, 0644);
    +
    1403 err += check_nlink(testfile2, 2);
    +
    1404 err += check_size(testfile2, datalen);
    +
    1405 err += check_data(testfile2, data, 0, datalen);
    +
    1406 res = unlink(testfile);
    +
    1407 if (res == -1) {
    +
    1408 PERROR("unlink");
    +
    1409 return -1;
    +
    1410 }
    +
    1411 res = check_nonexist(testfile);
    +
    1412 if (res == -1)
    +
    1413 return -1;
    +
    1414
    +
    1415 err += check_nlink(testfile2, 1);
    +
    1416 res = unlink(testfile2);
    +
    1417 if (res == -1) {
    +
    1418 PERROR("unlink");
    +
    1419 return -1;
    +
    1420 }
    +
    1421 res = check_nonexist(testfile2);
    +
    1422 if (res == -1)
    +
    1423 return -1;
    +
    1424 if (err)
    +
    1425 return -1;
    +
    1426
    +
    1427 success();
    +
    1428 return 0;
    +
    1429}
    +
    1430
    +
    1431static int test_link2(void)
    +
    1432{
    +
    1433 const char *data = testdata;
    +
    1434 int datalen = testdatalen;
    +
    1435 int err = 0;
    +
    1436 int res;
    +
    1437
    +
    1438 start_test("link-unlink-link");
    +
    1439 res = create_testfile(testfile, data, datalen);
    +
    1440 if (res == -1)
    +
    1441 return -1;
    +
    1442
    +
    1443 unlink(testfile2);
    +
    1444 res = link(testfile, testfile2);
    +
    1445 if (res == -1) {
    +
    1446 PERROR("link");
    +
    1447 return -1;
    +
    1448 }
    +
    1449 res = unlink(testfile);
    +
    1450 if (res == -1) {
    +
    1451 PERROR("unlink");
    +
    1452 return -1;
    +
    1453 }
    +
    1454 res = check_nonexist(testfile);
    +
    1455 if (res == -1)
    +
    1456 return -1;
    +
    1457 res = link(testfile2, testfile);
    +
    1458 if (res == -1) {
    +
    1459 PERROR("link");
    +
    1460 }
    +
    1461 res = check_type(testfile, S_IFREG);
    +
    1462 if (res == -1)
    +
    1463 return -1;
    +
    1464 err += check_mode(testfile, 0644);
    +
    1465 err += check_nlink(testfile, 2);
    +
    1466 err += check_size(testfile, datalen);
    +
    1467 err += check_data(testfile, data, 0, datalen);
    +
    1468
    +
    1469 res = unlink(testfile2);
    +
    1470 if (res == -1) {
    +
    1471 PERROR("unlink");
    +
    1472 return -1;
    +
    1473 }
    +
    1474 err += check_nlink(testfile, 1);
    +
    1475 res = unlink(testfile);
    +
    1476 if (res == -1) {
    +
    1477 PERROR("unlink");
    +
    1478 return -1;
    +
    1479 }
    +
    1480 res = check_nonexist(testfile);
    +
    1481 if (res == -1)
    +
    1482 return -1;
    +
    1483 if (err)
    +
    1484 return -1;
    +
    1485
    +
    1486 success();
    +
    1487 return 0;
    +
    1488}
    +
    1489
    +
    1490static int test_rename_file(void)
    +
    1491{
    +
    1492 const char *data = testdata;
    +
    1493 int datalen = testdatalen;
    +
    1494 int err = 0;
    +
    1495 int res;
    +
    1496
    +
    1497 start_test("rename file");
    +
    1498 res = create_testfile(testfile, data, datalen);
    +
    1499 if (res == -1)
    +
    1500 return -1;
    +
    1501
    +
    1502 unlink(testfile2);
    +
    1503 res = rename(testfile, testfile2);
    +
    1504 if (res == -1) {
    +
    1505 PERROR("rename");
    +
    1506 return -1;
    +
    1507 }
    +
    1508 res = check_nonexist(testfile);
    +
    1509 if (res == -1)
    +
    1510 return -1;
    +
    1511 res = check_type(testfile2, S_IFREG);
    +
    1512 if (res == -1)
    +
    1513 return -1;
    +
    1514 err += check_mode(testfile2, 0644);
    +
    1515 err += check_nlink(testfile2, 1);
    +
    1516 err += check_size(testfile2, datalen);
    +
    1517 err += check_data(testfile2, data, 0, datalen);
    +
    1518 res = unlink(testfile2);
    +
    1519 if (res == -1) {
    +
    1520 PERROR("unlink");
    +
    1521 return -1;
    +
    1522 }
    +
    1523 res = check_nonexist(testfile2);
    +
    1524 if (res == -1)
    +
    1525 return -1;
    +
    1526 if (err)
    +
    1527 return -1;
    +
    1528
    +
    1529 success();
    +
    1530 return 0;
    +
    1531}
    +
    1532
    +
    1533static int test_rename_dir(void)
    +
    1534{
    +
    1535 int err = 0;
    +
    1536 int res;
    +
    1537
    +
    1538 start_test("rename dir");
    +
    1539 res = create_dir(testdir, testdir_files);
    +
    1540 if (res == -1)
    +
    1541 return -1;
    +
    1542
    +
    1543 rmdir(testdir2);
    +
    1544 res = rename(testdir, testdir2);
    +
    1545 if (res == -1) {
    +
    1546 PERROR("rename");
    +
    1547 cleanup_dir(testdir, testdir_files, 1);
    +
    1548 return -1;
    +
    1549 }
    +
    1550 res = check_nonexist(testdir);
    +
    1551 if (res == -1) {
    +
    1552 cleanup_dir(testdir, testdir_files, 1);
    +
    1553 return -1;
    +
    1554 }
    +
    1555 res = check_type(testdir2, S_IFDIR);
    +
    1556 if (res == -1) {
    +
    1557 cleanup_dir(testdir2, testdir_files, 1);
    +
    1558 return -1;
    +
    1559 }
    +
    1560 err += check_mode(testdir2, 0755);
    +
    1561 err += check_dir_contents(testdir2, testdir_files);
    +
    1562 err += cleanup_dir(testdir2, testdir_files, 0);
    +
    1563 res = rmdir(testdir2);
    +
    1564 if (res == -1) {
    +
    1565 PERROR("rmdir");
    +
    1566 return -1;
    +
    1567 }
    +
    1568 res = check_nonexist(testdir2);
    +
    1569 if (res == -1)
    +
    1570 return -1;
    +
    1571 if (err)
    +
    1572 return -1;
    +
    1573
    +
    1574 success();
    +
    1575 return 0;
    +
    1576}
    +
    1577
    +
    1578static int test_rename_dir_loop(void)
    +
    1579{
    +
    1580#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
    +
    1581#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
    +
    1582
    +
    1583 char path[1280], path2[1280];
    +
    1584 int err = 0;
    +
    1585 int res;
    +
    1586
    +
    1587 start_test("rename dir loop");
    +
    1588
    +
    1589 res = create_dir(testdir, testdir_files);
    +
    1590 if (res == -1)
    +
    1591 return -1;
    +
    1592
    +
    1593 res = mkdir(PATH("a"), 0755);
    +
    1594 if (res == -1) {
    +
    1595 PERROR("mkdir");
    +
    1596 goto fail;
    +
    1597 }
    +
    1598
    +
    1599 res = rename(PATH("a"), PATH2("a"));
    +
    1600 if (res == -1) {
    +
    1601 PERROR("rename");
    +
    1602 goto fail;
    +
    1603 }
    +
    1604
    +
    1605 errno = 0;
    +
    1606 res = rename(PATH("a"), PATH2("a/b"));
    +
    1607 if (res == 0 || errno != EINVAL) {
    +
    1608 PERROR("rename");
    +
    1609 goto fail;
    +
    1610 }
    +
    1611
    +
    1612 res = mkdir(PATH("a/b"), 0755);
    +
    1613 if (res == -1) {
    +
    1614 PERROR("mkdir");
    +
    1615 goto fail;
    +
    1616 }
    +
    1617
    +
    1618 res = mkdir(PATH("a/b/c"), 0755);
    +
    1619 if (res == -1) {
    +
    1620 PERROR("mkdir");
    +
    1621 goto fail;
    +
    1622 }
    +
    1623
    +
    1624 errno = 0;
    +
    1625 res = rename(PATH("a"), PATH2("a/b/c"));
    +
    1626 if (res == 0 || errno != EINVAL) {
    +
    1627 PERROR("rename");
    +
    1628 goto fail;
    +
    1629 }
    +
    1630
    +
    1631 errno = 0;
    +
    1632 res = rename(PATH("a"), PATH2("a/b/c/a"));
    +
    1633 if (res == 0 || errno != EINVAL) {
    +
    1634 PERROR("rename");
    +
    1635 goto fail;
    +
    1636 }
    +
    1637
    +
    1638 errno = 0;
    +
    1639 res = rename(PATH("a/b/c"), PATH2("a"));
    +
    1640 if (res == 0 || errno != ENOTEMPTY) {
    +
    1641 PERROR("rename");
    +
    1642 goto fail;
    +
    1643 }
    +
    1644
    +
    1645 res = open(PATH("a/foo"), O_CREAT, 0644);
    +
    1646 if (res == -1) {
    +
    1647 PERROR("open");
    +
    1648 goto fail;
    +
    1649 }
    +
    1650 close(res);
    +
    1651
    +
    1652 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1653 if (res == -1) {
    +
    1654 PERROR("rename");
    +
    1655 goto fail;
    +
    1656 }
    +
    1657
    +
    1658 res = rename(PATH("a/bar"), PATH2("a/foo"));
    +
    1659 if (res == -1) {
    +
    1660 PERROR("rename");
    +
    1661 goto fail;
    +
    1662 }
    +
    1663
    +
    1664 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
    +
    1665 if (res == -1) {
    +
    1666 PERROR("rename");
    +
    1667 goto fail;
    +
    1668 }
    +
    1669
    +
    1670 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
    +
    1671 if (res == -1) {
    +
    1672 PERROR("rename");
    +
    1673 goto fail;
    +
    1674 }
    +
    1675
    +
    1676 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
    +
    1677 if (res == -1) {
    +
    1678 PERROR("rename");
    +
    1679 goto fail;
    +
    1680 }
    +
    1681
    +
    1682 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
    +
    1683 if (res == -1) {
    +
    1684 PERROR("rename");
    +
    1685 goto fail;
    +
    1686 }
    +
    1687
    +
    1688 res = open(PATH("a/bar"), O_CREAT, 0644);
    +
    1689 if (res == -1) {
    +
    1690 PERROR("open");
    +
    1691 goto fail;
    +
    1692 }
    +
    1693 close(res);
    +
    1694
    +
    1695 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1696 if (res == -1) {
    +
    1697 PERROR("rename");
    +
    1698 goto fail;
    +
    1699 }
    +
    1700
    +
    1701 unlink(PATH("a/bar"));
    +
    1702
    +
    1703 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1704 if (res == -1) {
    +
    1705 PERROR("rename");
    +
    1706 goto fail;
    +
    1707 }
    +
    1708
    +
    1709 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1710 if (res == -1) {
    +
    1711 PERROR("rename");
    +
    1712 goto fail;
    +
    1713 }
    +
    1714
    +
    1715 res = mkdir(PATH("a/d"), 0755);
    +
    1716 if (res == -1) {
    +
    1717 PERROR("mkdir");
    +
    1718 goto fail;
    +
    1719 }
    +
    1720
    +
    1721 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1722 if (res == -1) {
    +
    1723 PERROR("rename");
    +
    1724 goto fail;
    +
    1725 }
    +
    1726
    +
    1727 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1728 if (res == -1) {
    +
    1729 PERROR("rename");
    +
    1730 goto fail;
    +
    1731 }
    +
    1732
    +
    1733 res = mkdir(PATH("a/d"), 0755);
    +
    1734 if (res == -1) {
    +
    1735 PERROR("mkdir");
    +
    1736 goto fail;
    +
    1737 }
    +
    1738
    +
    1739 res = mkdir(PATH("a/d/e"), 0755);
    +
    1740 if (res == -1) {
    +
    1741 PERROR("mkdir");
    +
    1742 goto fail;
    +
    1743 }
    +
    1744
    +
    1745 errno = 0;
    +
    1746 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1747 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
    +
    1748 PERROR("rename");
    +
    1749 goto fail;
    +
    1750 }
    +
    1751
    +
    1752 rmdir(PATH("a/d/e"));
    +
    1753 rmdir(PATH("a/d"));
    +
    1754
    +
    1755 rmdir(PATH("a/b/c"));
    +
    1756 rmdir(PATH("a/b"));
    +
    1757 rmdir(PATH("a"));
    +
    1758
    +
    1759 err += cleanup_dir(testdir, testdir_files, 0);
    +
    1760 res = rmdir(testdir);
    +
    1761 if (res == -1) {
    +
    1762 PERROR("rmdir");
    +
    1763 goto fail;
    +
    1764 }
    +
    1765 res = check_nonexist(testdir);
    +
    1766 if (res == -1)
    +
    1767 return -1;
    +
    1768 if (err)
    +
    1769 return -1;
    +
    1770
    +
    1771 success();
    +
    1772 return 0;
    +
    1773
    +
    1774fail:
    +
    1775 unlink(PATH("a/bar"));
    +
    1776
    +
    1777 rmdir(PATH("a/d/e"));
    +
    1778 rmdir(PATH("a/d"));
    +
    1779
    +
    1780 rmdir(PATH("a/b/c"));
    +
    1781 rmdir(PATH("a/b"));
    +
    1782 rmdir(PATH("a"));
    +
    1783
    +
    1784 cleanup_dir(testdir, testdir_files, 1);
    +
    1785 rmdir(testdir);
    +
    1786
    +
    1787 return -1;
    +
    1788
    +
    1789#undef PATH2
    +
    1790#undef PATH
    +
    1791}
    +
    1792
    +
    1793static int test_mkfifo(void)
    +
    1794{
    +
    1795 int res;
    +
    1796 int err = 0;
    +
    1797
    +
    1798 start_test("mkfifo");
    +
    1799 unlink(testfile);
    +
    1800 res = mkfifo(testfile, 0644);
    +
    1801 if (res == -1) {
    +
    1802 PERROR("mkfifo");
    +
    1803 return -1;
    +
    1804 }
    +
    1805 res = check_type(testfile, S_IFIFO);
    +
    1806 if (res == -1)
    +
    1807 return -1;
    +
    1808 err += check_mode(testfile, 0644);
    +
    1809 err += check_nlink(testfile, 1);
    +
    1810 res = unlink(testfile);
    +
    1811 if (res == -1) {
    +
    1812 PERROR("unlink");
    +
    1813 return -1;
    +
    1814 }
    +
    1815 res = check_nonexist(testfile);
    +
    1816 if (res == -1)
    +
    1817 return -1;
    +
    1818 if (err)
    +
    1819 return -1;
    +
    1820
    +
    1821 success();
    +
    1822 return 0;
    +
    1823}
    +
    1824
    +
    1825static int test_mkdir(void)
    +
    1826{
    +
    1827 int res;
    +
    1828 int err = 0;
    +
    1829 const char *dir_contents[] = {NULL};
    +
    1830
    +
    1831 start_test("mkdir");
    +
    1832 rmdir(testdir);
    +
    1833 res = mkdir(testdir, 0755);
    +
    1834 if (res == -1) {
    +
    1835 PERROR("mkdir");
    +
    1836 return -1;
    +
    1837 }
    +
    1838 res = check_type(testdir, S_IFDIR);
    +
    1839 if (res == -1)
    +
    1840 return -1;
    +
    1841 err += check_mode(testdir, 0755);
    +
    1842 /* Some file systems (like btrfs) don't track link
    +
    1843 count for directories */
    +
    1844 //err += check_nlink(testdir, 2);
    +
    1845 err += check_dir_contents(testdir, dir_contents);
    +
    1846 res = rmdir(testdir);
    +
    1847 if (res == -1) {
    +
    1848 PERROR("rmdir");
    +
    1849 return -1;
    +
    1850 }
    +
    1851 res = check_nonexist(testdir);
    +
    1852 if (res == -1)
    +
    1853 return -1;
    +
    1854 if (err)
    +
    1855 return -1;
    +
    1856
    +
    1857 success();
    +
    1858 return 0;
    +
    1859}
    +
    1860
    +
    1861static int test_socket(void)
    +
    1862{
    +
    1863 struct sockaddr_un su;
    +
    1864 int fd;
    +
    1865 int res;
    +
    1866 int err = 0;
    +
    1867 const size_t test_sock_len = strlen(testsock) + 1;
    +
    1868
    +
    1869 start_test("socket");
    +
    1870 if (test_sock_len > sizeof(su.sun_path)) {
    +
    1871 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
    +
    1872 strlen(testsock) + 1 - sizeof(su.sun_path));
    +
    1873 return -1;
    +
    1874 }
    +
    1875 unlink(testsock);
    +
    1876 fd = socket(AF_UNIX, SOCK_STREAM, 0);
    +
    1877 if (fd < 0) {
    +
    1878 PERROR("socket");
    +
    1879 return -1;
    +
    1880 }
    +
    1881 su.sun_family = AF_UNIX;
    +
    1882
    +
    1883 strncpy(su.sun_path, testsock, test_sock_len);
    +
    1884 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
    +
    1885 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
    +
    1886 if (res == -1) {
    +
    1887 PERROR("bind");
    +
    1888 return -1;
    +
    1889 }
    +
    1890
    +
    1891 res = check_type(testsock, S_IFSOCK);
    +
    1892 if (res == -1) {
    +
    1893 close(fd);
    +
    1894 return -1;
    +
    1895 }
    +
    1896 err += check_nlink(testsock, 1);
    +
    1897 close(fd);
    +
    1898 res = unlink(testsock);
    +
    1899 if (res == -1) {
    +
    1900 PERROR("unlink");
    +
    1901 return -1;
    +
    1902 }
    +
    1903 res = check_nonexist(testsock);
    +
    1904 if (res == -1)
    +
    1905 return -1;
    +
    1906 if (err)
    +
    1907 return -1;
    +
    1908
    +
    1909 success();
    +
    1910 return 0;
    +
    1911}
    +
    1912
    +
    1913#define test_create_ro_dir(flags) \
    +
    1914 do_test_create_ro_dir(flags, #flags)
    +
    1915
    +
    1916static int do_test_create_ro_dir(int flags, const char *flags_str)
    +
    1917{
    +
    1918 int res;
    +
    1919 int err = 0;
    +
    1920 int fd;
    +
    1921
    +
    1922 start_test("open(%s) in read-only directory", flags_str);
    +
    1923 rmdir(testdir);
    +
    1924 res = mkdir(testdir, 0555);
    +
    1925 if (res == -1) {
    +
    1926 PERROR("mkdir");
    +
    1927 return -1;
    +
    1928 }
    +
    1929 fd = open(subfile, flags, 0644);
    +
    1930 if (fd != -1) {
    +
    1931 close(fd);
    +
    1932 unlink(subfile);
    +
    1933 ERROR("open should have failed");
    +
    1934 err--;
    +
    1935 } else {
    +
    1936 res = check_nonexist(subfile);
    +
    1937 if (res == -1)
    +
    1938 err--;
    +
    1939 }
    +
    1940 unlink(subfile);
    +
    1941 res = rmdir(testdir);
    +
    1942 if (res == -1) {
    +
    1943 PERROR("rmdir");
    +
    1944 return -1;
    +
    1945 }
    +
    1946 res = check_nonexist(testdir);
    +
    1947 if (res == -1)
    +
    1948 return -1;
    +
    1949 if (err)
    +
    1950 return -1;
    +
    1951
    +
    1952 success();
    +
    1953 return 0;
    +
    1954}
    +
    1955
    +
    1956#ifndef __FreeBSD__
    +
    1957/* this tests open with O_TMPFILE
    +
    1958 note that this will only work with the fuse low level api
    +
    1959 you will get ENOTSUP with the high level api */
    +
    1960static int test_create_tmpfile(void)
    +
    1961{
    +
    1962 rmdir(testdir);
    +
    1963 int res = mkdir(testdir, 0777);
    +
    1964 if (res)
    +
    1965 return -1;
    +
    1966
    +
    1967 start_test("create tmpfile");
    +
    1968
    +
    1969 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    1970 if(fd == -1) {
    +
    1971 if (errno == ENOTSUP) {
    +
    1972 /* don't bother if we're working on an old kernel
    +
    1973 or on the high level API */
    +
    1974 return 0;
    +
    1975 }
    +
    1976
    +
    1977 PERROR("open O_TMPFILE | O_RDWR");
    +
    1978 return -1;
    +
    1979 }
    +
    1980 close(fd);
    +
    1981
    +
    1982 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
    +
    1983 if(fd == -1){
    +
    1984 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
    +
    1985 return -1;
    +
    1986 };
    +
    1987 close(fd);
    +
    1988
    +
    1989 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
    +
    1990 if (fd != -1) {
    +
    1991 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
    +
    1992 return -1;
    +
    1993 }
    +
    1994
    +
    1995 success();
    +
    1996 return 0;
    +
    1997}
    +
    1998
    +
    1999static int test_create_and_link_tmpfile(void)
    +
    2000{
    +
    2001 /* skip this test for now since the github runner will fail in the linkat call below */
    +
    2002 return 0;
    +
    2003
    +
    2004 rmdir(testdir);
    +
    2005 unlink(testfile);
    +
    2006
    +
    2007 int res = mkdir(testdir, 0777);
    +
    2008 if (res)
    +
    2009 return -1;
    +
    2010
    +
    2011 start_test("create and link tmpfile");
    +
    2012
    +
    2013 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
    +
    2014 if(fd == -1) {
    +
    2015 if (errno == ENOTSUP) {
    +
    2016 /* don't bother if we're working on an old kernel
    +
    2017 or on the high level API */
    +
    2018 return 0;
    +
    2019 }
    +
    2020 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
    +
    2021 return -1;
    +
    2022 }
    +
    2023
    +
    2024 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2025 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
    +
    2026 return -1;
    +
    2027 }
    +
    2028 close(fd);
    +
    2029
    +
    2030 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    2031 if(fd == -1) {
    +
    2032 PERROR("open O_TMPFILE");
    +
    2033 return -1;
    +
    2034 }
    +
    2035
    +
    2036 if (check_nonexist(testfile)) {
    +
    2037 return -1;
    +
    2038 }
    +
    2039
    +
    2040 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2041 PERROR("linkat tempfile");
    +
    2042 return -1;
    +
    2043 }
    +
    2044 close(fd);
    +
    2045
    +
    2046 if (check_nlink(testfile, 1)) {
    +
    2047 return -1;
    +
    2048 }
    +
    2049 unlink(testfile);
    +
    2050
    +
    2051 success();
    +
    2052 return 0;
    +
    2053}
    +
    2054#endif
    +
    2055
    +
    2056int main(int argc, char *argv[])
    +
    2057{
    +
    2058 int err = 0;
    +
    2059 int a;
    +
    2060 int is_root;
    +
    2061
    +
    2062 umask(0);
    +
    2063 if (argc < 2 || argc > 4) {
    +
    2064 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
    +
    2065 return 1;
    +
    2066 }
    +
    2067 basepath = argv[1];
    +
    2068 basepath_r = basepath;
    +
    2069 for (a = 2; a < argc; a++) {
    +
    2070 char *endptr;
    +
    2071 char *arg = argv[a];
    +
    2072 if (arg[0] == ':') {
    +
    2073 basepath_r = arg + 1;
    +
    2074 } else {
    +
    2075 if (arg[0] == '-') {
    +
    2076 arg++;
    +
    2077 if (arg[0] == 'u') {
    +
    2078 unlinked_test = 1;
    +
    2079 endptr = arg + 1;
    +
    2080 } else {
    +
    2081 skip_test = strtoul(arg, &endptr, 10);
    +
    2082 }
    +
    2083 } else {
    +
    2084 select_test = strtoul(arg, &endptr, 10);
    +
    2085 }
    +
    2086 if (arg[0] == '\0' || *endptr != '\0') {
    +
    2087 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
    +
    2088 return 1;
    +
    2089 }
    +
    2090 }
    +
    2091 }
    +
    2092 assert(strlen(basepath) < 512);
    +
    2093 assert(strlen(basepath_r) < 512);
    +
    2094 if (basepath[0] != '/') {
    +
    2095 fprintf(stderr, "testdir must be an absolute path\n");
    +
    2096 return 1;
    +
    2097 }
    +
    2098
    +
    2099 sprintf(testfile, "%s/testfile", basepath);
    +
    2100 sprintf(testfile2, "%s/testfile2", basepath);
    +
    2101 sprintf(testdir, "%s/testdir", basepath);
    +
    2102 sprintf(testdir2, "%s/testdir2", basepath);
    +
    2103 sprintf(subfile, "%s/subfile", testdir2);
    +
    2104 sprintf(testsock, "%s/testsock", basepath);
    +
    2105
    +
    2106 sprintf(testfile_r, "%s/testfile", basepath_r);
    +
    2107 sprintf(testfile2_r, "%s/testfile2", basepath_r);
    +
    2108 sprintf(testdir_r, "%s/testdir", basepath_r);
    +
    2109 sprintf(testdir2_r, "%s/testdir2", basepath_r);
    +
    2110 sprintf(subfile_r, "%s/subfile", testdir2_r);
    +
    2111
    +
    2112 is_root = (geteuid() == 0);
    +
    2113
    +
    2114 err += test_create();
    +
    2115 err += test_create_unlink();
    +
    2116 err += test_symlink();
    +
    2117 err += test_link();
    +
    2118 err += test_link2();
    +
    2119 err += test_mknod();
    +
    2120 err += test_mkfifo();
    +
    2121 err += test_mkdir();
    +
    2122 err += test_rename_file();
    +
    2123 err += test_rename_dir();
    +
    2124 err += test_rename_dir_loop();
    +
    2125 err += test_seekdir();
    +
    2126 err += test_socket();
    +
    2127 err += test_utime();
    +
    2128 err += test_truncate(0);
    +
    2129 err += test_truncate(testdatalen / 2);
    +
    2130 err += test_truncate(testdatalen);
    +
    2131 err += test_truncate(testdatalen + 100);
    +
    2132 err += test_ftruncate(0, 0600);
    +
    2133 err += test_ftruncate(testdatalen / 2, 0600);
    +
    2134 err += test_ftruncate(testdatalen, 0600);
    +
    2135 err += test_ftruncate(testdatalen + 100, 0600);
    +
    2136 err += test_ftruncate(0, 0400);
    +
    2137 err += test_ftruncate(0, 0200);
    +
    2138 err += test_ftruncate(0, 0000);
    +
    2139 err += test_open(0, O_RDONLY, 0);
    +
    2140 err += test_open(1, O_RDONLY, 0);
    +
    2141 err += test_open(1, O_RDWR, 0);
    +
    2142 err += test_open(1, O_WRONLY, 0);
    +
    2143 err += test_open(0, O_RDWR | O_CREAT, 0600);
    +
    2144 err += test_open(1, O_RDWR | O_CREAT, 0600);
    +
    2145 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2146 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2147 err += test_open(0, O_RDONLY | O_CREAT, 0600);
    +
    2148 err += test_open(0, O_RDONLY | O_CREAT, 0400);
    +
    2149 err += test_open(0, O_RDONLY | O_CREAT, 0200);
    +
    2150 err += test_open(0, O_RDONLY | O_CREAT, 0000);
    +
    2151 err += test_open(0, O_WRONLY | O_CREAT, 0600);
    +
    2152 err += test_open(0, O_WRONLY | O_CREAT, 0400);
    +
    2153 err += test_open(0, O_WRONLY | O_CREAT, 0200);
    +
    2154 err += test_open(0, O_WRONLY | O_CREAT, 0000);
    +
    2155 err += test_open(0, O_RDWR | O_CREAT, 0400);
    +
    2156 err += test_open(0, O_RDWR | O_CREAT, 0200);
    +
    2157 err += test_open(0, O_RDWR | O_CREAT, 0000);
    +
    2158 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2159 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2160 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2161 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2162 err += test_open_acc(O_RDONLY, 0600, 0);
    +
    2163 err += test_open_acc(O_WRONLY, 0600, 0);
    +
    2164 err += test_open_acc(O_RDWR, 0600, 0);
    +
    2165 err += test_open_acc(O_RDONLY, 0400, 0);
    +
    2166 err += test_open_acc(O_WRONLY, 0200, 0);
    +
    2167 if(!is_root) {
    +
    2168 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
    +
    2169 err += test_open_acc(O_WRONLY, 0400, EACCES);
    +
    2170 err += test_open_acc(O_RDWR, 0400, EACCES);
    +
    2171 err += test_open_acc(O_RDONLY, 0200, EACCES);
    +
    2172 err += test_open_acc(O_RDWR, 0200, EACCES);
    +
    2173 err += test_open_acc(O_RDONLY, 0000, EACCES);
    +
    2174 err += test_open_acc(O_WRONLY, 0000, EACCES);
    +
    2175 err += test_open_acc(O_RDWR, 0000, EACCES);
    +
    2176 }
    +
    2177 err += test_create_ro_dir(O_CREAT);
    +
    2178 err += test_create_ro_dir(O_CREAT | O_EXCL);
    +
    2179 err += test_create_ro_dir(O_CREAT | O_WRONLY);
    +
    2180 err += test_create_ro_dir(O_CREAT | O_TRUNC);
    +
    2181 err += test_copy_file_range();
    +
    2182#ifndef __FreeBSD__
    +
    2183 err += test_create_tmpfile();
    +
    2184 err += test_create_and_link_tmpfile();
    +
    2185#endif
    +
    2186
    +
    2187 unlink(testfile2);
    +
    2188 unlink(testsock);
    +
    2189 rmdir(testdir);
    +
    2190 rmdir(testdir2);
    +
    2191
    +
    2192 if (err) {
    +
    2193 fprintf(stderr, "%i tests failed\n", -err);
    +
    2194 return 1;
    +
    2195 }
    +
    2196
    +
    2197 return check_unlinked_testfiles();
    +
    2198}
    +
    + + + + diff --git a/doc/html/test__want__conversion_8c_source.html b/doc/html/test__want__conversion_8c_source.html new file mode 100644 index 0000000..6659140 --- /dev/null +++ b/doc/html/test__want__conversion_8c_source.html @@ -0,0 +1,237 @@ + + + + + + + +libfuse: test/test_want_conversion.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_want_conversion.c
    +
    +
    +
    1#include "util.h"
    +
    2#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
    +
    3
    +
    4#include "fuse_i.h"
    +
    5#include <stdio.h>
    +
    6#include <assert.h>
    +
    7#include <inttypes.h>
    +
    8#include <stdbool.h>
    +
    9
    +
    10static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
    +
    11{
    +
    12 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 "\n", prefix,
    +
    13 conn->want, conn->want_ext);
    +
    14}
    +
    15
    +
    16static void application_init_old_style(struct fuse_conn_info *conn)
    +
    17{
    +
    18 /* Simulate application init the old style */
    + +
    20 conn->want &= ~FUSE_CAP_SPLICE_READ;
    +
    21}
    +
    22
    +
    23static void application_init_new_style(struct fuse_conn_info *conn)
    +
    24{
    +
    25 /* Simulate application init the new style */
    +
    26 fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ);
    +
    27 fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ);
    +
    28}
    +
    29
    +
    30static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
    +
    31{
    +
    32 uint64_t want_ext_default = conn->want_ext;
    +
    33 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
    +
    34 int rc;
    +
    35
    +
    36 /* High-level init */
    +
    37 fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT);
    +
    38
    +
    39 conn->want = want_default;
    +
    40
    +
    41 if (new_style)
    +
    42 application_init_new_style(conn);
    +
    43 else
    +
    44 application_init_old_style(conn);
    +
    45
    +
    46 rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
    +
    47 assert(rc == 0);
    +
    48}
    +
    49
    +
    50static void test_do_init(struct fuse_conn_info *conn, bool new_style)
    +
    51{
    +
    52 /* Initial setup */
    + + + + +
    57 conn->capable = fuse_lower_32_bits(conn->capable_ext);
    +
    58 conn->want_ext = conn->capable_ext;
    +
    59
    +
    60 print_conn_info("Initial state", conn);
    +
    61
    +
    62 uint64_t want_ext_default = conn->want_ext;
    +
    63 uint32_t want_default = fuse_lower_32_bits(conn->want_ext);
    +
    64 int rc;
    +
    65
    +
    66 conn->want = want_default;
    +
    67 conn->capable = fuse_lower_32_bits(conn->capable_ext);
    +
    68
    +
    69 test_fuse_fs_init(conn, new_style);
    +
    70
    +
    71 rc = convert_to_conn_want_ext(conn, want_ext_default, want_default);
    +
    72 assert(rc == 0);
    +
    73
    +
    74 /* Verify all expected flags are set */
    +
    75 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
    +
    76 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
    +
    77 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
    +
    78 assert(conn->want_ext & FUSE_CAP_POSIX_LOCKS);
    +
    79 assert(conn->want_ext & FUSE_CAP_FLOCK_LOCKS);
    +
    80 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
    +
    81 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
    +
    82 /* Verify no other flags are set */
    +
    83 assert(conn->want_ext ==
    + + + +
    87
    +
    88 print_conn_info("After init", conn);
    +
    89}
    +
    90
    +
    91static void test_want_conversion_basic(void)
    +
    92{
    +
    93 struct fuse_conn_info conn = { 0 };
    +
    94
    +
    95 printf("\nTesting basic want conversion:\n");
    +
    96 test_do_init(&conn, false);
    +
    97 test_do_init(&conn, true);
    +
    98 print_conn_info("After init", &conn);
    +
    99}
    +
    100
    +
    101static void test_want_conversion_conflict(void)
    +
    102{
    +
    103 struct fuse_conn_info conn = { 0 };
    +
    104 int rc;
    +
    105
    +
    106 printf("\nTesting want conversion conflict:\n");
    +
    107
    +
    108 /* Test conflicting values */
    +
    109 /* Initialize like fuse_lowlevel.c does */
    + + + +
    113 conn.capable = fuse_lower_32_bits(conn.capable_ext);
    +
    114 conn.want_ext = conn.capable_ext;
    +
    115 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    116 print_conn_info("Test conflict initial", &conn);
    +
    117
    +
    118 /* Initialize default values like in basic test */
    +
    119 uint64_t want_ext_default_ll = conn.want_ext;
    +
    120 uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll);
    +
    121
    +
    122 /* Simulate application init modifying capabilities */
    +
    123 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
    +
    124 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
    +
    125
    +
    126 rc = convert_to_conn_want_ext(&conn, want_ext_default_ll,
    +
    127 want_default_ll);
    +
    128 assert(rc == -EINVAL);
    +
    129 print_conn_info("Test conflict after", &conn);
    +
    130
    +
    131 printf("Want conversion conflict test passed\n");
    +
    132}
    +
    133
    +
    134static void test_want_conversion_high_bits(void)
    +
    135{
    +
    136 struct fuse_conn_info conn = { 0 };
    +
    137 int rc;
    +
    138
    +
    139 printf("\nTesting want conversion high bits preservation:\n");
    +
    140
    +
    141 /* Test high bits preservation */
    +
    142 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
    +
    143 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    144 print_conn_info("Test high bits initial", &conn);
    +
    145
    +
    146 uint64_t want_ext_default_ll = conn.want_ext;
    +
    147 uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll);
    +
    148
    +
    149 rc = convert_to_conn_want_ext(&conn, want_ext_default_ll,
    +
    150 want_default_ll);
    +
    151 assert(rc == 0);
    +
    152 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
    +
    153 print_conn_info("Test high bits after", &conn);
    +
    154
    +
    155 printf("Want conversion high bits test passed\n");
    +
    156}
    +
    157
    +
    158int main(void)
    +
    159{
    +
    160 test_want_conversion_basic();
    +
    161 test_want_conversion_conflict();
    +
    162 test_want_conversion_high_bits();
    +
    163 return 0;
    +
    164}
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_FLOCK_LOCKS
    + + +
    uint64_t capable_ext
    +
    uint32_t capable
    +
    uint64_t want_ext
    +
    + + + + diff --git a/doc/html/test__write__cache_8c_source.html b/doc/html/test__write__cache_8c_source.html new file mode 100644 index 0000000..53b6e26 --- /dev/null +++ b/doc/html/test__write__cache_8c_source.html @@ -0,0 +1,404 @@ + + + + + + + +libfuse: test/test_write_cache.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    test_write_cache.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    9
    +
    10#define FUSE_USE_VERSION 30
    +
    11
    +
    12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    13#include <fuse.h>
    +
    14
    +
    15#include <fuse_config.h>
    +
    16#include <fuse_lowlevel.h>
    +
    17#include <stdio.h>
    +
    18#include <stdlib.h>
    +
    19#include <string.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <assert.h>
    +
    23#include <stddef.h>
    +
    24#include <unistd.h>
    +
    25#include <sys/stat.h>
    +
    26#include <pthread.h>
    +
    27#include <stdatomic.h>
    +
    28
    +
    29#ifndef __linux__
    +
    30#include <limits.h>
    +
    31#else
    +
    32#include <linux/limits.h>
    +
    33#endif
    +
    34
    +
    35#define FILE_INO 2
    +
    36#define FILE_NAME "write_me"
    +
    37
    +
    38/* Command line parsing */
    +
    39struct options {
    +
    40 int writeback;
    +
    41 int data_size;
    +
    42 int delay_ms;
    +
    43} options = {
    +
    44 .writeback = 0,
    +
    45 .data_size = 2048,
    +
    46 .delay_ms = 0,
    +
    47};
    +
    48
    +
    49#define WRITE_SYSCALLS 64
    +
    50
    +
    51#define OPTION(t, p) \
    +
    52 { t, offsetof(struct options, p), 1 }
    +
    53static const struct fuse_opt option_spec[] = {
    +
    54 OPTION("writeback_cache", writeback),
    +
    55 OPTION("--data-size=%d", data_size),
    +
    56 OPTION("--delay_ms=%d", delay_ms),
    + +
    58};
    +
    59static int got_write;
    +
    60static atomic_int write_cnt;
    +
    61
    +
    62pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    +
    63pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    64static int write_start, write_done;
    +
    65
    +
    66static void tfs_init (void *userdata, struct fuse_conn_info *conn)
    +
    67{
    +
    68 (void) userdata;
    +
    69
    +
    70 if(options.writeback) {
    +
    71 assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE));
    +
    72 fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE);
    +
    73 }
    +
    74}
    +
    75
    +
    76static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    77 stbuf->st_ino = ino;
    +
    78 if (ino == FUSE_ROOT_ID) {
    +
    79 stbuf->st_mode = S_IFDIR | 0755;
    +
    80 stbuf->st_nlink = 1;
    +
    81 }
    +
    82
    +
    83 else if (ino == FILE_INO) {
    +
    84 stbuf->st_mode = S_IFREG | 0222;
    +
    85 stbuf->st_nlink = 1;
    +
    86 stbuf->st_size = 0;
    +
    87 }
    +
    88
    +
    89 else
    +
    90 return -1;
    +
    91
    +
    92 return 0;
    +
    93}
    +
    94
    +
    95static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    96 const char *name) {
    +
    97 struct fuse_entry_param e;
    +
    98 memset(&e, 0, sizeof(e));
    +
    99
    +
    100 if (parent != FUSE_ROOT_ID)
    +
    101 goto err_out;
    +
    102 else if (strcmp(name, FILE_NAME) == 0)
    +
    103 e.ino = FILE_INO;
    +
    104 else
    +
    105 goto err_out;
    +
    106
    +
    107 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    108 goto err_out;
    +
    109 fuse_reply_entry(req, &e);
    +
    110 return;
    +
    111
    +
    112err_out:
    +
    113 fuse_reply_err(req, ENOENT);
    +
    114}
    +
    115
    +
    116static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    117 struct fuse_file_info *fi) {
    +
    118 struct stat stbuf;
    +
    119
    +
    120 (void) fi;
    +
    121
    +
    122 memset(&stbuf, 0, sizeof(stbuf));
    +
    123 if (tfs_stat(ino, &stbuf) != 0)
    +
    124 fuse_reply_err(req, ENOENT);
    +
    125 else
    +
    126 fuse_reply_attr(req, &stbuf, 5);
    +
    127}
    +
    128
    +
    129static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    130 struct fuse_file_info *fi) {
    +
    131 if (ino == FUSE_ROOT_ID)
    +
    132 fuse_reply_err(req, EISDIR);
    +
    133 else {
    +
    134 assert(ino == FILE_INO);
    +
    135 /* Test close(rofd) does not block waiting for pending writes */
    +
    136 fi->noflush = !options.writeback && options.delay_ms &&
    +
    137 (fi->flags & O_ACCMODE) == O_RDONLY;
    +
    138 fuse_reply_open(req, fi);
    +
    139 }
    +
    140}
    +
    141
    +
    142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
    +
    143 size_t size, off_t off, struct fuse_file_info *fi) {
    +
    144 (void) fi; (void) buf; (void) off;
    +
    145 size_t expected;
    +
    146
    +
    147 assert(ino == FILE_INO);
    +
    148 expected = options.data_size;
    +
    149 if(options.writeback)
    +
    150 expected *= 2;
    +
    151
    +
    152 write_cnt++;
    +
    153
    +
    154 if(size != expected && !options.writeback)
    +
    155 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
    +
    156 expected, size);
    +
    157 else
    +
    158 got_write = 1;
    +
    159
    +
    160 /* Simulate waiting for pending writes */
    +
    161 if (options.delay_ms) {
    +
    162 pthread_mutex_lock(&lock);
    +
    163 write_start = 1;
    +
    164 pthread_cond_signal(&cond);
    +
    165 pthread_mutex_unlock(&lock);
    +
    166
    +
    167 usleep(options.delay_ms * 1000);
    +
    168
    +
    169 pthread_mutex_lock(&lock);
    +
    170 write_done = 1;
    +
    171 pthread_cond_signal(&cond);
    +
    172 pthread_mutex_unlock(&lock);
    +
    173 }
    +
    174
    +
    175 fuse_reply_write(req, size);
    +
    176}
    +
    177
    +
    178static struct fuse_lowlevel_ops tfs_oper = {
    +
    179 .init = tfs_init,
    +
    180 .lookup = tfs_lookup,
    +
    181 .getattr = tfs_getattr,
    +
    182 .open = tfs_open,
    +
    183 .write = tfs_write,
    +
    184};
    +
    185
    +
    186static void* close_rofd(void *data) {
    +
    187 int rofd = (int)(long) data;
    +
    188
    +
    189 /* Wait for first write to start */
    +
    190 pthread_mutex_lock(&lock);
    +
    191 while (!write_start && !write_done)
    +
    192 pthread_cond_wait(&cond, &lock);
    +
    193 pthread_mutex_unlock(&lock);
    +
    194
    +
    195 close(rofd);
    +
    196 printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done);
    +
    197
    +
    198 /* First write should not have been completed */
    +
    199 if (write_done)
    +
    200 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
    +
    201
    +
    202 return NULL;
    +
    203}
    +
    204
    +
    205static void* run_fs(void *data) {
    +
    206 struct fuse_session *se = (struct fuse_session*) data;
    +
    207 assert(fuse_session_loop(se) == 0);
    +
    208 return NULL;
    +
    209}
    +
    210
    +
    211static void test_fs(char *mountpoint) {
    +
    212 char fname[PATH_MAX];
    +
    213 char *buf;
    +
    214 const size_t iosize = options.data_size;
    +
    215 const size_t dsize = options.data_size * WRITE_SYSCALLS;
    +
    216 int fd, rofd;
    +
    217 pthread_t rofd_thread;
    +
    218 off_t off = 0;
    +
    219
    +
    220 buf = malloc(dsize);
    +
    221 assert(buf != NULL);
    +
    222 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
    +
    223 assert(read(fd, buf, dsize) == dsize);
    +
    224 close(fd);
    +
    225
    +
    226 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
    +
    227 mountpoint) > 0);
    +
    228 fd = open(fname, O_WRONLY);
    +
    229 if (fd == -1) {
    +
    230 perror(fname);
    +
    231 assert(0);
    +
    232 }
    +
    233
    +
    234 if (options.delay_ms) {
    +
    235 /* Verify that close(rofd) does not block waiting for pending writes */
    +
    236 rofd = open(fname, O_RDONLY);
    +
    237 assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0);
    +
    238 /* Give close_rofd time to start */
    +
    239 usleep(options.delay_ms * 1000);
    +
    240 }
    +
    241
    +
    242 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
    +
    243 assert(pwrite(fd, buf + off, iosize, off) == iosize);
    +
    244 off += iosize;
    +
    245 assert(off <= dsize);
    +
    246 }
    +
    247 free(buf);
    +
    248 close(fd);
    +
    249
    +
    250 if (options.delay_ms) {
    +
    251 printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done);
    +
    252 assert(pthread_join(rofd_thread, NULL) == 0);
    +
    253 }
    +
    254}
    +
    255
    +
    256int main(int argc, char *argv[]) {
    +
    257 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    258 struct fuse_session *se;
    +
    259 struct fuse_cmdline_opts fuse_opts;
    +
    260 pthread_t fs_thread;
    +
    261
    +
    262 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
    +
    263 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    264#ifndef __FreeBSD__
    +
    265 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    266#endif
    +
    267 se = fuse_session_new(&args, &tfs_oper,
    +
    268 sizeof(tfs_oper), NULL);
    +
    269 fuse_opt_free_args(&args);
    +
    270 assert (se != NULL);
    +
    271 assert(fuse_set_signal_handlers(se) == 0);
    +
    272 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    273
    +
    274 /* Start file-system thread */
    +
    275 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    276
    +
    277 /* Write test data */
    +
    278 test_fs(fuse_opts.mountpoint);
    +
    279 free(fuse_opts.mountpoint);
    +
    280
    +
    281 /* Stop file system */
    + + +
    284 assert(pthread_join(fs_thread, NULL) == 0);
    +
    285
    +
    286 assert(got_write == 1);
    +
    287
    +
    288 /*
    +
    289 * when writeback cache is enabled, kernel side can merge requests, but
    +
    290 * memory pressure, system 'sync' might trigger data flushes before - flush
    +
    291 * might happen in between write syscalls - merging subpage writes into
    +
    292 * a single page and pages into large fuse requests might or might not work.
    +
    293 * Though we can expect that that at least some (but maybe all) write
    +
    294 * system calls can be merged.
    +
    295 */
    +
    296 if (options.writeback)
    +
    297 assert(write_cnt < WRITE_SYSCALLS);
    +
    298 else
    +
    299 assert(write_cnt == WRITE_SYSCALLS);
    +
    300
    + + +
    303
    +
    304 printf("Test completed successfully.\n");
    +
    305 return 0;
    +
    306}
    +
    307
    +
    308
    + +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    #define FUSE_ROOT_ID
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    +
    fuse_ino_t ino
    + +
    uint32_t noflush
    Definition fuse_common.h:99
    + + +
    void(* init)(void *userdata, struct fuse_conn_info *conn)
    + +
    + + + + diff --git a/doc/html/util_2fusermount_8c_source.html b/doc/html/util_2fusermount_8c_source.html new file mode 100644 index 0000000..fa8eda1 --- /dev/null +++ b/doc/html/util_2fusermount_8c_source.html @@ -0,0 +1,1796 @@ + + + + + + + +libfuse: util/fusermount.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    fusermount.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8/* This program does the mounting and unmounting of FUSE filesystems */
    +
    9
    +
    10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
    +
    11#include "fuse_config.h"
    +
    12#include "mount_util.h"
    +
    13#include "util.h"
    +
    14
    +
    15#include <stdio.h>
    +
    16#include <stdlib.h>
    +
    17#include <string.h>
    +
    18#include <ctype.h>
    +
    19#include <unistd.h>
    +
    20#include <getopt.h>
    +
    21#include <errno.h>
    +
    22#include <fcntl.h>
    +
    23#include <pwd.h>
    +
    24#include <paths.h>
    +
    25#include <mntent.h>
    +
    26#include <sys/wait.h>
    +
    27#include <sys/stat.h>
    +
    28#include <sys/param.h>
    +
    29
    +
    30#include "fuse_mount_compat.h"
    +
    31
    +
    32#include <sys/fsuid.h>
    +
    33#include <sys/socket.h>
    +
    34#include <sys/utsname.h>
    +
    35#include <sched.h>
    +
    36#include <stdbool.h>
    +
    37#include <sys/vfs.h>
    +
    38
    +
    39#ifdef HAVE_LINUX_CLOSE_RANGE_H
    +
    40#include <linux/close_range.h>
    +
    41#endif
    +
    42
    +
    43#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
    +
    44
    +
    45#define FUSE_DEV "/dev/fuse"
    +
    46
    +
    47static const char *progname;
    +
    48
    +
    49static int user_allow_other = 0;
    +
    50static int mount_max = 1000;
    +
    51
    +
    52static int auto_unmount = 0;
    +
    53
    +
    54#ifdef GETMNTENT_NEEDS_UNESCAPING
    +
    55// Older versions of musl libc don't unescape entries in /etc/mtab
    +
    56
    +
    57// unescapes octal sequences like \040 in-place
    +
    58// That's ok, because unescaping can not extend the length of the string.
    +
    59static void unescape(char *buf) {
    +
    60 char *src = buf;
    +
    61 char *dest = buf;
    +
    62 while (1) {
    +
    63 char *next_src = strchrnul(src, '\\');
    +
    64 int offset = next_src - src;
    +
    65 memmove(dest, src, offset);
    +
    66 src = next_src;
    +
    67 dest += offset;
    +
    68
    +
    69 if(*src == '\0') {
    +
    70 *dest = *src;
    +
    71 return;
    +
    72 }
    +
    73 src++;
    +
    74
    +
    75 if('0' <= src[0] && src[0] < '2' &&
    +
    76 '0' <= src[1] && src[1] < '8' &&
    +
    77 '0' <= src[2] && src[2] < '8') {
    +
    78 *dest++ = (src[0] - '0') << 6
    +
    79 | (src[1] - '0') << 3
    +
    80 | (src[2] - '0') << 0;
    +
    81 src += 3;
    +
    82 } else if (src[0] == '\\') {
    +
    83 *dest++ = '\\';
    +
    84 src += 1;
    +
    85 } else {
    +
    86 *dest++ = '\\';
    +
    87 }
    +
    88 }
    +
    89}
    +
    90
    +
    91static struct mntent *GETMNTENT(FILE *stream)
    +
    92{
    +
    93 struct mntent *entp = getmntent(stream);
    +
    94 if(entp != NULL) {
    +
    95 unescape(entp->mnt_fsname);
    +
    96 unescape(entp->mnt_dir);
    +
    97 unescape(entp->mnt_type);
    +
    98 unescape(entp->mnt_opts);
    +
    99 }
    +
    100 return entp;
    +
    101}
    +
    102#else
    +
    103#define GETMNTENT getmntent
    +
    104#endif // GETMNTENT_NEEDS_UNESCAPING
    +
    105
    +
    106/*
    +
    107 * Take a ',' separated option string and extract "x-" options
    +
    108 */
    +
    109static int extract_x_options(const char *original, char **non_x_opts,
    +
    110 char **x_opts)
    +
    111{
    +
    112 size_t orig_len;
    +
    113 const char *opt, *opt_end;
    +
    114
    +
    115 orig_len = strlen(original) + 1;
    +
    116
    +
    117 *non_x_opts = calloc(1, orig_len);
    +
    118 *x_opts = calloc(1, orig_len);
    +
    119
    +
    120 size_t non_x_opts_len = orig_len;
    +
    121 size_t x_opts_len = orig_len;
    +
    122
    +
    123 if (*non_x_opts == NULL || *x_opts == NULL) {
    +
    124 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
    +
    125 __func__, orig_len);
    +
    126 return -ENOMEM;
    +
    127 }
    +
    128
    +
    129 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
    +
    130 char *opt_buf;
    +
    131
    +
    132 opt_end = strchr(opt, ',');
    +
    133 if (opt_end == NULL)
    +
    134 opt_end = original + orig_len;
    +
    135
    +
    136 size_t opt_len = opt_end - opt;
    +
    137 size_t opt_len_left = orig_len - (opt - original);
    +
    138 size_t buf_len;
    +
    139 bool is_x_opts;
    +
    140
    +
    141 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
    +
    142 buf_len = x_opts_len;
    +
    143 is_x_opts = true;
    +
    144 opt_buf = *x_opts;
    +
    145 } else {
    +
    146 buf_len = non_x_opts_len;
    +
    147 is_x_opts = false;
    +
    148 opt_buf = *non_x_opts;
    +
    149 }
    +
    150
    +
    151 if (buf_len < orig_len) {
    +
    152 strncat(opt_buf, ",", 2);
    +
    153 buf_len -= 1;
    +
    154 }
    +
    155
    +
    156 /* omits ',' */
    +
    157 if ((ssize_t)(buf_len - opt_len) < 0) {
    +
    158 /* This would be a bug */
    +
    159 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
    +
    160 __func__, original);
    +
    161 return -EIO;
    +
    162 }
    +
    163
    +
    164 strncat(opt_buf, opt, opt_end - opt);
    +
    165 buf_len -= opt_len;
    +
    166
    +
    167 if (is_x_opts)
    +
    168 x_opts_len = buf_len;
    +
    169 else
    +
    170 non_x_opts_len = buf_len;
    +
    171 }
    +
    172
    +
    173 return 0;
    +
    174}
    +
    175
    +
    176static const char *get_user_name(void)
    +
    177{
    +
    178 struct passwd *pw = getpwuid(getuid());
    +
    179 if (pw != NULL && pw->pw_name != NULL)
    +
    180 return pw->pw_name;
    +
    181 else {
    +
    182 fprintf(stderr, "%s: could not determine username\n", progname);
    +
    183 return NULL;
    +
    184 }
    +
    185}
    +
    186
    +
    187static uid_t oldfsuid;
    +
    188static gid_t oldfsgid;
    +
    189
    +
    190static void drop_privs(void)
    +
    191{
    +
    192 if (getuid() != 0) {
    +
    193 oldfsuid = setfsuid(getuid());
    +
    194 oldfsgid = setfsgid(getgid());
    +
    195 }
    +
    196}
    +
    197
    +
    198static void restore_privs(void)
    +
    199{
    +
    200 if (getuid() != 0) {
    +
    201 setfsuid(oldfsuid);
    +
    202 setfsgid(oldfsgid);
    +
    203 }
    +
    204}
    +
    205
    +
    206#ifndef IGNORE_MTAB
    +
    207/*
    +
    208 * Make sure that /etc/mtab is checked and updated atomically
    +
    209 */
    +
    210static int lock_umount(void)
    +
    211{
    +
    212 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
    +
    213 int mtablock;
    +
    214 int res;
    +
    215 struct stat mtab_stat;
    +
    216
    +
    217 /* /etc/mtab could be a symlink to /proc/mounts */
    +
    218 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
    +
    219 return -1;
    +
    220
    +
    221 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
    +
    222 if (mtablock == -1) {
    +
    223 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
    +
    224 progname, strerror(errno));
    +
    225 return -1;
    +
    226 }
    +
    227 res = lockf(mtablock, F_LOCK, 0);
    +
    228 if (res < 0) {
    +
    229 fprintf(stderr, "%s: error getting lock: %s\n", progname,
    +
    230 strerror(errno));
    +
    231 close(mtablock);
    +
    232 return -1;
    +
    233 }
    +
    234
    +
    235 return mtablock;
    +
    236}
    +
    237
    +
    238static void unlock_umount(int mtablock)
    +
    239{
    +
    240 if (mtablock >= 0) {
    +
    241 int res;
    +
    242
    +
    243 res = lockf(mtablock, F_ULOCK, 0);
    +
    244 if (res < 0) {
    +
    245 fprintf(stderr, "%s: error releasing lock: %s\n",
    +
    246 progname, strerror(errno));
    +
    247 }
    +
    248 close(mtablock);
    +
    249 }
    +
    250}
    +
    251
    +
    252static int add_mount(const char *source, const char *mnt, const char *type,
    +
    253 const char *opts)
    +
    254{
    +
    255 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
    +
    256}
    +
    257
    +
    258static int may_unmount(const char *mnt, int quiet)
    +
    259{
    +
    260 struct mntent *entp;
    +
    261 FILE *fp;
    +
    262 const char *user = NULL;
    +
    263 char uidstr[32];
    +
    264 unsigned uidlen = 0;
    +
    265 int found;
    +
    266 const char *mtab = _PATH_MOUNTED;
    +
    267
    +
    268 user = get_user_name();
    +
    269 if (user == NULL)
    +
    270 return -1;
    +
    271
    +
    272 fp = setmntent(mtab, "r");
    +
    273 if (fp == NULL) {
    +
    274 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
    +
    275 strerror(errno));
    +
    276 return -1;
    +
    277 }
    +
    278
    +
    279 uidlen = sprintf(uidstr, "%u", getuid());
    +
    280
    +
    281 found = 0;
    +
    282 while ((entp = GETMNTENT(fp)) != NULL) {
    +
    283 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
    +
    284 (strcmp(entp->mnt_type, "fuse") == 0 ||
    +
    285 strcmp(entp->mnt_type, "fuseblk") == 0 ||
    +
    286 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
    +
    287 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
    +
    288 char *p = strstr(entp->mnt_opts, "user=");
    +
    289 if (p &&
    +
    290 (p == entp->mnt_opts || *(p-1) == ',') &&
    +
    291 strcmp(p + 5, user) == 0) {
    +
    292 found = 1;
    +
    293 break;
    +
    294 }
    +
    295 /* /etc/mtab is a link pointing to
    +
    296 /proc/mounts: */
    +
    297 else if ((p =
    +
    298 strstr(entp->mnt_opts, "user_id=")) &&
    +
    299 (p == entp->mnt_opts ||
    +
    300 *(p-1) == ',') &&
    +
    301 strncmp(p + 8, uidstr, uidlen) == 0 &&
    +
    302 (*(p+8+uidlen) == ',' ||
    +
    303 *(p+8+uidlen) == '\0')) {
    +
    304 found = 1;
    +
    305 break;
    +
    306 }
    +
    307 }
    +
    308 }
    +
    309 endmntent(fp);
    +
    310
    +
    311 if (!found) {
    +
    312 if (!quiet)
    +
    313 fprintf(stderr,
    +
    314 "%s: entry for %s not found in %s\n",
    +
    315 progname, mnt, mtab);
    +
    316 return -1;
    +
    317 }
    +
    318
    +
    319 return 0;
    +
    320}
    +
    321#endif
    +
    322
    +
    323/*
    +
    324 * Check whether the file specified in "fusermount3 -u" is really a
    +
    325 * mountpoint and not a symlink. This is necessary otherwise the user
    +
    326 * could move the mountpoint away and replace it with a symlink
    +
    327 * pointing to an arbitrary mount, thereby tricking fusermount3 into
    +
    328 * unmounting that (umount(2) will follow symlinks).
    +
    329 *
    +
    330 * This is the child process running in a separate mount namespace, so
    +
    331 * we don't mess with the global namespace and if the process is
    +
    332 * killed for any reason, mounts are automatically cleaned up.
    +
    333 *
    +
    334 * First make sure nothing is propagated back into the parent
    +
    335 * namespace by marking all mounts "private".
    +
    336 *
    +
    337 * Then bind mount parent onto a stable base where the user can't move
    +
    338 * it around.
    +
    339 *
    +
    340 * Finally check /proc/mounts for an entry matching the requested
    +
    341 * mountpoint. If it's found then we are OK, and the user can't move
    +
    342 * it around within the parent directory as rename() will return
    +
    343 * EBUSY. Be careful to ignore any mounts that existed before the
    +
    344 * bind.
    +
    345 */
    +
    346static int check_is_mount_child(void *p)
    +
    347{
    +
    348 const char **a = p;
    +
    349 const char *last = a[0];
    +
    350 const char *mnt = a[1];
    +
    351 const char *type = a[2];
    +
    352 int res;
    +
    353 const char *procmounts = "/proc/mounts";
    +
    354 int found;
    +
    355 FILE *fp;
    +
    356 struct mntent *entp;
    +
    357 int count;
    +
    358
    +
    359 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
    +
    360 if (res == -1) {
    +
    361 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
    +
    362 progname, strerror(errno));
    +
    363 return 1;
    +
    364 }
    +
    365
    +
    366 fp = setmntent(procmounts, "r");
    +
    367 if (fp == NULL) {
    +
    368 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
    +
    369 procmounts, strerror(errno));
    +
    370 return 1;
    +
    371 }
    +
    372
    +
    373 count = 0;
    +
    374 while (GETMNTENT(fp) != NULL)
    +
    375 count++;
    +
    376 endmntent(fp);
    +
    377
    +
    378 fp = setmntent(procmounts, "r");
    +
    379 if (fp == NULL) {
    +
    380 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
    +
    381 procmounts, strerror(errno));
    +
    382 return 1;
    +
    383 }
    +
    384
    +
    385 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
    +
    386 if (res == -1) {
    +
    387 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
    +
    388 progname, strerror(errno));
    +
    389 return 1;
    +
    390 }
    +
    391
    +
    392 found = 0;
    +
    393 while ((entp = GETMNTENT(fp)) != NULL) {
    +
    394 if (count > 0) {
    +
    395 count--;
    +
    396 continue;
    +
    397 }
    +
    398 if (entp->mnt_dir[0] == '/' &&
    +
    399 strcmp(entp->mnt_dir + 1, last) == 0 &&
    +
    400 (!type || strcmp(entp->mnt_type, type) == 0)) {
    +
    401 found = 1;
    +
    402 break;
    +
    403 }
    +
    404 }
    +
    405 endmntent(fp);
    +
    406
    +
    407 if (!found) {
    +
    408 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
    +
    409 return 1;
    +
    410 }
    +
    411
    +
    412 return 0;
    +
    413}
    +
    414
    +
    415static pid_t clone_newns(void *a)
    +
    416{
    +
    417 char buf[131072];
    +
    418 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
    +
    419
    +
    420#ifdef __ia64__
    +
    421 extern int __clone2(int (*fn)(void *),
    +
    422 void *child_stack_base, size_t stack_size,
    +
    423 int flags, void *arg, pid_t *ptid,
    +
    424 void *tls, pid_t *ctid);
    +
    425
    +
    426 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
    +
    427 CLONE_NEWNS, a, NULL, NULL, NULL);
    +
    428#else
    +
    429 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
    +
    430#endif
    +
    431}
    +
    432
    +
    433static int check_is_mount(const char *last, const char *mnt, const char *type)
    +
    434{
    +
    435 pid_t pid, p;
    +
    436 int status;
    +
    437 const char *a[3] = { last, mnt, type };
    +
    438
    +
    439 pid = clone_newns((void *) a);
    +
    440 if (pid == (pid_t) -1) {
    +
    441 fprintf(stderr, "%s: failed to clone namespace: %s\n",
    +
    442 progname, strerror(errno));
    +
    443 return -1;
    +
    444 }
    +
    445 p = waitpid(pid, &status, __WCLONE);
    +
    446 if (p == (pid_t) -1) {
    +
    447 fprintf(stderr, "%s: waitpid failed: %s\n",
    +
    448 progname, strerror(errno));
    +
    449 return -1;
    +
    450 }
    +
    451 if (!WIFEXITED(status)) {
    +
    452 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
    +
    453 progname, status);
    +
    454 return -1;
    +
    455 }
    +
    456 if (WEXITSTATUS(status) != 0)
    +
    457 return -1;
    +
    458
    +
    459 return 0;
    +
    460}
    +
    461
    +
    462static int chdir_to_parent(char *copy, const char **lastp)
    +
    463{
    +
    464 char *tmp;
    +
    465 const char *parent;
    +
    466 char buf[65536];
    +
    467 int res;
    +
    468
    +
    469 tmp = strrchr(copy, '/');
    +
    470 if (tmp == NULL || tmp[1] == '\0') {
    +
    471 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
    +
    472 progname, copy);
    +
    473 return -1;
    +
    474 }
    +
    475 if (tmp != copy) {
    +
    476 *tmp = '\0';
    +
    477 parent = copy;
    +
    478 *lastp = tmp + 1;
    +
    479 } else if (tmp[1] != '\0') {
    +
    480 *lastp = tmp + 1;
    +
    481 parent = "/";
    +
    482 } else {
    +
    483 *lastp = ".";
    +
    484 parent = "/";
    +
    485 }
    +
    486
    +
    487 res = chdir(parent);
    +
    488 if (res == -1) {
    +
    489 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
    +
    490 progname, parent, strerror(errno));
    +
    491 return -1;
    +
    492 }
    +
    493
    +
    494 if (getcwd(buf, sizeof(buf)) == NULL) {
    +
    495 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
    +
    496 progname, strerror(errno));
    +
    497 return -1;
    +
    498 }
    +
    499 if (strcmp(buf, parent) != 0) {
    +
    500 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
    +
    501 parent, buf);
    +
    502 return -1;
    +
    503
    +
    504 }
    +
    505
    +
    506 return 0;
    +
    507}
    +
    508
    +
    509#ifndef IGNORE_MTAB
    +
    510static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
    +
    511{
    +
    512 int res;
    +
    513 char *copy;
    +
    514 const char *last;
    +
    515 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
    +
    516
    +
    517 if (getuid() != 0) {
    +
    518 res = may_unmount(mnt, quiet);
    +
    519 if (res == -1)
    +
    520 return -1;
    +
    521 }
    +
    522
    +
    523 copy = strdup(mnt);
    +
    524 if (copy == NULL) {
    +
    525 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    526 return -1;
    +
    527 }
    +
    528
    +
    529 drop_privs();
    +
    530 res = chdir_to_parent(copy, &last);
    +
    531 if (res == -1) {
    +
    532 restore_privs();
    +
    533 goto out;
    +
    534 }
    +
    535
    +
    536 res = umount2(last, umount_flags);
    +
    537 restore_privs();
    +
    538 if (res == -1 && !quiet) {
    +
    539 fprintf(stderr, "%s: failed to unmount %s: %s\n",
    +
    540 progname, mnt, strerror(errno));
    +
    541 }
    +
    542
    +
    543out:
    +
    544 free(copy);
    +
    545 if (res == -1)
    +
    546 return -1;
    +
    547
    +
    548 res = chdir("/");
    +
    549 if (res == -1) {
    +
    550 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    551 return -1;
    +
    552 }
    +
    553
    +
    554 return fuse_mnt_remove_mount(progname, mnt);
    +
    555}
    +
    556
    +
    557static int unmount_fuse(const char *mnt, int quiet, int lazy)
    +
    558{
    +
    559 int res;
    +
    560 int mtablock = lock_umount();
    +
    561
    +
    562 res = unmount_fuse_locked(mnt, quiet, lazy);
    +
    563 unlock_umount(mtablock);
    +
    564
    +
    565 return res;
    +
    566}
    +
    567
    +
    568static int count_fuse_fs(void)
    +
    569{
    +
    570 struct mntent *entp;
    +
    571 int count = 0;
    +
    572 const char *mtab = _PATH_MOUNTED;
    +
    573 FILE *fp = setmntent(mtab, "r");
    +
    574 if (fp == NULL) {
    +
    575 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
    +
    576 strerror(errno));
    +
    577 return -1;
    +
    578 }
    +
    579 while ((entp = GETMNTENT(fp)) != NULL) {
    +
    580 if (strcmp(entp->mnt_type, "fuse") == 0 ||
    +
    581 strncmp(entp->mnt_type, "fuse.", 5) == 0)
    +
    582 count ++;
    +
    583 }
    +
    584 endmntent(fp);
    +
    585 return count;
    +
    586}
    +
    587
    +
    588
    +
    589#else /* IGNORE_MTAB */
    +
    590static int count_fuse_fs(void)
    +
    591{
    +
    592 return 0;
    +
    593}
    +
    594
    +
    595static int add_mount(const char *source, const char *mnt, const char *type,
    +
    596 const char *opts)
    +
    597{
    +
    598 (void) source;
    +
    599 (void) mnt;
    +
    600 (void) type;
    +
    601 (void) opts;
    +
    602 return 0;
    +
    603}
    +
    604
    +
    605static int unmount_fuse(const char *mnt, int quiet, int lazy)
    +
    606{
    +
    607 (void) quiet;
    +
    608 return fuse_mnt_umount(progname, mnt, mnt, lazy);
    +
    609}
    +
    610#endif /* IGNORE_MTAB */
    +
    611
    +
    612static void strip_line(char *line)
    +
    613{
    +
    614 char *s = strchr(line, '#');
    +
    615 if (s != NULL)
    +
    616 s[0] = '\0';
    +
    617 for (s = line + strlen(line) - 1;
    +
    618 s >= line && isspace((unsigned char) *s); s--);
    +
    619 s[1] = '\0';
    +
    620 for (s = line; isspace((unsigned char) *s); s++);
    +
    621 if (s != line)
    +
    622 memmove(line, s, strlen(s)+1);
    +
    623}
    +
    624
    +
    625static void parse_line(char *line, int linenum)
    +
    626{
    +
    627 int tmp;
    +
    628 if (strcmp(line, "user_allow_other") == 0)
    +
    629 user_allow_other = 1;
    +
    630 else if (sscanf(line, "mount_max = %i", &tmp) == 1)
    +
    631 mount_max = tmp;
    +
    632 else if(line[0])
    +
    633 fprintf(stderr,
    +
    634 "%s: unknown parameter in %s at line %i: '%s'\n",
    +
    635 progname, FUSE_CONF, linenum, line);
    +
    636}
    +
    637
    +
    638static void read_conf(void)
    +
    639{
    +
    640 FILE *fp = fopen(FUSE_CONF, "r");
    +
    641 if (fp != NULL) {
    +
    642 int linenum = 1;
    +
    643 char line[256];
    +
    644 int isnewline = 1;
    +
    645 while (fgets(line, sizeof(line), fp) != NULL) {
    +
    646 if (isnewline) {
    +
    647 if (line[strlen(line)-1] == '\n') {
    +
    648 strip_line(line);
    +
    649 parse_line(line, linenum);
    +
    650 } else {
    +
    651 isnewline = 0;
    +
    652 }
    +
    653 } else if(line[strlen(line)-1] == '\n') {
    +
    654 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
    +
    655
    +
    656 isnewline = 1;
    +
    657 }
    +
    658 if (isnewline)
    +
    659 linenum ++;
    +
    660 }
    +
    661 if (!isnewline) {
    +
    662 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
    +
    663
    +
    664 }
    +
    665 if (ferror(fp)) {
    +
    666 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
    +
    667 exit(1);
    +
    668 }
    +
    669 fclose(fp);
    +
    670 } else if (errno != ENOENT) {
    +
    671 bool fatal = (errno != EACCES && errno != ELOOP &&
    +
    672 errno != ENAMETOOLONG && errno != ENOTDIR &&
    +
    673 errno != EOVERFLOW);
    +
    674 fprintf(stderr, "%s: failed to open %s: %s\n",
    +
    675 progname, FUSE_CONF, strerror(errno));
    +
    676 if (fatal)
    +
    677 exit(1);
    +
    678 }
    +
    679}
    +
    680
    +
    681static int begins_with(const char *s, const char *beg)
    +
    682{
    +
    683 if (strncmp(s, beg, strlen(beg)) == 0)
    +
    684 return 1;
    +
    685 else
    +
    686 return 0;
    +
    687}
    +
    688
    +
    689struct mount_flags {
    +
    690 const char *opt;
    +
    691 unsigned long flag;
    +
    692 int on;
    +
    693 int safe;
    +
    694};
    +
    695
    +
    696static struct mount_flags mount_flags[] = {
    +
    697 {"rw", MS_RDONLY, 0, 1},
    +
    698 {"ro", MS_RDONLY, 1, 1},
    +
    699 {"suid", MS_NOSUID, 0, 0},
    +
    700 {"nosuid", MS_NOSUID, 1, 1},
    +
    701 {"dev", MS_NODEV, 0, 0},
    +
    702 {"nodev", MS_NODEV, 1, 1},
    +
    703 {"exec", MS_NOEXEC, 0, 1},
    +
    704 {"noexec", MS_NOEXEC, 1, 1},
    +
    705 {"async", MS_SYNCHRONOUS, 0, 1},
    +
    706 {"sync", MS_SYNCHRONOUS, 1, 1},
    +
    707 {"atime", MS_NOATIME, 0, 1},
    +
    708 {"noatime", MS_NOATIME, 1, 1},
    +
    709 {"diratime", MS_NODIRATIME, 0, 1},
    +
    710 {"nodiratime", MS_NODIRATIME, 1, 1},
    +
    711 {"lazytime", MS_LAZYTIME, 1, 1},
    +
    712 {"nolazytime", MS_LAZYTIME, 0, 1},
    +
    713 {"relatime", MS_RELATIME, 1, 1},
    +
    714 {"norelatime", MS_RELATIME, 0, 1},
    +
    715 {"strictatime", MS_STRICTATIME, 1, 1},
    +
    716 {"nostrictatime", MS_STRICTATIME, 0, 1},
    +
    717 {"dirsync", MS_DIRSYNC, 1, 1},
    +
    718 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
    +
    719 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
    +
    720 {NULL, 0, 0, 0}
    +
    721};
    +
    722
    +
    723static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
    +
    724{
    +
    725 int i;
    +
    726
    +
    727 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    728 const char *opt = mount_flags[i].opt;
    +
    729 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
    +
    730 *on = mount_flags[i].on;
    +
    731 *flag = mount_flags[i].flag;
    +
    732 if (!mount_flags[i].safe && getuid() != 0) {
    +
    733 *flag = 0;
    +
    734 fprintf(stderr,
    +
    735 "%s: unsafe option %s ignored\n",
    +
    736 progname, opt);
    +
    737 }
    +
    738 return 1;
    +
    739 }
    +
    740 }
    +
    741 return 0;
    +
    742}
    +
    743
    +
    744static int add_option(char **optsp, const char *opt, unsigned expand)
    +
    745{
    +
    746 char *newopts;
    +
    747 if (*optsp == NULL)
    +
    748 newopts = strdup(opt);
    +
    749 else {
    +
    750 unsigned oldsize = strlen(*optsp);
    +
    751 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
    +
    752 newopts = (char *) realloc(*optsp, newsize);
    +
    753 if (newopts)
    +
    754 sprintf(newopts + oldsize, ",%s", opt);
    +
    755 }
    +
    756 if (newopts == NULL) {
    +
    757 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    758 return -1;
    +
    759 }
    +
    760 *optsp = newopts;
    +
    761 return 0;
    +
    762}
    +
    763
    +
    764static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
    +
    765{
    +
    766 int i;
    +
    767 int l;
    +
    768
    +
    769 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
    +
    770 return -1;
    +
    771
    +
    772 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    773 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
    +
    774 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
    +
    775 return -1;
    +
    776 }
    +
    777
    +
    778 if (add_option(mnt_optsp, opts, 0) == -1)
    +
    779 return -1;
    +
    780 /* remove comma from end of opts*/
    +
    781 l = strlen(*mnt_optsp);
    +
    782 if ((*mnt_optsp)[l-1] == ',')
    +
    783 (*mnt_optsp)[l-1] = '\0';
    +
    784 if (getuid() != 0) {
    +
    785 const char *user = get_user_name();
    +
    786 if (user == NULL)
    +
    787 return -1;
    +
    788
    +
    789 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
    +
    790 return -1;
    +
    791 strcat(*mnt_optsp, user);
    +
    792 }
    +
    793 return 0;
    +
    794}
    +
    795
    +
    796static int opt_eq(const char *s, unsigned len, const char *opt)
    +
    797{
    +
    798 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
    +
    799 return 1;
    +
    800 else
    +
    801 return 0;
    +
    802}
    +
    803
    +
    804static int get_string_opt(const char *s, unsigned len, const char *opt,
    +
    805 char **val)
    +
    806{
    +
    807 int i;
    +
    808 unsigned opt_len = strlen(opt);
    +
    809 char *d;
    +
    810
    +
    811 if (*val)
    +
    812 free(*val);
    +
    813 *val = (char *) malloc(len - opt_len + 1);
    +
    814 if (!*val) {
    +
    815 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    816 return 0;
    +
    817 }
    +
    818
    +
    819 d = *val;
    +
    820 s += opt_len;
    +
    821 len -= opt_len;
    +
    822 for (i = 0; i < len; i++) {
    +
    823 if (s[i] == '\\' && i + 1 < len)
    +
    824 i++;
    +
    825 *d++ = s[i];
    +
    826 }
    +
    827 *d = '\0';
    +
    828 return 1;
    +
    829}
    +
    830
    +
    831/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
    +
    832 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
    +
    833 * "group_id=1".
    +
    834 * This wrapper detects this case and bails out with an error.
    +
    835 */
    +
    836static int mount_notrunc(const char *source, const char *target,
    +
    837 const char *filesystemtype, unsigned long mountflags,
    +
    838 const char *data) {
    +
    839 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
    +
    840 fprintf(stderr, "%s: mount options too long\n", progname);
    +
    841 errno = EINVAL;
    +
    842 return -1;
    +
    843 }
    +
    844 return mount(source, target, filesystemtype, mountflags, data);
    +
    845}
    +
    846
    +
    847
    +
    848static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
    +
    849 int fd, const char *opts, const char *dev, char **sourcep,
    +
    850 char **mnt_optsp)
    +
    851{
    +
    852 int res;
    +
    853 int flags = MS_NOSUID | MS_NODEV;
    +
    854 char *optbuf;
    +
    855 char *mnt_opts = NULL;
    +
    856 const char *s;
    +
    857 char *d;
    +
    858 char *fsname = NULL;
    +
    859 char *subtype = NULL;
    +
    860 char *source = NULL;
    +
    861 char *type = NULL;
    +
    862 int blkdev = 0;
    +
    863
    +
    864 optbuf = (char *) malloc(strlen(opts) + 128);
    +
    865 if (!optbuf) {
    +
    866 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    867 return -1;
    +
    868 }
    +
    869
    +
    870 for (s = opts, d = optbuf; *s;) {
    +
    871 unsigned len;
    +
    872 const char *fsname_str = "fsname=";
    +
    873 const char *subtype_str = "subtype=";
    +
    874 bool escape_ok = begins_with(s, fsname_str) ||
    +
    875 begins_with(s, subtype_str);
    +
    876 for (len = 0; s[len]; len++) {
    +
    877 if (escape_ok && s[len] == '\\' && s[len + 1])
    +
    878 len++;
    +
    879 else if (s[len] == ',')
    +
    880 break;
    +
    881 }
    +
    882 if (begins_with(s, fsname_str)) {
    +
    883 if (!get_string_opt(s, len, fsname_str, &fsname))
    +
    884 goto err;
    +
    885 } else if (begins_with(s, subtype_str)) {
    +
    886 if (!get_string_opt(s, len, subtype_str, &subtype))
    +
    887 goto err;
    +
    888 } else if (opt_eq(s, len, "blkdev")) {
    +
    889 if (getuid() != 0) {
    +
    890 fprintf(stderr,
    +
    891 "%s: option blkdev is privileged\n",
    +
    892 progname);
    +
    893 goto err;
    +
    894 }
    +
    895 blkdev = 1;
    +
    896 } else if (opt_eq(s, len, "auto_unmount")) {
    +
    897 auto_unmount = 1;
    +
    898 } else if (!opt_eq(s, len, "nonempty") &&
    +
    899 !begins_with(s, "fd=") &&
    +
    900 !begins_with(s, "rootmode=") &&
    +
    901 !begins_with(s, "user_id=") &&
    +
    902 !begins_with(s, "group_id=")) {
    +
    903 int on;
    +
    904 int flag;
    +
    905 int skip_option = 0;
    +
    906 if (opt_eq(s, len, "large_read")) {
    +
    907 struct utsname utsname;
    +
    908 unsigned kmaj, kmin;
    +
    909 res = uname(&utsname);
    +
    910 if (res == 0 &&
    +
    911 sscanf(utsname.release, "%u.%u",
    +
    912 &kmaj, &kmin) == 2 &&
    +
    913 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
    +
    914 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
    +
    915 skip_option = 1;
    +
    916 }
    +
    917 }
    +
    918 if (getuid() != 0 && !user_allow_other &&
    +
    919 (opt_eq(s, len, "allow_other") ||
    +
    920 opt_eq(s, len, "allow_root"))) {
    +
    921 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
    +
    922 goto err;
    +
    923 }
    +
    924 if (!skip_option) {
    +
    925 if (find_mount_flag(s, len, &on, &flag)) {
    +
    926 if (on)
    +
    927 flags |= flag;
    +
    928 else
    +
    929 flags &= ~flag;
    +
    930 } else if (opt_eq(s, len, "default_permissions") ||
    +
    931 opt_eq(s, len, "allow_other") ||
    +
    932 begins_with(s, "max_read=") ||
    +
    933 begins_with(s, "blksize=")) {
    +
    934 memcpy(d, s, len);
    +
    935 d += len;
    +
    936 *d++ = ',';
    +
    937 } else {
    +
    938 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
    +
    939 exit(1);
    +
    940 }
    +
    941 }
    +
    942 }
    +
    943 s += len;
    +
    944 if (*s)
    +
    945 s++;
    +
    946 }
    +
    947 *d = '\0';
    +
    948 res = get_mnt_opts(flags, optbuf, &mnt_opts);
    +
    949 if (res == -1)
    +
    950 goto err;
    +
    951
    +
    952 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
    +
    953 fd, rootmode, getuid(), getgid());
    +
    954
    +
    955 source = malloc((fsname ? strlen(fsname) : 0) +
    +
    956 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
    +
    957
    +
    958 type = malloc((subtype ? strlen(subtype) : 0) + 32);
    +
    959 if (!type || !source) {
    +
    960 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    961 goto err;
    +
    962 }
    +
    963
    +
    964 if (subtype)
    +
    965 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
    +
    966 else
    +
    967 strcpy(type, blkdev ? "fuseblk" : "fuse");
    +
    968
    +
    969 if (fsname)
    +
    970 strcpy(source, fsname);
    +
    971 else
    +
    972 strcpy(source, subtype ? subtype : dev);
    +
    973
    +
    974 res = mount_notrunc(source, mnt, type, flags, optbuf);
    +
    975 if (res == -1 && errno == ENODEV && subtype) {
    +
    976 /* Probably missing subtype support */
    +
    977 strcpy(type, blkdev ? "fuseblk" : "fuse");
    +
    978 if (fsname) {
    +
    979 if (!blkdev)
    +
    980 sprintf(source, "%s#%s", subtype, fsname);
    +
    981 } else {
    +
    982 strcpy(source, type);
    +
    983 }
    +
    984
    +
    985 res = mount_notrunc(source, mnt, type, flags, optbuf);
    +
    986 }
    +
    987 if (res == -1 && errno == EINVAL) {
    +
    988 /* It could be an old version not supporting group_id */
    +
    989 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
    +
    990 fd, rootmode, getuid());
    +
    991 res = mount_notrunc(source, mnt, type, flags, optbuf);
    +
    992 }
    +
    993 if (res == -1) {
    +
    994 int errno_save = errno;
    +
    995 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
    +
    996 fprintf(stderr, "%s: 'fuseblk' support missing\n",
    +
    997 progname);
    +
    998 else
    +
    999 fprintf(stderr, "%s: mount failed: %s\n", progname,
    +
    1000 strerror(errno_save));
    +
    1001 goto err;
    +
    1002 }
    +
    1003 *sourcep = source;
    +
    1004 *typep = type;
    +
    1005 *mnt_optsp = mnt_opts;
    +
    1006 free(fsname);
    +
    1007 free(optbuf);
    +
    1008
    +
    1009 return 0;
    +
    1010
    +
    1011err:
    +
    1012 free(fsname);
    +
    1013 free(subtype);
    +
    1014 free(source);
    +
    1015 free(type);
    +
    1016 free(mnt_opts);
    +
    1017 free(optbuf);
    +
    1018 return -1;
    +
    1019}
    +
    1020
    +
    1021static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
    +
    1022{
    +
    1023 int res;
    +
    1024 const char *mnt = *mntp;
    +
    1025 const char *origmnt = mnt;
    +
    1026 struct statfs fs_buf;
    +
    1027 size_t i;
    +
    1028
    +
    1029 res = lstat(mnt, stbuf);
    +
    1030 if (res == -1) {
    +
    1031 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
    +
    1032 progname, mnt, strerror(errno));
    +
    1033 return -1;
    +
    1034 }
    +
    1035
    +
    1036 /* No permission checking is done for root */
    +
    1037 if (getuid() == 0)
    +
    1038 return 0;
    +
    1039
    +
    1040 if (S_ISDIR(stbuf->st_mode)) {
    +
    1041 res = chdir(mnt);
    +
    1042 if (res == -1) {
    +
    1043 fprintf(stderr,
    +
    1044 "%s: failed to chdir to mountpoint: %s\n",
    +
    1045 progname, strerror(errno));
    +
    1046 return -1;
    +
    1047 }
    +
    1048 mnt = *mntp = ".";
    +
    1049 res = lstat(mnt, stbuf);
    +
    1050 if (res == -1) {
    +
    1051 fprintf(stderr,
    +
    1052 "%s: failed to access mountpoint %s: %s\n",
    +
    1053 progname, origmnt, strerror(errno));
    +
    1054 return -1;
    +
    1055 }
    +
    1056
    +
    1057 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
    +
    1058 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
    +
    1059 progname, origmnt);
    +
    1060 return -1;
    +
    1061 }
    +
    1062
    +
    1063 res = access(mnt, W_OK);
    +
    1064 if (res == -1) {
    +
    1065 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
    +
    1066 progname, origmnt);
    +
    1067 return -1;
    +
    1068 }
    +
    1069 } else if (S_ISREG(stbuf->st_mode)) {
    +
    1070 static char procfile[256];
    +
    1071 *mountpoint_fd = open(mnt, O_WRONLY);
    +
    1072 if (*mountpoint_fd == -1) {
    +
    1073 fprintf(stderr, "%s: failed to open %s: %s\n",
    +
    1074 progname, mnt, strerror(errno));
    +
    1075 return -1;
    +
    1076 }
    +
    1077 res = fstat(*mountpoint_fd, stbuf);
    +
    1078 if (res == -1) {
    +
    1079 fprintf(stderr,
    +
    1080 "%s: failed to access mountpoint %s: %s\n",
    +
    1081 progname, mnt, strerror(errno));
    +
    1082 return -1;
    +
    1083 }
    +
    1084 if (!S_ISREG(stbuf->st_mode)) {
    +
    1085 fprintf(stderr,
    +
    1086 "%s: mountpoint %s is no longer a regular file\n",
    +
    1087 progname, mnt);
    +
    1088 return -1;
    +
    1089 }
    +
    1090
    +
    1091 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
    +
    1092 *mntp = procfile;
    +
    1093 } else {
    +
    1094 fprintf(stderr,
    +
    1095 "%s: mountpoint %s is not a directory or a regular file\n",
    +
    1096 progname, mnt);
    +
    1097 return -1;
    +
    1098 }
    +
    1099
    +
    1100 /* Do not permit mounting over anything in procfs - it has a couple
    +
    1101 * places to which we have "write access" without being supposed to be
    +
    1102 * able to just put anything we want there.
    +
    1103 * Luckily, without allow_other, we can't get other users to actually
    +
    1104 * use any fake information we try to put there anyway.
    +
    1105 * Use a whitelist to be safe. */
    +
    1106 if (statfs(*mntp, &fs_buf)) {
    +
    1107 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
    +
    1108 progname, mnt, strerror(errno));
    +
    1109 return -1;
    +
    1110 }
    +
    1111
    +
    1112 /* Define permitted filesystems for the mount target. This was
    +
    1113 * originally the same list as used by the ecryptfs mount helper
    +
    1114 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
    +
    1115 * but got expanded as we found more filesystems that needed to be
    +
    1116 * overlaid. */
    +
    1117 typeof(fs_buf.f_type) f_type_whitelist[] = {
    +
    1118 0x61756673 /* AUFS_SUPER_MAGIC */,
    +
    1119 0x00000187 /* AUTOFS_SUPER_MAGIC */,
    +
    1120 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
    +
    1121 0x9123683E /* BTRFS_SUPER_MAGIC */,
    +
    1122 0x00C36400 /* CEPH_SUPER_MAGIC */,
    +
    1123 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
    +
    1124 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
    +
    1125 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
    +
    1126 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
    +
    1127 0xF2F52010 /* F2FS_SUPER_MAGIC */,
    +
    1128 0x65735546 /* FUSE_SUPER_MAGIC */,
    +
    1129 0x01161970 /* GFS2_MAGIC */,
    +
    1130 0x47504653 /* GPFS_SUPER_MAGIC */,
    +
    1131 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
    +
    1132 0x000072B6 /* JFFS2_SUPER_MAGIC */,
    +
    1133 0x3153464A /* JFS_SUPER_MAGIC */,
    +
    1134 0x0BD00BD0 /* LL_SUPER_MAGIC */,
    +
    1135 0X00004D44 /* MSDOS_SUPER_MAGIC */,
    +
    1136 0x0000564C /* NCP_SUPER_MAGIC */,
    +
    1137 0x00006969 /* NFS_SUPER_MAGIC */,
    +
    1138 0x00003434 /* NILFS_SUPER_MAGIC */,
    +
    1139 0x5346544E /* NTFS_SB_MAGIC */,
    +
    1140 0x7366746E /* NTFS3_SUPER_MAGIC */,
    +
    1141 0x5346414f /* OPENAFS_SUPER_MAGIC */,
    +
    1142 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
    +
    1143 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
    +
    1144 0x52654973 /* REISERFS_SUPER_MAGIC */,
    +
    1145 0xFE534D42 /* SMB2_SUPER_MAGIC */,
    +
    1146 0x73717368 /* SQUASHFS_MAGIC */,
    +
    1147 0x01021994 /* TMPFS_MAGIC */,
    +
    1148 0x24051905 /* UBIFS_SUPER_MAGIC */,
    +
    1149#if __SIZEOF_LONG__ > 4
    +
    1150 0x736675005346544e /* UFSD */,
    +
    1151#endif
    +
    1152 0x58465342 /* XFS_SB_MAGIC */,
    +
    1153 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
    +
    1154 0x858458f6 /* RAMFS_MAGIC */,
    +
    1155 };
    +
    1156 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
    +
    1157 if (f_type_whitelist[i] == fs_buf.f_type)
    +
    1158 return 0;
    +
    1159 }
    +
    1160
    +
    1161 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
    +
    1162 progname, (unsigned long)fs_buf.f_type);
    +
    1163 return -1;
    +
    1164}
    +
    1165
    +
    1166static int try_open(const char *dev, char **devp, int silent)
    +
    1167{
    +
    1168 int fd = open(dev, O_RDWR);
    +
    1169 if (fd != -1) {
    +
    1170 *devp = strdup(dev);
    +
    1171 if (*devp == NULL) {
    +
    1172 fprintf(stderr, "%s: failed to allocate memory\n",
    +
    1173 progname);
    +
    1174 close(fd);
    +
    1175 fd = -1;
    +
    1176 }
    +
    1177 } else if (errno == ENODEV ||
    +
    1178 errno == ENOENT)/* check for ENOENT too, for the udev case */
    +
    1179 return -2;
    +
    1180 else if (!silent) {
    +
    1181 fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
    +
    1182 strerror(errno));
    +
    1183 }
    +
    1184 return fd;
    +
    1185}
    +
    1186
    +
    1187static int try_open_fuse_device(char **devp)
    +
    1188{
    +
    1189 int fd;
    +
    1190
    +
    1191 drop_privs();
    +
    1192 fd = try_open(FUSE_DEV, devp, 0);
    +
    1193 restore_privs();
    +
    1194 return fd;
    +
    1195}
    +
    1196
    +
    1197static int open_fuse_device(char **devp)
    +
    1198{
    +
    1199 int fd = try_open_fuse_device(devp);
    +
    1200 if (fd >= -1)
    +
    1201 return fd;
    +
    1202
    +
    1203 fprintf(stderr,
    +
    1204 "%s: fuse device not found, try 'modprobe fuse' first\n",
    +
    1205 progname);
    +
    1206
    +
    1207 return -1;
    +
    1208}
    +
    1209
    +
    1210
    +
    1211static int mount_fuse(const char *mnt, const char *opts, const char **type)
    +
    1212{
    +
    1213 int res;
    +
    1214 int fd;
    +
    1215 char *dev;
    +
    1216 struct stat stbuf;
    +
    1217 char *source = NULL;
    +
    1218 char *mnt_opts = NULL;
    +
    1219 const char *real_mnt = mnt;
    +
    1220 int mountpoint_fd = -1;
    +
    1221 char *do_mount_opts = NULL;
    +
    1222 char *x_opts = NULL;
    +
    1223
    +
    1224 fd = open_fuse_device(&dev);
    +
    1225 if (fd == -1)
    +
    1226 return -1;
    +
    1227
    +
    1228 drop_privs();
    +
    1229 read_conf();
    +
    1230
    +
    1231 if (getuid() != 0 && mount_max != -1) {
    +
    1232 int mount_count = count_fuse_fs();
    +
    1233 if (mount_count >= mount_max) {
    +
    1234 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
    +
    1235 goto fail_close_fd;
    +
    1236 }
    +
    1237 }
    +
    1238
    +
    1239 // Extract any options starting with "x-"
    +
    1240 res= extract_x_options(opts, &do_mount_opts, &x_opts);
    +
    1241 if (res)
    +
    1242 goto fail_close_fd;
    +
    1243
    +
    1244 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
    +
    1245 restore_privs();
    +
    1246 if (res != -1)
    +
    1247 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
    +
    1248 fd, do_mount_opts, dev, &source, &mnt_opts);
    +
    1249
    +
    1250 if (mountpoint_fd != -1)
    +
    1251 close(mountpoint_fd);
    +
    1252
    +
    1253 if (res == -1)
    +
    1254 goto fail_close_fd;
    +
    1255
    +
    1256 res = chdir("/");
    +
    1257 if (res == -1) {
    +
    1258 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    1259 goto fail_close_fd;
    +
    1260 }
    +
    1261
    +
    1262 if (geteuid() == 0) {
    +
    1263 if (x_opts && strlen(x_opts) > 0) {
    +
    1264 /*
    +
    1265 * Add back the options starting with "x-" to opts from
    +
    1266 * do_mount. +2 for ',' and '\0'
    +
    1267 */
    +
    1268 size_t mnt_opts_len = strlen(mnt_opts);
    +
    1269 size_t x_mnt_opts_len = mnt_opts_len+
    +
    1270 strlen(x_opts) + 2;
    +
    1271 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
    +
    1272
    +
    1273 if (mnt_opts_len) {
    +
    1274 strcpy(x_mnt_opts, mnt_opts);
    +
    1275 strncat(x_mnt_opts, ",", 2);
    +
    1276 }
    +
    1277
    +
    1278 strncat(x_mnt_opts, x_opts,
    +
    1279 x_mnt_opts_len - mnt_opts_len - 2);
    +
    1280
    +
    1281 free(mnt_opts);
    +
    1282 mnt_opts = x_mnt_opts;
    +
    1283 }
    +
    1284
    +
    1285 res = add_mount(source, mnt, *type, mnt_opts);
    +
    1286 if (res == -1) {
    +
    1287 /* Can't clean up mount in a non-racy way */
    +
    1288 goto fail_close_fd;
    +
    1289 }
    +
    1290 }
    +
    1291
    +
    1292out_free:
    +
    1293 free(source);
    +
    1294 free(mnt_opts);
    +
    1295 free(dev);
    +
    1296 free(x_opts);
    +
    1297 free(do_mount_opts);
    +
    1298
    +
    1299 return fd;
    +
    1300
    +
    1301fail_close_fd:
    +
    1302 close(fd);
    +
    1303 fd = -1;
    +
    1304 goto out_free;
    +
    1305}
    +
    1306
    +
    1307static int send_fd(int sock_fd, int fd)
    +
    1308{
    +
    1309 int retval;
    +
    1310 struct msghdr msg;
    +
    1311 struct cmsghdr *p_cmsg;
    +
    1312 struct iovec vec;
    +
    1313 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
    +
    1314 int *p_fds;
    +
    1315 char sendchar = 0;
    +
    1316
    +
    1317 msg.msg_control = cmsgbuf;
    +
    1318 msg.msg_controllen = sizeof(cmsgbuf);
    +
    1319 p_cmsg = CMSG_FIRSTHDR(&msg);
    +
    1320 p_cmsg->cmsg_level = SOL_SOCKET;
    +
    1321 p_cmsg->cmsg_type = SCM_RIGHTS;
    +
    1322 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
    +
    1323 p_fds = (int *) CMSG_DATA(p_cmsg);
    +
    1324 *p_fds = fd;
    +
    1325 msg.msg_controllen = p_cmsg->cmsg_len;
    +
    1326 msg.msg_name = NULL;
    +
    1327 msg.msg_namelen = 0;
    +
    1328 msg.msg_iov = &vec;
    +
    1329 msg.msg_iovlen = 1;
    +
    1330 msg.msg_flags = 0;
    +
    1331 /* "To pass file descriptors or credentials you need to send/read at
    +
    1332 * least one byte" (man 7 unix) */
    +
    1333 vec.iov_base = &sendchar;
    +
    1334 vec.iov_len = sizeof(sendchar);
    +
    1335 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
    +
    1336 if (retval != 1) {
    +
    1337 perror("sending file descriptor");
    +
    1338 return -1;
    +
    1339 }
    +
    1340 return 0;
    +
    1341}
    +
    1342
    +
    1343/* Helper for should_auto_unmount
    +
    1344 *
    +
    1345 * fusermount typically has the s-bit set - initial open of `mnt` was as root
    +
    1346 * and got EACCESS as 'allow_other' was not specified.
    +
    1347 * Try opening `mnt` again with uid and guid of the calling process.
    +
    1348 */
    +
    1349static int recheck_ENOTCONN_as_owner(const char *mnt)
    +
    1350{
    +
    1351 int pid = fork();
    +
    1352 if(pid == -1) {
    +
    1353 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
    +
    1354 _exit(EXIT_FAILURE);
    +
    1355 } else if(pid == 0) {
    +
    1356 uid_t uid = getuid();
    +
    1357 gid_t gid = getgid();
    +
    1358 if(setresgid(gid, gid, gid) == -1) {
    +
    1359 perror("fuse: can't set resgid");
    +
    1360 _exit(EXIT_FAILURE);
    +
    1361 }
    +
    1362 if(setresuid(uid, uid, uid) == -1) {
    +
    1363 perror("fuse: can't set resuid");
    +
    1364 _exit(EXIT_FAILURE);
    +
    1365 }
    +
    1366
    +
    1367 int fd = open(mnt, O_RDONLY);
    +
    1368 if(fd == -1 && errno == ENOTCONN)
    +
    1369 _exit(EXIT_SUCCESS);
    +
    1370 else
    +
    1371 _exit(EXIT_FAILURE);
    +
    1372 } else {
    +
    1373 int status;
    +
    1374 int res = waitpid(pid, &status, 0);
    +
    1375 if (res == -1) {
    +
    1376 perror("fuse: waiting for child failed");
    +
    1377 _exit(EXIT_FAILURE);
    +
    1378 }
    +
    1379 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
    +
    1380 }
    +
    1381}
    +
    1382
    +
    1383/* The parent fuse process has died: decide whether to auto_unmount.
    +
    1384 *
    +
    1385 * In the normal case (umount or fusermount -u), the filesystem
    +
    1386 * has already been unmounted. If we simply unmount again we can
    +
    1387 * cause problems with stacked mounts (e.g. autofs).
    +
    1388 *
    +
    1389 * So we unmount here only in abnormal case where fuse process has
    +
    1390 * died without unmount happening. To detect this, we first look in
    +
    1391 * the mount table to make sure the mountpoint is still mounted and
    +
    1392 * has proper type. If so, we then see if opening the mount dir is
    +
    1393 * returning 'Transport endpoint is not connected'.
    +
    1394 *
    +
    1395 * The order of these is important, because if autofs is in use,
    +
    1396 * opening the dir to check for ENOTCONN will cause a new mount
    +
    1397 * in the normal case where filesystem has been unmounted cleanly.
    +
    1398 */
    +
    1399static int should_auto_unmount(const char *mnt, const char *type)
    +
    1400{
    +
    1401 char *copy;
    +
    1402 const char *last;
    +
    1403 int result = 0;
    +
    1404 int fd;
    +
    1405
    +
    1406 copy = strdup(mnt);
    +
    1407 if (copy == NULL) {
    +
    1408 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    1409 return 0;
    +
    1410 }
    +
    1411
    +
    1412 if (chdir_to_parent(copy, &last) == -1)
    +
    1413 goto out;
    +
    1414 if (check_is_mount(last, mnt, type) == -1)
    +
    1415 goto out;
    +
    1416
    +
    1417 fd = open(mnt, O_RDONLY);
    +
    1418
    +
    1419 if (fd != -1) {
    +
    1420 close(fd);
    +
    1421 } else {
    +
    1422 switch(errno) {
    +
    1423 case ENOTCONN:
    +
    1424 result = 1;
    +
    1425 break;
    +
    1426 case EACCES:
    +
    1427 result = recheck_ENOTCONN_as_owner(mnt);
    +
    1428 break;
    +
    1429 default:
    +
    1430 result = 0;
    +
    1431 break;
    +
    1432 }
    +
    1433 }
    +
    1434out:
    +
    1435 free(copy);
    +
    1436 return result;
    +
    1437}
    +
    1438
    +
    1439static void usage(void)
    +
    1440{
    +
    1441 printf("%s: [options] mountpoint\n"
    +
    1442 "Options:\n"
    +
    1443 " -h print help\n"
    +
    1444 " -V print version\n"
    +
    1445 " -o opt[,opt...] mount options\n"
    +
    1446 " -u unmount\n"
    +
    1447 " -q quiet\n"
    +
    1448 " -z lazy unmount\n",
    +
    1449 progname);
    +
    1450 exit(1);
    +
    1451}
    +
    1452
    +
    1453static void show_version(void)
    +
    1454{
    +
    1455 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
    +
    1456 exit(0);
    +
    1457}
    +
    1458
    +
    1459static void close_range_loop(int min_fd, int max_fd, int cfd)
    +
    1460{
    +
    1461 for (int fd = min_fd; fd <= max_fd; fd++)
    +
    1462 if (fd != cfd)
    +
    1463 close(fd);
    +
    1464}
    +
    1465
    +
    1466/*
    +
    1467 * Close all inherited fds that are not needed
    +
    1468 * Ideally these wouldn't come up at all, applications should better
    +
    1469 * use FD_CLOEXEC / O_CLOEXEC
    +
    1470 */
    +
    1471static int close_inherited_fds(int cfd)
    +
    1472{
    +
    1473 int rc = -1;
    +
    1474 int nullfd;
    +
    1475
    +
    1476 /* We can't even report an error */
    +
    1477 if (cfd <= STDERR_FILENO)
    +
    1478 return -EINVAL;
    +
    1479
    +
    1480#ifdef HAVE_LINUX_CLOSE_RANGE_H
    +
    1481 if (cfd < STDERR_FILENO + 2) {
    +
    1482 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
    +
    1483 } else {
    +
    1484 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
    +
    1485 if (rc < 0)
    +
    1486 goto fallback;
    +
    1487 }
    +
    1488
    +
    1489 /* Close high range */
    +
    1490 rc = close_range(cfd + 1, ~0U, 0);
    +
    1491#else
    +
    1492 goto fallback; /* make use of fallback to avoid compiler warnings */
    +
    1493#endif
    +
    1494
    +
    1495fallback:
    +
    1496 if (rc < 0) {
    +
    1497 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
    +
    1498
    +
    1499 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
    +
    1500 }
    +
    1501
    +
    1502 nullfd = open("/dev/null", O_RDWR);
    +
    1503 if (nullfd < 0) {
    +
    1504 perror("fusermount: cannot open /dev/null");
    +
    1505 return -errno;
    +
    1506 }
    +
    1507
    +
    1508 /* Redirect stdin, stdout, stderr to /dev/null */
    +
    1509 dup2(nullfd, STDIN_FILENO);
    +
    1510 dup2(nullfd, STDOUT_FILENO);
    +
    1511 dup2(nullfd, STDERR_FILENO);
    +
    1512 if (nullfd > STDERR_FILENO)
    +
    1513 close(nullfd);
    +
    1514
    +
    1515 return 0;
    +
    1516}
    +
    1517
    +
    1518int main(int argc, char *argv[])
    +
    1519{
    +
    1520 sigset_t sigset;
    +
    1521 int ch;
    +
    1522 int fd;
    +
    1523 int res;
    +
    1524 char *origmnt;
    +
    1525 char *mnt;
    +
    1526 static int unmount = 0;
    +
    1527 static int lazy = 0;
    +
    1528 static int quiet = 0;
    +
    1529 char *commfd = NULL;
    +
    1530 long cfd;
    +
    1531 const char *opts = "";
    +
    1532 const char *type = NULL;
    +
    1533 int setup_auto_unmount_only = 0;
    +
    1534
    +
    1535 static const struct option long_opts[] = {
    +
    1536 {"unmount", no_argument, NULL, 'u'},
    +
    1537 {"lazy", no_argument, NULL, 'z'},
    +
    1538 {"quiet", no_argument, NULL, 'q'},
    +
    1539 {"help", no_argument, NULL, 'h'},
    +
    1540 {"version", no_argument, NULL, 'V'},
    +
    1541 {"options", required_argument, NULL, 'o'},
    +
    1542 // Note: auto-unmount and comm-fd don't have short versions.
    +
    1543 // They'ne meant for internal use by mount.c
    +
    1544 {"auto-unmount", no_argument, NULL, 'U'},
    +
    1545 {"comm-fd", required_argument, NULL, 'c'},
    +
    1546 {0, 0, 0, 0}};
    +
    1547
    +
    1548 progname = strdup(argc > 0 ? argv[0] : "fusermount");
    +
    1549 if (progname == NULL) {
    +
    1550 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
    +
    1551 exit(1);
    +
    1552 }
    +
    1553
    +
    1554 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
    +
    1555 NULL)) != -1) {
    +
    1556 switch (ch) {
    +
    1557 case 'h':
    +
    1558 usage();
    +
    1559 break;
    +
    1560
    +
    1561 case 'V':
    +
    1562 show_version();
    +
    1563 break;
    +
    1564
    +
    1565 case 'o':
    +
    1566 opts = optarg;
    +
    1567 break;
    +
    1568
    +
    1569 case 'u':
    +
    1570 unmount = 1;
    +
    1571 break;
    +
    1572 case 'U':
    +
    1573 unmount = 1;
    +
    1574 auto_unmount = 1;
    +
    1575 setup_auto_unmount_only = 1;
    +
    1576 break;
    +
    1577 case 'c':
    +
    1578 commfd = optarg;
    +
    1579 break;
    +
    1580 case 'z':
    +
    1581 lazy = 1;
    +
    1582 break;
    +
    1583
    +
    1584 case 'q':
    +
    1585 quiet = 1;
    +
    1586 break;
    +
    1587
    +
    1588 default:
    +
    1589 exit(1);
    +
    1590 }
    +
    1591 }
    +
    1592
    +
    1593 if (lazy && !unmount) {
    +
    1594 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
    +
    1595 exit(1);
    +
    1596 }
    +
    1597
    +
    1598 if (optind >= argc) {
    +
    1599 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
    +
    1600 exit(1);
    +
    1601 } else if (argc > optind + 1) {
    +
    1602 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
    +
    1603 progname);
    +
    1604 exit(1);
    +
    1605 }
    +
    1606
    +
    1607 origmnt = argv[optind];
    +
    1608
    +
    1609 drop_privs();
    +
    1610 mnt = fuse_mnt_resolve_path(progname, origmnt);
    +
    1611 if (mnt != NULL) {
    +
    1612 res = chdir("/");
    +
    1613 if (res == -1) {
    +
    1614 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    1615 goto err_out;
    +
    1616 }
    +
    1617 }
    +
    1618 restore_privs();
    +
    1619 if (mnt == NULL)
    +
    1620 exit(1);
    +
    1621
    +
    1622 umask(033);
    +
    1623 if (!setup_auto_unmount_only && unmount)
    +
    1624 goto do_unmount;
    +
    1625
    +
    1626 if(commfd == NULL)
    +
    1627 commfd = getenv(FUSE_COMMFD_ENV);
    +
    1628 if (commfd == NULL) {
    +
    1629 fprintf(stderr, "%s: old style mounting not supported\n",
    +
    1630 progname);
    +
    1631 goto err_out;
    +
    1632 }
    +
    1633
    +
    1634 res = libfuse_strtol(commfd, &cfd);
    +
    1635 if (res) {
    +
    1636 fprintf(stderr,
    +
    1637 "%s: invalid _FUSE_COMMFD: %s\n",
    +
    1638 progname, commfd);
    +
    1639 goto err_out;
    +
    1640
    +
    1641 }
    +
    1642
    +
    1643 {
    +
    1644 struct stat statbuf;
    +
    1645 fstat(cfd, &statbuf);
    +
    1646 if(!S_ISSOCK(statbuf.st_mode)) {
    +
    1647 fprintf(stderr,
    +
    1648 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
    +
    1649 progname, cfd);
    +
    1650 goto err_out;
    +
    1651 }
    +
    1652 }
    +
    1653
    +
    1654 if (setup_auto_unmount_only)
    +
    1655 goto wait_for_auto_unmount;
    +
    1656
    +
    1657 fd = mount_fuse(mnt, opts, &type);
    +
    1658 if (fd == -1)
    +
    1659 goto err_out;
    +
    1660
    +
    1661 res = send_fd(cfd, fd);
    +
    1662 if (res != 0) {
    +
    1663 umount2(mnt, MNT_DETACH); /* lazy umount */
    +
    1664 goto err_out;
    +
    1665 }
    +
    1666 close(fd);
    +
    1667
    +
    1668 if (!auto_unmount) {
    +
    1669 free(mnt);
    +
    1670 free((void*) type);
    +
    1671 return 0;
    +
    1672 }
    +
    1673
    +
    1674wait_for_auto_unmount:
    +
    1675 /* Become a daemon and wait for the parent to exit or die.
    +
    1676 ie For the control socket to get closed.
    +
    1677 Btw, we don't want to use daemon() function here because
    +
    1678 it forks and messes with the file descriptors. */
    +
    1679
    +
    1680 res = close_inherited_fds(cfd);
    +
    1681 if (res < 0)
    +
    1682 exit(EXIT_FAILURE);
    +
    1683
    +
    1684 setsid();
    +
    1685 res = chdir("/");
    +
    1686 if (res == -1) {
    +
    1687 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
    +
    1688 goto err_out;
    +
    1689 }
    +
    1690
    +
    1691 sigfillset(&sigset);
    +
    1692 sigprocmask(SIG_BLOCK, &sigset, NULL);
    +
    1693
    +
    1694 lazy = 1;
    +
    1695 quiet = 1;
    +
    1696
    +
    1697 while (1) {
    +
    1698 unsigned char buf[16];
    +
    1699 int n = recv(cfd, buf, sizeof(buf), 0);
    +
    1700 if (!n)
    +
    1701 break;
    +
    1702
    +
    1703 if (n < 0) {
    +
    1704 if (errno == EINTR)
    +
    1705 continue;
    +
    1706 break;
    +
    1707 }
    +
    1708 }
    +
    1709
    +
    1710 if (!should_auto_unmount(mnt, type)) {
    +
    1711 goto success_out;
    +
    1712 }
    +
    1713
    +
    1714do_unmount:
    +
    1715 if (geteuid() == 0)
    +
    1716 res = unmount_fuse(mnt, quiet, lazy);
    +
    1717 else {
    +
    1718 res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
    +
    1719 if (res == -1 && !quiet)
    +
    1720 fprintf(stderr,
    +
    1721 "%s: failed to unmount %s: %s\n",
    +
    1722 progname, mnt, strerror(errno));
    +
    1723 }
    +
    1724 if (res == -1)
    +
    1725 goto err_out;
    +
    1726
    +
    1727success_out:
    +
    1728 free((void*) type);
    +
    1729 free(mnt);
    +
    1730 return 0;
    +
    1731
    +
    1732err_out:
    +
    1733 free((void*) type);
    +
    1734 free(mnt);
    +
    1735 exit(1);
    +
    1736}
    +
    + + + + diff --git a/doc/html/util_2mount_8fuse_8c_source.html b/doc/html/util_2mount_8fuse_8c_source.html new file mode 100644 index 0000000..5d53c95 --- /dev/null +++ b/doc/html/util_2mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: util/mount.fuse.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    mount.fuse.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file COPYING.
    +
    7*/
    +
    8
    +
    9#include "fuse_config.h"
    +
    10
    +
    11#include <stdio.h>
    +
    12#include <stdlib.h>
    +
    13#include <string.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16#include <stdint.h>
    +
    17#include <fcntl.h>
    +
    18#include <pwd.h>
    +
    19#include <sys/wait.h>
    +
    20
    +
    21#ifdef linux
    +
    22#include <sys/prctl.h>
    +
    23#include <sys/syscall.h>
    +
    24#include <linux/capability.h>
    +
    25#include <linux/securebits.h>
    +
    26/* for 2.6 kernels */
    +
    27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
    +
    28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
    +
    29#endif
    +
    30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
    +
    31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
    +
    32#endif
    +
    33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
    +
    34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
    +
    35#endif
    +
    36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
    +
    37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
    +
    38#endif
    +
    39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
    +
    40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
    +
    41#endif
    +
    42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
    +
    43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
    +
    44#endif
    +
    45#endif
    +
    46/* linux < 3.5 */
    +
    47#ifndef PR_SET_NO_NEW_PRIVS
    +
    48#define PR_SET_NO_NEW_PRIVS 38
    +
    49#endif
    +
    50
    +
    51#include "fuse.h"
    +
    52
    +
    53static char *progname;
    +
    54
    +
    55static char *xstrdup(const char *s)
    +
    56{
    +
    57 char *t = strdup(s);
    +
    58 if (!t) {
    +
    59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    60 exit(1);
    +
    61 }
    +
    62 return t;
    +
    63}
    +
    64
    +
    65static void *xrealloc(void *oldptr, size_t size)
    +
    66{
    +
    67 void *ptr = realloc(oldptr, size);
    +
    68 if (!ptr) {
    +
    69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    70 exit(1);
    +
    71 }
    +
    72 return ptr;
    +
    73}
    +
    74
    +
    75static void add_arg(char **cmdp, const char *opt)
    +
    76{
    +
    77 size_t optlen = strlen(opt);
    +
    78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
    +
    79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
    +
    80 fprintf(stderr, "%s: argument too long\n", progname);
    +
    81 exit(1);
    +
    82 }
    +
    83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
    +
    84 char *s;
    +
    85 s = cmd + cmdlen;
    +
    86 if (*cmdp)
    +
    87 *s++ = ' ';
    +
    88
    +
    89 *s++ = '\'';
    +
    90 for (; *opt; opt++) {
    +
    91 if (*opt == '\'') {
    +
    92 *s++ = '\'';
    +
    93 *s++ = '\\';
    +
    94 *s++ = '\'';
    +
    95 *s++ = '\'';
    +
    96 } else
    +
    97 *s++ = *opt;
    +
    98 }
    +
    99 *s++ = '\'';
    +
    100 *s = '\0';
    +
    101 *cmdp = cmd;
    +
    102}
    +
    103
    +
    104static char *add_option(const char *opt, char *options)
    +
    105{
    +
    106 int oldlen = options ? strlen(options) : 0;
    +
    107
    +
    108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
    +
    109 if (!oldlen)
    +
    110 strcpy(options, opt);
    +
    111 else {
    +
    112 strcat(options, ",");
    +
    113 strcat(options, opt);
    +
    114 }
    +
    115 return options;
    +
    116}
    +
    117
    +
    118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
    +
    119 const char *options)
    +
    120{
    +
    121 int fuse_fd = -1;
    +
    122 int flags = -1;
    +
    123 int subtype_len = strlen(subtype) + 9;
    +
    124 char* options_copy = xrealloc(NULL, subtype_len);
    +
    125
    +
    126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
    +
    127 options_copy = add_option(options, options_copy);
    +
    128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
    +
    129 if (fuse_fd == -1) {
    +
    130 exit(1);
    +
    131 }
    +
    132
    +
    133 flags = fcntl(fuse_fd, F_GETFD);
    +
    134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) {
    +
    135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
    +
    136 progname, strerror(errno));
    +
    137 exit(1);
    +
    138 }
    +
    139
    +
    140 return fuse_fd;
    +
    141}
    +
    142
    +
    143#ifdef linux
    +
    144static uint64_t get_capabilities(void)
    +
    145{
    +
    146 /*
    +
    147 * This invokes the capset syscall directly to avoid the libcap
    +
    148 * dependency, which isn't really justified just for this.
    +
    149 */
    +
    150 struct __user_cap_header_struct header = {
    +
    151 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    152 .pid = 0,
    +
    153 };
    +
    154 struct __user_cap_data_struct data[2];
    +
    155 memset(data, 0, sizeof(data));
    +
    156 if (syscall(SYS_capget, &header, data) == -1) {
    +
    157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
    +
    158 progname, strerror(errno));
    +
    159 exit(1);
    +
    160 }
    +
    161
    +
    162 return data[0].effective | ((uint64_t) data[1].effective << 32);
    +
    163}
    +
    164
    +
    165static void set_capabilities(uint64_t caps)
    +
    166{
    +
    167 /*
    +
    168 * This invokes the capset syscall directly to avoid the libcap
    +
    169 * dependency, which isn't really justified just for this.
    +
    170 */
    +
    171 struct __user_cap_header_struct header = {
    +
    172 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    173 .pid = 0,
    +
    174 };
    +
    175 struct __user_cap_data_struct data[2];
    +
    176 memset(data, 0, sizeof(data));
    +
    177 data[0].effective = data[0].permitted = caps;
    +
    178 data[1].effective = data[1].permitted = caps >> 32;
    +
    179 if (syscall(SYS_capset, &header, data) == -1) {
    +
    180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
    +
    181 progname, strerror(errno));
    +
    182 exit(1);
    +
    183 }
    +
    184}
    +
    185
    +
    186static void drop_and_lock_capabilities(void)
    +
    187{
    +
    188 /* Set and lock securebits. */
    +
    189 if (prctl(PR_SET_SECUREBITS,
    +
    190 SECBIT_KEEP_CAPS_LOCKED |
    +
    191 SECBIT_NO_SETUID_FIXUP |
    +
    192 SECBIT_NO_SETUID_FIXUP_LOCKED |
    +
    193 SECBIT_NOROOT |
    +
    194 SECBIT_NOROOT_LOCKED) == -1) {
    +
    195 fprintf(stderr, "%s: Failed to set securebits %s\n",
    +
    196 progname, strerror(errno));
    +
    197 exit(1);
    +
    198 }
    +
    199
    +
    200 /* Clear the capability bounding set. */
    +
    201 int cap;
    +
    202 for (cap = 0; ; cap++) {
    +
    203 int cap_status = prctl(PR_CAPBSET_READ, cap);
    +
    204 if (cap_status == 0) {
    +
    205 continue;
    +
    206 }
    +
    207 if (cap_status == -1 && errno == EINVAL) {
    +
    208 break;
    +
    209 }
    +
    210
    +
    211 if (cap_status != 1) {
    +
    212 fprintf(stderr,
    +
    213 "%s: Failed to get capability %u: %s\n",
    +
    214 progname, cap, strerror(errno));
    +
    215 exit(1);
    +
    216 }
    +
    217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
    +
    218 fprintf(stderr,
    +
    219 "%s: Failed to drop capability %u: %s\n",
    +
    220 progname, cap, strerror(errno));
    +
    221 }
    +
    222 }
    +
    223
    +
    224 /* Drop capabilities. */
    +
    225 set_capabilities(0);
    +
    226
    +
    227 /* Prevent re-acquisition of privileges. */
    +
    228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    +
    229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
    +
    230 progname, strerror(errno));
    +
    231 exit(1);
    +
    232 }
    +
    233}
    +
    234#endif
    +
    235
    +
    236int main(int argc, char *argv[])
    +
    237{
    +
    238 char *type = NULL;
    +
    239 char *source;
    +
    240 char *dup_source = NULL;
    +
    241 const char *mountpoint;
    +
    242 char *basename;
    +
    243 char *options = NULL;
    +
    244 char *command = NULL;
    +
    245 char *setuid_name = NULL;
    +
    246 int i;
    +
    247 int dev = 1;
    +
    248 int suid = 1;
    +
    249 int pass_fuse_fd = 0;
    +
    250 int fuse_fd = 0;
    +
    251 int drop_privileges = 0;
    +
    252 char *dev_fd_mountpoint = NULL;
    +
    253
    +
    254 progname = argv[0];
    +
    255 basename = strrchr(argv[0], '/');
    +
    256 if (basename)
    +
    257 basename++;
    +
    258 else
    +
    259 basename = argv[0];
    +
    260
    +
    261 if (strncmp(basename, "mount.fuse.", 11) == 0)
    +
    262 type = basename + 11;
    +
    263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
    +
    264 type = basename + 14;
    +
    265
    +
    266 if (type && !type[0])
    +
    267 type = NULL;
    +
    268
    +
    269 if (argc < 3) {
    +
    270 fprintf(stderr,
    +
    271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
    +
    272 progname, type ? "source" : "type#[source]");
    +
    273 exit(1);
    +
    274 }
    +
    275
    +
    276 source = argv[1];
    +
    277 if (!source[0])
    +
    278 source = NULL;
    +
    279
    +
    280 mountpoint = argv[2];
    +
    281
    +
    282 for (i = 3; i < argc; i++) {
    +
    283 if (strcmp(argv[i], "-v") == 0) {
    +
    284 continue;
    +
    285 } else if (strcmp(argv[i], "-t") == 0) {
    +
    286 i++;
    +
    287
    +
    288 if (i == argc) {
    +
    289 fprintf(stderr,
    +
    290 "%s: missing argument to option '-t'\n",
    +
    291 progname);
    +
    292 exit(1);
    +
    293 }
    +
    294 type = argv[i];
    +
    295 if (strncmp(type, "fuse.", 5) == 0)
    +
    296 type += 5;
    +
    297 else if (strncmp(type, "fuseblk.", 8) == 0)
    +
    298 type += 8;
    +
    299
    +
    300 if (!type[0]) {
    +
    301 fprintf(stderr,
    +
    302 "%s: empty type given as argument to option '-t'\n",
    +
    303 progname);
    +
    304 exit(1);
    +
    305 }
    +
    306 } else if (strcmp(argv[i], "-o") == 0) {
    +
    307 char *opts;
    +
    308 char *opt;
    +
    309 i++;
    +
    310 if (i == argc)
    +
    311 break;
    +
    312
    +
    313 opts = xstrdup(argv[i]);
    +
    314 opt = strtok(opts, ",");
    +
    315 while (opt) {
    +
    316 int j;
    +
    317 int ignore = 0;
    +
    318 const char *ignore_opts[] = { "",
    +
    319 "user",
    +
    320 "nofail",
    +
    321 "nouser",
    +
    322 "users",
    +
    323 "auto",
    +
    324 "noauto",
    +
    325 "_netdev",
    +
    326 NULL};
    +
    327 if (strncmp(opt, "setuid=", 7) == 0) {
    +
    328 setuid_name = xstrdup(opt + 7);
    +
    329 ignore = 1;
    +
    330 } else if (strcmp(opt,
    +
    331 "drop_privileges") == 0) {
    +
    332 pass_fuse_fd = 1;
    +
    333 drop_privileges = 1;
    +
    334 ignore = 1;
    +
    335 }
    +
    336 for (j = 0; ignore_opts[j]; j++)
    +
    337 if (strcmp(opt, ignore_opts[j]) == 0)
    +
    338 ignore = 1;
    +
    339
    +
    340 if (!ignore) {
    +
    341 if (strcmp(opt, "nodev") == 0)
    +
    342 dev = 0;
    +
    343 else if (strcmp(opt, "nosuid") == 0)
    +
    344 suid = 0;
    +
    345
    +
    346 options = add_option(opt, options);
    +
    347 }
    +
    348 opt = strtok(NULL, ",");
    +
    349 }
    +
    350 free(opts);
    +
    351 }
    +
    352 }
    +
    353
    +
    354 if (drop_privileges) {
    +
    355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
    +
    356 CAP_TO_MASK(CAP_SYS_ADMIN);
    +
    357 if ((get_capabilities() & required_caps) != required_caps) {
    +
    358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
    +
    359 progname, progname);
    +
    360 exit(1);
    +
    361 }
    +
    362 }
    +
    363
    +
    364 if (dev)
    +
    365 options = add_option("dev", options);
    +
    366 if (suid)
    +
    367 options = add_option("suid", options);
    +
    368
    +
    369 if (!type) {
    +
    370 if (source) {
    +
    371 dup_source = xstrdup(source);
    +
    372 type = dup_source;
    +
    373 source = strchr(type, '#');
    +
    374 if (source)
    +
    375 *source++ = '\0';
    +
    376 if (!type[0]) {
    +
    377 fprintf(stderr, "%s: empty filesystem type\n",
    +
    378 progname);
    +
    379 exit(1);
    +
    380 }
    +
    381 } else {
    +
    382 fprintf(stderr, "%s: empty source\n", progname);
    +
    383 exit(1);
    +
    384 }
    +
    385 }
    +
    386
    +
    387 if (setuid_name && setuid_name[0]) {
    +
    388#ifdef linux
    +
    389 if (drop_privileges) {
    +
    390 /*
    +
    391 * Make securebits more permissive before calling
    +
    392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
    +
    393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
    +
    394 * have the side effect of dropping all capabilities,
    +
    395 * and we need to retain CAP_SETPCAP in order to drop
    +
    396 * all privileges before exec().
    +
    397 */
    +
    398 if (prctl(PR_SET_SECUREBITS,
    +
    399 SECBIT_KEEP_CAPS |
    +
    400 SECBIT_NO_SETUID_FIXUP) == -1) {
    +
    401 fprintf(stderr,
    +
    402 "%s: Failed to set securebits %s\n",
    +
    403 progname, strerror(errno));
    +
    404 exit(1);
    +
    405 }
    +
    406 }
    +
    407#endif
    +
    408
    +
    409 struct passwd *pwd = getpwnam(setuid_name);
    +
    410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
    +
    411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
    +
    412 progname, setuid_name, strerror(errno));
    +
    413 exit(1);
    +
    414 }
    +
    415 } else if (!getenv("HOME")) {
    +
    416 /* Hack to make filesystems work in the boot environment */
    +
    417 setenv("HOME", "/root", 0);
    +
    418 }
    +
    419
    +
    420 if (pass_fuse_fd) {
    +
    421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
    +
    422 dev_fd_mountpoint = xrealloc(NULL, 20);
    +
    423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
    +
    424 mountpoint = dev_fd_mountpoint;
    +
    425 }
    +
    426
    +
    427#ifdef linux
    +
    428 if (drop_privileges) {
    +
    429 drop_and_lock_capabilities();
    +
    430 }
    +
    431#endif
    +
    432 add_arg(&command, type);
    +
    433 if (source)
    +
    434 add_arg(&command, source);
    +
    435 add_arg(&command, mountpoint);
    +
    436 if (options) {
    +
    437 add_arg(&command, "-o");
    +
    438 add_arg(&command, options);
    +
    439 }
    +
    440
    +
    441 free(options);
    +
    442 free(dev_fd_mountpoint);
    +
    443 free(dup_source);
    +
    444 free(setuid_name);
    +
    445
    +
    446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
    +
    447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
    +
    448 strerror(errno));
    +
    449
    +
    450 if (pass_fuse_fd)
    +
    451 close(fuse_fd);
    +
    452 free(command);
    +
    453 return 1;
    +
    454}
    +
    int fuse_open_channel(const char *mountpoint, const char *options)
    Definition helper.c:475
    +
    + + + + diff --git a/doc/html/util_8c_source.html b/doc/html/util_8c_source.html new file mode 100644 index 0000000..8502090 --- /dev/null +++ b/doc/html/util_8c_source.html @@ -0,0 +1,87 @@ + + + + + + + +libfuse: lib/util.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    util.c
    +
    +
    +
    1#include <stdlib.h>
    +
    2#include <errno.h>
    +
    3
    +
    4#include "util.h"
    +
    5
    +
    6int libfuse_strtol(const char *str, long *res)
    +
    7{
    +
    8 char *endptr;
    +
    9 int base = 10;
    +
    10 long val;
    +
    11
    +
    12 errno = 0;
    +
    13
    +
    14 if (!str)
    +
    15 return -EINVAL;
    +
    16
    +
    17 val = strtol(str, &endptr, base);
    +
    18
    +
    19 if (errno)
    +
    20 return -errno;
    +
    21
    +
    22 if (endptr == str || *endptr != '\0')
    +
    23 return -EINVAL;
    +
    24
    +
    25 *res = val;
    +
    26 return 0;
    +
    27}
    +
    + + + + diff --git a/doc/html/util_8h_source.html b/doc/html/util_8h_source.html new file mode 100644 index 0000000..4acdad4 --- /dev/null +++ b/doc/html/util_8h_source.html @@ -0,0 +1,87 @@ + + + + + + + +libfuse: lib/util.h Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    util.h
    +
    +
    +
    1#ifndef FUSE_UTIL_H_
    +
    2#define FUSE_UTIL_H_
    +
    3
    +
    4#include <stdint.h>
    +
    5
    +
    6#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
    +
    7
    +
    8#define likely(x) __builtin_expect(!!(x), 1)
    +
    9#define unlikely(x) __builtin_expect(!!(x), 0)
    +
    10
    +
    11int libfuse_strtol(const char *str, long *res);
    +
    12
    +
    16static inline uint32_t fuse_lower_32_bits(uint64_t nr)
    +
    17{
    +
    18 return (uint32_t)(nr & 0xffffffff);
    +
    19}
    +
    20
    +
    24static inline uint64_t fuse_higher_32_bits(uint64_t nr)
    +
    25{
    +
    26 return nr & ~0xffffffffULL;
    +
    27}
    +
    28
    +
    29#ifndef FUSE_VAR_UNUSED
    +
    30#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var)
    +
    31#endif
    +
    32
    +
    33#endif
    +
    + + + + diff --git a/doc/html/wrong__command_8c_source.html b/doc/html/wrong__command_8c_source.html new file mode 100644 index 0000000..2ce3098 --- /dev/null +++ b/doc/html/wrong__command_8c_source.html @@ -0,0 +1,76 @@ + + + + + + + +libfuse: test/wrong_command.c Source File + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    wrong_command.c
    +
    +
    +
    1#include <stdio.h>
    +
    2
    +
    3int main(void) {
    +
    4#ifdef MESON_IS_SUBPROJECT
    +
    5 fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n"
    +
    6 "If you wish to run them try:\n"
    +
    7 "'cd <srcdir>/subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead");
    +
    8 return 77; /* report as a skipped test */
    +
    9#else
    +
    10 fprintf(stderr, "\x1B[31m\e[1m"
    +
    11 "This is not the command you are looking for.\n"
    +
    12 "You probably want to run 'python3 -m pytest test/' instead"
    +
    13 "\e[0m\n");
    +
    14 return 1;
    +
    15#endif
    +
    16}
    +
    + + + + diff --git a/doc/kernel.txt b/doc/kernel.txt new file mode 100644 index 0000000..6e7c7a1 --- /dev/null +++ b/doc/kernel.txt @@ -0,0 +1,380 @@ +Definitions +~~~~~~~~~~~ + +Userspace filesystem: + + A filesystem in which data and metadata are provided by an ordinary + userspace process. The filesystem can be accessed normally through + the kernel interface. + +Filesystem daemon: + + The process(es) providing the data and metadata of the filesystem. + +Non-privileged mount (or user mount): + + A userspace filesystem mounted by a non-privileged (non-root) user. + The filesystem daemon is running with the privileges of the mounting + user. NOTE: this is not the same as mounts allowed with the "user" + option in /etc/fstab, which is not discussed here. + +Filesystem connection: + + A connection between the filesystem daemon and the kernel. The + connection exists until either the daemon dies, or the filesystem is + umounted. Note that detaching (or lazy umounting) the filesystem + does _not_ break the connection, in this case it will exist until + the last reference to the filesystem is released. + +Mount owner: + + The user who does the mounting. + +User: + + The user who is performing filesystem operations. + +What is FUSE? +~~~~~~~~~~~~~ + +FUSE is a userspace filesystem framework. It consists of a kernel +module (fuse.ko), a userspace library (libfuse.*) and a mount utility +(fusermount3). + +One of the most important features of FUSE is allowing secure, +non-privileged mounts. This opens up new possibilities for the use of +filesystems. A good example is sshfs: a secure network filesystem +using the sftp protocol. + +The userspace library and utilities are available from the FUSE +homepage: + + https://github.com/libfuse/libfuse/ + +Filesystem type +~~~~~~~~~~~~~~~ + +The filesystem type given to mount(2) can be one of the following: + +'fuse' + + This is the usual way to mount a FUSE filesystem. The first + argument of the mount system call may contain an arbitrary string, + which is not interpreted by the kernel. + +'fuseblk' + + The filesystem is block device based. The first argument of the + mount system call is interpreted as the name of the device. + +Mount options +~~~~~~~~~~~~~ + +See mount.fuse3(8). + +Control filesystem +~~~~~~~~~~~~~~~~~~ + +There's a control filesystem for FUSE, which can be mounted by: + + mount -t fusectl none /sys/fs/fuse/connections + +Mounting it under the '/sys/fs/fuse/connections' directory makes it +backwards compatible with versions before 2.6.0. + +Under the fuse control filesystem each connection has a directory +named by a unique number. + +For each connection the following files exist within this directory: + + 'waiting' + + The number of requests which are waiting to be transferred to + userspace or being processed by the filesystem daemon. If there is + no filesystem activity and 'waiting' is non-zero, then the + filesystem is hung or deadlocked. + + 'abort' + + Writing anything into this file will abort the filesystem + connection. This means that all waiting requests will be aborted an + error returned for all aborted and new requests. + +Only the owner of the mount may read or write these files. + +Interrupting filesystem operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a process issuing a FUSE filesystem request is interrupted, the +following will happen: + + 1) If the request is not yet sent to userspace AND the signal is + fatal (SIGKILL or unhandled fatal signal), then the request is + dequeued and returns immediately. + + 2) If the request is not yet sent to userspace AND the signal is not + fatal, then an 'interrupted' flag is set for the request. When + the request has been successfully transferred to userspace and + this flag is set, an INTERRUPT request is queued. + + 3) If the request is already sent to userspace, then an INTERRUPT + request is queued. + +INTERRUPT requests take precedence over other requests, so the +userspace filesystem will receive queued INTERRUPTs before any others. + +The userspace filesystem may ignore the INTERRUPT requests entirely, +or may honor them by sending a reply to the _original_ request, with +the error set to EINTR. + +It is also possible that there's a race between processing the +original request and it's INTERRUPT request. There are two possibilities: + + 1) The INTERRUPT request is processed before the original request is + processed + + 2) The INTERRUPT request is processed after the original request has + been answered + +If the filesystem cannot find the original request, it should wait for +some timeout and/or a number of new requests to arrive, after which it +should reply to the INTERRUPT request with an EAGAIN error. In case +1) the INTERRUPT request will be requeued. In case 2) the INTERRUPT +reply will be ignored. + +Aborting a filesystem connection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is possible to get into certain situations where the filesystem is +not responding. Reasons for this may be: + + a) Broken userspace filesystem implementation + + b) Network connection down + + c) Accidental deadlock + + d) Malicious deadlock + +(For more on c) and d) see later sections) + +In either of these cases it may be useful to abort the connection to +the filesystem. There are several ways to do this: + + - Kill the filesystem daemon. Works in case of a) and b) + + - Kill the filesystem daemon and all users of the filesystem. Works + in all cases except some malicious deadlocks + + - Use forced umount (umount -f). Works in all cases but only if + filesystem is still attached (it hasn't been lazy unmounted) + + - Abort filesystem through the FUSE control filesystem. Most + powerful method, always works. + +How do non-privileged mounts work? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the mount() system call is a privileged operation, a helper +program (fusermount3) is needed, which is installed setuid root. + +The implication of providing non-privileged mounts is that the mount +owner must not be able to use this capability to compromise the +system. Obvious requirements arising from this are: + + A) mount owner should not be able to get elevated privileges with the + help of the mounted filesystem + + B) mount owner should not get illegitimate access to information from + other users' and the super user's processes + + C) mount owner should not be able to induce undesired behavior in + other users' or the super user's processes + +How are requirements fulfilled? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + A) The mount owner could gain elevated privileges by either: + + 1) creating a filesystem containing a device file, then opening + this device + + 2) creating a filesystem containing a suid or sgid application, + then executing this application + + The solution is not to allow opening device files and ignore + setuid and setgid bits when executing programs. To ensure this + fusermount3 always adds "nosuid" and "nodev" to the mount options + for non-privileged mounts. + + B) If another user is accessing files or directories in the + filesystem, the filesystem daemon serving requests can record the + exact sequence and timing of operations performed. This + information is otherwise inaccessible to the mount owner, so this + counts as an information leak. + + The solution to this problem will be presented in point 2) of C). + + C) There are several ways in which the mount owner can induce + undesired behavior in other users' processes, such as: + + 1) mounting a filesystem over a file or directory which the mount + owner could otherwise not be able to modify (or could only + make limited modifications). + + This is solved in fusermount3, by checking the access + permissions on the mountpoint and only allowing the mount if + the mount owner can do unlimited modification (has write + access to the mountpoint, and mountpoint is not a "sticky" + directory) + + 2) Even if 1) is solved the mount owner can change the behavior + of other users' processes. + + i) It can slow down or indefinitely delay the execution of a + filesystem operation creating a DoS against the user or the + whole system. For example a suid application locking a + system file, and then accessing a file on the mount owner's + filesystem could be stopped, and thus causing the system + file to be locked forever. + + ii) It can present files or directories of unlimited length, or + directory structures of unlimited depth, possibly causing a + system process to eat up diskspace, memory or other + resources, again causing DoS. + + The solution to this as well as B) is not to allow processes + to access the filesystem, which could otherwise not be + monitored or manipulated by the mount owner. Since if the + mount owner can ptrace a process, it can do all of the above + without using a FUSE mount, the same criteria as used in + ptrace can be used to check if a process is allowed to access + the filesystem or not. + + Note that the ptrace check is not strictly necessary to + prevent B/2/i, it is enough to check if mount owner has enough + privilege to send signal to the process accessing the + filesystem, since SIGSTOP can be used to get a similar effect. + +I think these limitations are unacceptable? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a sysadmin trusts the users enough, or can ensure through other +measures, that system processes will never enter non-privileged +mounts, it can relax the last limitation with a "user_allow_other" +config option. If this config option is set, the mounting user can +add the "allow_other" mount option which disables the check for other +users' processes. + +Kernel - userspace interface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following diagram shows how a filesystem operation (in this +example unlink) is performed in FUSE. + +NOTE: everything in this description is greatly simplified + + | "rm /mnt/fuse/file" | FUSE filesystem daemon + | | + | | >sys_read() + | | >fuse_dev_read() + | | >request_wait() + | | [sleep on fc->waitq] + | | + | >sys_unlink() | + | >fuse_unlink() | + | [get request from | + | fc->unused_list] | + | >request_send() | + | [queue req on fc->pending] | + | [wake up fc->waitq] | [woken up] + | >request_wait_answer() | + | [sleep on req->waitq] | + | | pending] + | | [copy req to read buffer] + | | [add req to fc->processing] + | | sys_write() + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | [woken up] | [wake up req->waitq] + | | unused_list] | + | sys_unlink("/mnt/fuse/file") | + | [acquire inode semaphore | + | for "file"] | + | >fuse_unlink() | + | [sleep on req->waitq] | + | | sys_unlink("/mnt/fuse/file") + | | [acquire inode semaphore + | | for "file"] + | | *DEADLOCK* + +The solution for this is to allow the filesystem to be aborted. + +Scenario 2 - Tricky deadlock +---------------------------- + +This one needs a carefully crafted filesystem. It's a variation on +the above, only the call back to the filesystem is not explicit, +but is caused by a pagefault. + + | Kamikaze filesystem thread 1 | Kamikaze filesystem thread 2 + | | + | [fd = open("/mnt/fuse/file")] | [request served normally] + | [mmap fd to 'addr'] | + | [close fd] | [FLUSH triggers 'magic' flag] + | [read a byte from addr] | + | >do_page_fault() | + | [find or create page] | + | [lock page] | + | >fuse_readpage() | + | [queue READ request] | + | [sleep on req->waitq] | + | | [read request to buffer] + | | [create reply header before addr] + | | >sys_write(addr - headerlength) + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | | >do_page_fault() + | | [find or create page] + | | [lock page] + | | * DEADLOCK * + +Solution is basically the same as above. + +An additional problem is that while the write buffer is being copied +to the request, the request must not be interrupted/aborted. This is +because the destination address of the copy may not be valid after the +request has returned. + +This is solved with doing the copy atomically, and allowing abort +while the page(s) belonging to the write buffer are faulted with +get_user_pages(). The 'req->locked' flag indicates when the copy is +taking place, and abort is delayed until this flag is unset. diff --git a/doc/libfuse-operations.txt b/doc/libfuse-operations.txt new file mode 100644 index 0000000..a56f89b --- /dev/null +++ b/doc/libfuse-operations.txt @@ -0,0 +1,418 @@ +List of libfuse operations with their in/out arguments, created with +help of chatgpt. As of kernel 6.9 (protocol 7.40). The list +was only partly human verified - use with care. + +1. FUSE_LOOKUP (1) + - in_args[0]: Variable (up to PATH_MAX for the file name) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +2. FUSE_FORGET (2) + - in_args[0]: Size of fuse_forget_in (typically 8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +3. FUSE_GETATTR (3) + - in_args[0]: Size of fuse_getattr_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_attr_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +4. FUSE_SETATTR (4) + - in_args[0]: Size of fuse_setattr_in (typically 32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_attr_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +5. FUSE_READLINK (5) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (size of the link target path) + - out_args[1]: Not used + - out_args[2]: Not used + +6. FUSE_SYMLINK (6) + - in_args[0]: Variable (new link file name, up to PATH_MAX) + - in_args[1]: Variable (target path for the symlink, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +7. Unused (7) + +8. FUSE_MKNOD (8) + - in_args[0]: Size of fuse_mknod_in (24 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +9. FUSE_MKDIR (9) + - in_args[0]: Size of fuse_mkdir_in (16 bytes) + - in_args[1]: Variable (directory name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +10. FUSE_UNLINK (10) + - in_args[0]: Variable (file name, up to PATH_MAX) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +11. FUSE_RMDIR (11) + - in_args[0]: Variable (directory name, up to PATH_MAX) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +12. FUSE_RENAME (12) + - in_args[0]: Size of fuse_rename_in (24 bytes) + - in_args[1]: Variable (old path, up to PATH_MAX) + - in_args[2]: Variable (new path, up to PATH_MAX) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +13. FUSE_LINK (13) + - in_args[0]: Size of fuse_link_in (16 bytes) + - in_args[1]: Variable (new link path, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +14. FUSE_OPEN (14) + - in_args[0]: Size of fuse_open_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_open_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +15. FUSE_READ (15) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (data read from the file) + - out_args[1]: Not used + - out_args[2]: Not used + +16. FUSE_WRITE (16) + - in_args[0]: Size of fuse_write_in (32 bytes) + - in_args[1]: Variable (data to write) + - in_args[2]: Not used + - out_args[0]: Size of fuse_write_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +17. FUSE_STATFS (17) + - in_args[0]: Size of fuse_statfs_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_statfs_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +18. FUSE_RELEASE (18) + - in_args[0]: Size of fuse_release_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +19. Unused (19) + +20. FUSE_FSYNC (20) + - in_args[0]: Size of fuse_fsync_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +21. FUSE_SETXATTR (21) + - in_args[0]: Size of fuse_setxattr_in (24 bytes) + - in_args[1]: Variable (name of attribute, up to 255 bytes) + - in_args[2]: Variable (value of attribute) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + - When FUSE_SETXATTR_EXT is not set: Size of fuse_setxattr_in is 16 bytes (FUSE_COMPAT_SETXATTR_IN_SIZE). + +22. FUSE_GETXATTR (22) + - in_args[0]: Size of fuse_getxattr_in (24 bytes) + - in_args[1]: Variable (name of attribute, up to 255 bytes) + - in_args[2]: Not used + - out_args[0]: Variable (value of the attribute) + - out_args[1]: Not used + - out_args[2]: Not used + +23. FUSE_LISTXATTR (23) + - in_args[0]: Size of fuse_getxattr_in (24 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (list of attribute names, each up to 255 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +24. FUSE_REMOVEXATTR (24) + - in_args[0]: Variable (name of attribute, up to 255 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +25. FUSE_FLUSH (25) + - in_args[0]: Size of fuse_flush_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +26. FUSE_INIT (26) + - in_args[0]: Size of fuse_init_in (64 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_init_out (typically 80 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + - Flags: + - FUSE_SECURITY_CTX + - FUSE_CREATE_SUPP_GROUP + - FUSE_SETXATTR_EXT + - FUSE_COMPAT_SETXATTR_IN_SIZE + - Additional flags may include FUSE_ASYNC_READ, FUSE_POSIX_LOCKS, FUSE_FILE_OPS, etc. + +27. FUSE_OPENDIR (27) + - in_args[0]: Size of fuse_open_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_open_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +28. FUSE_READDIR (28) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (directory entries) + - out_args[1]: Not used + - out_args[2]: Not used + +29. FUSE_RELEASEDIR (29) + - in_args[0]: Size of fuse_release_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +30. FUSE_FSYNCDIR (30) + - in_args[0]: Size of fuse_fsync_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +31. FUSE_GETLK (31) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_lk_out (typically 32 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +32. FUSE_SETLK (32) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +33. FUSE_SETLKW (33) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +34. FUSE_ACCESS (34) + - in_args[0]: Size of fuse_access_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +35. FUSE_CREATE (35) + - in_args[0]: Size of fuse_create_in (16 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Size of fuse_open_out (24 bytes) + - out_args[2]: Not used + - When FUSE_SECURITY_CTX is set (fc->init_security): Additional security context included in in_args and/or out_args. + - When FUSE_CREATE_SUPP_GROUP is set (fc->create_supp_group): Additional supplementary group information included in in_args. + +36. FUSE_INTERRUPT (36) + - in_args[0]: Size of fuse_interrupt_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +37. FUSE_BMAP (37) + - in_args[0]: Size of fuse_bmap_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_bmap_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +38. FUSE_DESTROY (38) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +39. FUSE_IOCTL (39) + - in_args[0]: Size of fuse_ioctl_in (64 bytes) + - in_args[1]: Variable (ioctl command data) + - in_args[2]: Not used + - out_args[0]: Size of fuse_ioctl_out (32 bytes) + - out_args[1]: Variable (data returned by the ioctl command) + - out_args[2]: Not used + +40. FUSE_POLL (40) + - in_args[0]: Size of fuse_poll_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_poll_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +41. FUSE_NOTIFY_REPLY (41) + - in_args[0]: Variable (reply data) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +42. FUSE_BATCH_FORGET (42) + - in_args[0]: Variable (size depends on the number of forget requests) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +43. FUSE_FALLOCATE (43) + - in_args[0]: Size of fuse_fallocate_in (24 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +44. FUSE_READDIRPLUS (44) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (directory entries) + - out_args[1]: Not used + - out_args[2]: Not used + +45. FUSE_RENAME2 (45) + - in_args[0]: Size of fuse_rename2_in (32 bytes) + - in_args[1]: Variable (old path, up to PATH_MAX) + - in_args[2]: Variable (new path, up to PATH_MAX) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +46. FUSE_LSEEK (46) + - in_args[0]: Size of fuse_lseek_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_lseek_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +47. FUSE_COPY_FILE_RANGE (47) + - in_args[0]: Size of fuse_copy_file_range_in (48 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_write_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +48. FUSE_SETUPMAPPING (48) + - in_args[0]: Size of fuse_setupmapping_in (32 bytes) + - in_args[1]: Variable (data to map) + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +49. FUSE_REMOVEMAPPING (49) + - in_args[0]: Variable (size depends on the number of mappings to remove) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +50. FUSE_SYNCFS (50) + - in_args[0]: Size of fuse_syncfs_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +51. FUSE_TMPFILE (51) + - in_args[0]: Size of fuse_create_in (16 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Size of fuse_open_out (24 bytes) + - out_args[2]: Not used + - When FUSE_SECURITY_CTX is set (fc->init_security): Additional security context included in in_args and/or out_args. + - When FUSE_CREATE_SUPP_GROUP is set (fc->create_supp_group): Additional supplementary group information included in in_args. + +52. FUSE_STATX (52) + - in_args[0]: Size of fuse_statx_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_statx_out (typically 256 bytes) + - out_args[1]: Not used + - out_args[2]: Not used diff --git a/doc/mainpage.dox b/doc/mainpage.dox new file mode 100644 index 0000000..36ba3bc --- /dev/null +++ b/doc/mainpage.dox @@ -0,0 +1,54 @@ +/*! +\mainpage libfuse API documentation + +FUSE (Filesystem in Userspace) is an interface for userspace programs +to export a filesystem to the Linux kernel. The FUSE project consists +of two components: the *fuse* kernel module (maintained in the regular +kernel repositories) and the *libfuse* userspace library. libfuse +provides the reference implementation for communicating with the FUSE +kernel module. + +A FUSE file system is typically implemented as a standalone +application that links with libfuse. libfuse provides functions to +mount the file system, unmount it, read requests from the kernel, and +send responses back. + + +## Getting started ## + +libfuse offers two APIs: a "high-level", synchronous API, and a +"low-level" asynchronous API. In both cases, incoming requests from +the kernel are passed to the main program using callbacks. When using +the high-level API, the callbacks may work with file names and paths +instead of inodes, and processing of a request finishes when the +callback function returns. When using the low-level API, the callbacks +must work with inodes and responses must be sent explicitly using a +separate set of API functions. + +The high-level API that is primarily specified in fuse.h. The +low-level API that is primarily documented in fuse_lowlevel.h. + +## Examples ## + +FUSE comes with several examples in the examples directory. A good starting point are +hello.c (for the high-level API) and hello_ll.c (for the low-level +API). + +## FUSE internals ## + +The authoritative source of information about libfuse internals +(including the protocol used for communication with the FUSE kernel +module) is the source code. + +However, some people have kindly documented different aspects of FUSE +in a more beginner friendly way. While this information is +increasingly out of date, it still provides a good overview: + +- Bharat Vangoor et al have included an overview of the FUSE internals + in a paper evaluating FUSE performance. + +- Some documentation of the kernel-userspace protocol is available on + the libfuse wiki. + +*/ diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..db3e0b2 --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,4 @@ +if not platform.endswith('bsd') and platform != 'dragonfly' + install_man('fusermount3.1', 'mount.fuse3.8') +endif + diff --git a/doc/mount.fuse3.8 b/doc/mount.fuse3.8 new file mode 100644 index 0000000..32862fc --- /dev/null +++ b/doc/mount.fuse3.8 @@ -0,0 +1,273 @@ +.TH fuse "8" +.SH NAME +fuse \- configuration and mount options for FUSE file systems +.SH DESCRIPTION +FUSE (Filesystem in Userspace) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. FUSE also aims to provide a secure method for non privileged users to create and mount their own filesystem implementations. +.SH DEFINITIONS +.TP +\fBFUSE\fP +The in-kernel filesystem that forwards requests to a user-space +process. +.TP +\fBfilesystem\fP +The user-space process that responds to requests received from the +kernel. +.TP +\fBlibfuse\fP +The shared library that most (user-space) filesystems use to +communicate with FUSE (the kernel filesystem). libfuse also provides +the \fBfusermount3\fP (or \fBfusermount\fP if you have older version of +libfuse) helper to allow non-privileged users to mount filesystems. +.TP +\fBfilesystem owner\fP +The user that starts the filesystem and instructs the kernel to +associate it with a particular mountpoint. The latter is typically done +by the filesystem itself on start-up. When using libfuse, this is done +by calling the \fBfusermount3\fP utility. +.TP +\fBclient\fP +Any process that interacts with the mountpoint. +.SH CONFIGURATION +Some options regarding mount policy can be set in the file \fI/etc/fuse.conf\fP. Currently these options are: +.TP +\fBmount_max = NNN\fP +Set the maximum number of FUSE mounts allowed to non-root users. The default is 1000. +.TP +\fBuser_allow_other\fP +Allow non-root users to specify the \fBallow_other\fP or +\fBallow_root\fP mount options (see below). +.TP +These limits are enforced by the \fBfusermount3\fP helper, so they can be avoided by filesystems that run as root. +.SH OPTIONS +Most of the generic mount options described in \fBmount\fP are +supported (\fBro\fP, \fBrw\fP, \fBsuid\fP, \fBnosuid\fP, \fBdev\fP, +\fBnodev\fP, \fBexec\fP, \fBnoexec\fP, \fBatime\fP, \fBnoatime\fP, +\fBsync\fP, \fBasync\fP, \fBdirsync\fP). Filesystems are mounted with +\fBnodev,nosuid\fP by default, which can only be overridden by a +privileged user. +.SS "General mount options:" +These are FUSE specific mount options that can be specified for all filesystems: +.TP +\fBdefault_permissions\fP +This option instructs the kernel to perform its own permission check +instead of deferring all permission checking to the +filesystem. The check by the kernel is done in addition to any +permission checks by the filesystem, and both have to succeed for an +operation to be allowed. The kernel performs a standard UNIX permission +check (based on mode bits and ownership of the directory entry, and +uid/gid of the client). + +This mount option is activated implicitly if the filesystem enables +ACL support during the initial feature negotiation when opening the +device fd. In this case, the kernel performs both ACL and standard +unix permission checking. + +Filesystems that do not implement any permission checking should +generally add this option internally. +.TP +\fBallow_other\fP +This option overrides the security measure +restricting file access to the filesystem owner, so that all users +(including root) can access the files. +.TP +\fBrootmode=M\fP +Specifies the file mode of the filesystem's root (in octal +representation). +.TP +\fBblkdev\fP +Mount a filesystem backed by a block device. This is a privileged +option. The device must be specified with the \fBfsname=NAME\fP +option. +.TP +\fBblksize=N\fP +Set the block size for the filesystem. This option is only valid +for 'fuseblk' type mounts. The default is 512. + +In most cases, this option should not be specified by +the filesystem owner but set internally by the filesystem. +.TP +\fBmax_read=N\fP +With this option the maximum size of read operations can be set. The +default is infinite, but typically the kernel enforces its own limit +in addition to this one. A value of zero corresponds to no limit. + +This option should not be specified by the filesystem owner. The +correct (or optimum) value depends on the filesystem implementation +and should thus be set by the filesystem internally. + +This mount option is deprecated in favor of direct negotiation over +the device fd (as done for e.g. the maximum size of write +operations). For the time being, libfuse-using filesystems that want +to limit the read size must therefore use this mount option \fIand\fP +set the same value again in the init() handler. +.TP +\fBfd=N\fP +The file descriptor to use for communication between the userspace +filesystem and the kernel. The file descriptor must have been +obtained by opening the FUSE device (/dev/fuse). + +This option should not be specified by the filesystem owner. It is set +by libfuse (or, if libfuse is not used, must be set by the filesystem +itself). +.TP +\fBuser_id=N\fP +\fBgroup_id=N\fP +Specifies the numeric uid/gid of the mount owner. + +This option should not be specified by the filesystem owner. It is set +by libfuse (or, if libfuse is not used, must be set by the filesystem +itself). +.TP +\fBfsname=NAME\fP +Sets the filesystem source (first field in \fI/etc/mtab\fP). The +default is the name of the filesystem process. +.TP +\fBsubtype=TYPE\fP +Sets the filesystem type (third field in \fI/etc/mtab\fP). The default +is the name of the filesystem process. If the kernel supports it, \fI/etc/mtab\fP and \fI/proc/mounts\fP will show the filesystem type as \fBfuse.TYPE\fP + +If the kernel doesn't support subtypes, the source field will be +\fBTYPE#NAME\fP, or if \fBfsname\fP option is not specified, just +\fBTYPE\fP. + +.SS "libfuse-specific mount options:" +These following options are not actually passed to the kernel but +interpreted by libfuse. They can be specified for all filesystems +that use libfuse: +.TP +\fBallow_root\fP +This option is similar to \fBallow_other\fP but file access is limited +to the filesystem owner and root. This option and \fBallow_other\fP are mutually exclusive. +.TP +\fBauto_unmount\fP +This option enables automatic release of the mountpoint if filesystem +terminates for any reason. Normally the filesystem is +responsible for releasing the mountpoint, which means that the +mountpoint becomes inaccessible if the filesystem terminates +without first unmounting. + +This option is dangerous and should only be used after careful consideration of the +risks. + +Automatically unmounting the filesystem means that if the filesystem crashes the +mountpoint may suddenly appear empty, which may have unintended consequences. For example, +a running backup and mirroring program may conclude that all the data in the filesystem +has been deleted and proceed to propagate this deletion to the backup / remote system. If +the mountpoint instead becomes inaccessible (the default), most programs will behave +correctly (report an error). + +This feature may also accidentally unmount the wrong filesystem due to race +conditions. For example, if another filesystem was mounted underneath the same mountpoint, +or if a new filesystem is mounted after the FUSE process has crashed, it may accidentally +get unmounted. + +At the moment, this option implies that the filesystem will also be +mounted with \fBnodev\fP and \fBnosuid\fP (even when mounted by +root). This restriction may be lifted in the future. + +.SS "High-level mount options:" +These following options are not actually passed to the kernel but +interpreted by libfuse. They can only be specified for filesystems +that use the high-level libfuse API: +.TP +\fBkernel_cache\fP +This option disables flushing the cache of the file contents on every \fBopen\fP(2). This should only be enabled on filesystems, where the file data is never changed externally (not through the mounted FUSE filesystem). Thus it is not suitable for network filesystems and other "intermediate" filesystems. + +\fBNOTE\fP: if this option is not specified (and neither \fBdirect_io\fP) data is still cached after the \fBopen\fP(2), so a \fBread\fP(2) system call will not always initiate a read operation. +.TP +\fBauto_cache\fP +This option is an alternative to +\fBkernel_cache\fP. Instead of unconditionally keeping cached data, the +cached data is invalidated on \fBopen\fP(2) if the modification +time or the size of the file has changed since it was last opened. +.TP +\fBumask=M fmask=M dmask=M\fP +Override the permission bits set by the filesystem in \fIst_mode\fP. The resulting permission bits are the ones missing from the mask value, which is given in octal representation. \fBfmask\fP and \fBdmask\fP (respectively) may be used to control the permission bits of files and directories separately. umask is overridden by the individual fmask and dmask options. +.TP +\fBuid=N\fP +Override the \fIst_uid\fP field set by the filesystem (N is numeric). +.TP +\fBgid=N\fP +Override the \fIst_gid\fP field set by the filesystem (N is numeric). +.TP +\fBentry_timeout=T\fP +The timeout in seconds for which name lookups will be cached. The default is 1.0 second. For all the timeout options, it is possible to give fractions of a second as well (e.g. \fBentry_timeout=2.8\fP) +.TP +\fBnegative_timeout=T\fP +The timeout in seconds for which a negative lookup will be cached. This means, that if file did not exist (lookup returned \fBENOENT\fP), the lookup will only be redone after the timeout, and the file/directory will be assumed to not exist until then. The default is 0.0 second, meaning that caching negative lookups are disabled. +.TP +\fBattr_timeout=T\fP +The timeout in seconds for which file/directory attributes are cached. The default is 1.0 second. +.TP +\fBac_attr_timeout=T\fP +The timeout in seconds for which file attributes are cached for the purpose of checking if \fBauto_cache\fP should flush the file data on open. The default is the value of \fBattr_timeout\fP +.TP +\fBnoforget\fP +.TP +\fBremember=T\fP +Normally, libfuse assigns inodes to paths only for as long as the kernel +is aware of them. With this option inodes are instead assigned +for at least \fBT\fP seconds (or, in the case of \fBnoforget\fP, +the life-time of the filesystem). This will require more +memory, but may be necessary when using applications that make use of +inode numbers. +.TP +\fBmodules=M1[:M2...]\fP +Add modules to the filesystem stack. Modules are pushed in the order they are specified, with the original filesystem being on the bottom of the stack. + +.SS "\fBmount.fuse3\fP options:" +These options are interpreted by \fBmount.fuse3\fP and are thus only available when mounting a file system via \fBmount.fuse3\fP (such as when mounting via the generic \fBmount\fP(1) command or \fI/etc/fstab\fP). Supported options are: +.TP +\fBsetuid=USER\fP +Switch to \fBUSER\fP and its primary group before launching the FUSE file system process. mount.fuse3 must be run as root or with \fBCAP_SETUID\fP and \fBCAP_SETGID\fP for this to work. +.TP +\fBdrop_privileges\fP +Perform setup of the FUSE file descriptor and mounting the file system before launching the FUSE file system process. \fBmount.fuse3\fP requires privilege to do so, i.e. must be run as root or at least with \fBCAP_SYS_ADMIN\fP and \fBCAP_SETPCAP\fP. It will launch the file system process fully unprivileged, i.e. without \fBcapabilities\fP(7) and \fBprctl\fP(2) flags set up such that privileges can't be reacquired (e.g. via setuid or fscaps binaries). This reduces risk in the event of the FUSE file system process getting compromised by malicious file system data. + +.SH FUSE MODULES (STACKING) +Modules are filesystem stacking support to high level API. Filesystem modules can be built into libfuse or loaded from shared object +.SS "iconv" +Perform file name character set conversion. Options are: +.TP +\fBfrom_code=CHARSET\fP +Character set to convert from (see \fBiconv -l\fP for a list of possible values). Default is \fBUTF-8\fP. +.TP +\fBto_code=CHARSET\fP +Character set to convert to. Default is determined by the current locale. +.SS "subdir" +Prepend a given directory to each path. Options are: +.TP +\fBsubdir=DIR\fP +Directory to prepend to all paths. This option is \fImandatory\fP. +.TP +\fBrellinks\fP +Transform absolute symlinks into relative +.TP +\fBnorellinks\fP +Do not transform absolute symlinks into relative. This is the default. +.SH SECURITY +The fusermount3 program is installed set-user-gid to fuse. This is done to allow users from fuse group to mount +their own filesystem implementations. +There must however be some limitations, in order to prevent Bad User from +doing nasty things. Currently those limitations are: +.IP 1. +The user can only mount on a mountpoint, for which it has write permission +.IP 2. +The mountpoint is not a sticky directory which isn't owned by the user (like \fI/tmp\fP usually is) +.IP 3. +No other user (including root) can access the contents of the mounted filesystem. +.SH NOTE +FUSE filesystems are unmounted using the \fBfusermount3\fP(1) command (\fBfusermount3 -u mountpoint\fP). +.SH "AUTHORS" +.LP +FUSE is currently maintained by Nikolaus Rath +.LP +The original author of FUSE is Miklos Szeredi . +.LP +This man page was originally written by Bastien Roucaries for the +Debian GNU/Linux distribution. +.SH SEE ALSO +.BR fusermount3 (1) +.BR fusermount (1) +.BR mount (8) +.BR fuse (4) diff --git a/example/cuse.c b/example/cuse.c new file mode 100644 index 0000000..6b33302 --- /dev/null +++ b/example/cuse.c @@ -0,0 +1,335 @@ +/* + CUSE example: Character device in Userspace + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. + +*/ + +/** @file + * + * This example demonstrates how to implement a character device in + * userspace ("CUSE"). This is only allowed for root. The character + * device should appear in /dev under the specified name. It can be + * tested with the cuse_client.c program. + * + * Mount the file system with: + * + * cuse -f --name=mydevice + * + * You should now have a new /dev/mydevice character device. To "unmount" it, + * kill the "cuse" process. + * + * To compile this example, run + * + * gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse + * + * ## Source code ## + * \include cuse.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +static void *cusexmp_buf; +static size_t cusexmp_size; + +static const char *usage = +"usage: cusexmp [options]\n" +"\n" +"options:\n" +" --help|-h print this help message\n" +" --maj=MAJ|-M MAJ device major number\n" +" --min=MIN|-m MIN device minor number\n" +" --name=NAME|-n NAME device name (mandatory)\n" +" -d -o debug enable debug output (implies -f)\n" +" -f foreground operation\n" +" -s disable multi-threaded operation\n" +"\n"; + +static int cusexmp_resize(size_t new_size) +{ + void *new_buf; + + if (new_size == cusexmp_size) + return 0; + + new_buf = realloc(cusexmp_buf, new_size); + if (!new_buf && new_size) + return -ENOMEM; + + if (new_size > cusexmp_size) + memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size); + + cusexmp_buf = new_buf; + cusexmp_size = new_size; + + return 0; +} + +static int cusexmp_expand(size_t new_size) +{ + if (new_size > cusexmp_size) + return cusexmp_resize(new_size); + return 0; +} + +static void cusexmp_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi) +{ + fuse_reply_open(req, fi); +} + +static void cusexmp_read(fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + if (off >= cusexmp_size) + off = cusexmp_size; + if (size > cusexmp_size - off) + size = cusexmp_size - off; + + fuse_reply_buf(req, cusexmp_buf + off, size); +} + +static void cusexmp_write(fuse_req_t req, const char *buf, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void)fi; + + if (cusexmp_expand(off + size)) { + fuse_reply_err(req, ENOMEM); + return; + } + + memcpy(cusexmp_buf + off, buf, size); + fuse_reply_write(req, size); +} + +static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf, + size_t in_bufsz, size_t out_bufsz, int is_read) +{ + const struct fioc_rw_arg *arg; + struct iovec in_iov[2], out_iov[3], iov[3]; + size_t cur_size; + + /* read in arg */ + in_iov[0].iov_base = addr; + in_iov[0].iov_len = sizeof(*arg); + if (!in_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0); + return; + } + arg = in_buf; + in_buf += sizeof(*arg); + in_bufsz -= sizeof(*arg); + + /* prepare size outputs */ + out_iov[0].iov_base = + addr + offsetof(struct fioc_rw_arg, prev_size); + out_iov[0].iov_len = sizeof(arg->prev_size); + + out_iov[1].iov_base = + addr + offsetof(struct fioc_rw_arg, new_size); + out_iov[1].iov_len = sizeof(arg->new_size); + + /* prepare client buf */ + if (is_read) { + out_iov[2].iov_base = arg->buf; + out_iov[2].iov_len = arg->size; + if (!out_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3); + return; + } + } else { + in_iov[1].iov_base = arg->buf; + in_iov[1].iov_len = arg->size; + if (arg->size && !in_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2); + return; + } + } + + /* we're all set */ + cur_size = cusexmp_size; + iov[0].iov_base = &cur_size; + iov[0].iov_len = sizeof(cur_size); + + iov[1].iov_base = &cusexmp_size; + iov[1].iov_len = sizeof(cusexmp_size); + + if (is_read) { + size_t off = arg->offset; + size_t size = arg->size; + + if (off >= cusexmp_size) + off = cusexmp_size; + if (size > cusexmp_size - off) + size = cusexmp_size - off; + + iov[2].iov_base = cusexmp_buf + off; + iov[2].iov_len = size; + fuse_reply_ioctl_iov(req, size, iov, 3); + } else { + if (cusexmp_expand(arg->offset + in_bufsz)) { + fuse_reply_err(req, ENOMEM); + return; + } + + memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz); + fuse_reply_ioctl_iov(req, in_bufsz, iov, 2); + } +} + +static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + int is_read = 0; + + (void)fi; + + if (flags & FUSE_IOCTL_COMPAT) { + fuse_reply_err(req, ENOSYS); + return; + } + + switch (cmd) { + case FIOC_GET_SIZE: + if (!out_bufsz) { + struct iovec iov = { arg, sizeof(size_t) }; + + fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); + } else + fuse_reply_ioctl(req, 0, &cusexmp_size, + sizeof(cusexmp_size)); + break; + + case FIOC_SET_SIZE: + if (!in_bufsz) { + struct iovec iov = { arg, sizeof(size_t) }; + + fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); + } else { + cusexmp_resize(*(size_t *)in_buf); + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case FIOC_READ: + is_read = 1; + /* fall through */ + case FIOC_WRITE: + fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read); + break; + + default: + fuse_reply_err(req, EINVAL); + } +} + +struct cusexmp_param { + unsigned major; + unsigned minor; + char *dev_name; + int is_help; +}; + +#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 } + +static const struct fuse_opt cusexmp_opts[] = { + CUSEXMP_OPT("-M %u", major), + CUSEXMP_OPT("--maj=%u", major), + CUSEXMP_OPT("-m %u", minor), + CUSEXMP_OPT("--min=%u", minor), + CUSEXMP_OPT("-n %s", dev_name), + CUSEXMP_OPT("--name=%s", dev_name), + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + FUSE_OPT_END +}; + +static int cusexmp_process_arg(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct cusexmp_param *param = data; + + (void)outargs; + (void)arg; + + switch (key) { + case 0: + param->is_help = 1; + fprintf(stderr, "%s", usage); + return fuse_opt_add_arg(outargs, "-ho"); + default: + return 1; + } +} + +static const struct cuse_lowlevel_ops cusexmp_clop = { + .init = cusexmp_init, + .open = cusexmp_open, + .read = cusexmp_read, + .write = cusexmp_write, + .ioctl = cusexmp_ioctl, +}; + +int main(int argc, char **argv) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct cusexmp_param param = { 0, 0, NULL, 0 }; + char dev_name[128] = "DEVNAME="; + const char *dev_info_argv[] = { dev_name }; + struct cuse_info ci; + int ret = 1; + + if (fuse_opt_parse(&args, ¶m, cusexmp_opts, cusexmp_process_arg)) { + printf("failed to parse option\n"); + free(param.dev_name); + goto out; + } + + if (!param.is_help) { + if (!param.dev_name) { + fprintf(stderr, "Error: device name missing\n"); + goto out; + } + strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME=")); + free(param.dev_name); + } + + memset(&ci, 0, sizeof(ci)); + ci.dev_major = param.major; + ci.dev_minor = param.minor; + ci.dev_info_argc = 1; + ci.dev_info_argv = dev_info_argv; + ci.flags = CUSE_UNRESTRICTED_IOCTL; + + ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL); + +out: + fuse_opt_free_args(&args); + return ret; +} diff --git a/example/cuse_client.c b/example/cuse_client.c new file mode 100644 index 0000000..903ffc6 --- /dev/null +++ b/example/cuse_client.c @@ -0,0 +1,157 @@ +/* + FUSE fioclient: FUSE ioctl example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This program tests the cuse.c example file system. + * + * Example usage (assuming that /dev/foobar is a CUSE device provided + * by the cuse.c example file system): + * + * $ cuse_client /dev/foobar s + * 0 + * + * $ echo "hello" | cuse_client /dev/foobar w 6 + * Writing 6 bytes + * transferred 6 bytes (0 -> 6) + * + * $ cuse_client /dev/foobar s + * 6 + * + * $ cuse_client /dev/foobar r 10 + * hello + * transferred 6 bytes (6 -> 6) + * + * Compiling this example + * + * gcc -Wall cuse_client.c -o cuse_client + * + * ## Source Code ## + * \include cuse_client.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ioctl.h" + +const char *usage = +"Usage: cuse_client FIOC_FILE COMMAND\n" +"\n" +"COMMANDS\n" +" s [SIZE] : get size if SIZE is omitted, set size otherwise\n" +" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n" +" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n" +"\n"; + +static int do_rw(int fd, int is_read, size_t size, off_t offset, + size_t *prev_size, size_t *new_size) +{ + struct fioc_rw_arg arg = { .offset = offset }; + ssize_t ret; + + arg.buf = calloc(1, size); + if (!arg.buf) { + fprintf(stderr, "failed to allocated %zu bytes\n", size); + return -1; + } + + if (is_read) { + arg.size = size; + ret = ioctl(fd, FIOC_READ, &arg); + if (ret >= 0) + fwrite(arg.buf, 1, ret, stdout); + } else { + arg.size = fread(arg.buf, 1, size, stdin); + fprintf(stderr, "Writing %zu bytes\n", arg.size); + ret = ioctl(fd, FIOC_WRITE, &arg); + } + + if (ret >= 0) { + *prev_size = arg.prev_size; + *new_size = arg.new_size; + } else + perror("ioctl"); + + free(arg.buf); + return ret; +} + +int main(int argc, char **argv) +{ + size_t param[2] = { }; + size_t size, prev_size = 0, new_size = 0; + char cmd; + int fd, i, rc; + + if (argc < 3) + goto usage; + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + + cmd = tolower(argv[2][0]); + argc -= 3; + argv += 3; + + for (i = 0; i < argc; i++) { + char *endp; + param[i] = strtoul(argv[i], &endp, 0); + if (endp == argv[i] || *endp != '\0') + goto usage; + } + + switch (cmd) { + case 's': + if (!argc) { + if (ioctl(fd, FIOC_GET_SIZE, &size)) { + perror("ioctl"); + goto error; + } + printf("%zu\n", size); + } else { + size = param[0]; + if (ioctl(fd, FIOC_SET_SIZE, &size)) { + perror("ioctl"); + goto error; + } + } + close(fd); + return 0; + + case 'r': + case 'w': + rc = do_rw(fd, cmd == 'r', param[0], param[1], + &prev_size, &new_size); + if (rc < 0) + goto error; + fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n", + rc, prev_size, new_size); + close(fd); + return 0; + } + +usage: + fprintf(stderr, "%s", usage); + return 1; + +error: + close(fd); + return 1; +} diff --git a/example/cxxopts.hpp b/example/cxxopts.hpp new file mode 100644 index 0000000..80b695a --- /dev/null +++ b/example/cxxopts.hpp @@ -0,0 +1,2114 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, int n, UChar32 c) + { + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + class OptionException : public std::exception + { + public: + OptionException(const std::string& message) + : m_message(message) + { + } + + virtual const char* + what() const noexcept + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + namespace values + { + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw argument_incorrect_type(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw argument_incorrect_type(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } + + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return -static_cast(t-1)-1; + } + + template + T + checked_negate(T&&, const std::string& text, std::false_type) + { + throw argument_incorrect_type(text); + } + + template + void + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw argument_incorrect_type(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; + + auto value_match = match[3]; + + US result = 0; + + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw argument_incorrect_type(text); + } + + US next = result * base + digit; + if (result > next) + { + throw argument_incorrect_type(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw argument_incorrect_type(text); + } + } + + inline + void + parse_value(const std::string& text, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw argument_incorrect_type(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + abstract_value(T* t) + : m_store(t) + { + } + + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const + { + parse_value(text, *m_store); + } + + bool + is_container() const + { + return type_is_container::value; + } + + void + parse() const + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const + { + return m_default_value; + } + + std::string + get_implicit_value() const + { + return m_implicit_value; + } + + bool + is_boolean() const + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + else + { + return *m_store; + } + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() = default; + + standard_value() + { + set_default_and_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + const std::string& short_, + const std::string& long_, + const String& desc, + std::shared_ptr val + ) + : m_short(short_) + , m_long(long_) + , m_desc(desc) + , m_value(val) + , m_count(0) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + OptionDetails(OptionDetails&& rhs) = default; + + const String& + description() const + { + return m_desc; + } + + const Value& value() const { + return *m_value; + } + + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + const std::string& + short_name() const + { + return m_short; + } + + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name; + std::string description; + std::vector options; + }; + + class OptionValue + { + public: + void + parse + ( + std::shared_ptr details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } + + void + parse_default(std::shared_ptr details) + { + ensure_value(details); + m_default = true; + m_value->parse(); + } + + size_t + count() const noexcept + { + return m_count; + } + + // TODO: maybe default options should count towards the number of arguments + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw std::domain_error("No value"); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(std::shared_ptr details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + std::shared_ptr m_value; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const + std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_present_exception(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(std::shared_ptr details); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + class Options + { + typedef std::unordered_map> + OptionMap; + public: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help + ); + + //parse positional arguments into the given option + void + parse_positional(std::string option); + + void + parse_positional(std::vector options); + + void + parse_positional(std::initializer_list options); + + template + void + parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector{begin, end}); + } + + std::string + help(const std::vector& groups = {}) const; + + const std::vector + groups() const; + + const HelpGroupDetails& + group_help(const std::string& group) const; + + private: + + void + add_one_option + ( + const std::string& option, + std::shared_ptr details + ); + + String + help_one_group(const std::string& group) const; + + void + generate_group_help + ( + String& result, + const std::vector& groups + ) const; + + void + generate_all_groups_help(String& result) const; + + std::string m_program; + String m_help_string; + std::string m_custom_help; + std::string m_positional_help; + bool m_show_positional; + bool m_allow_unrecognised; + + std::shared_ptr m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + + //mapping from groups to help options + std::map m_help; + }; + + class OptionAdder + { + public: + + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) + { + } + + OptionAdder& + operator() + ( + const std::string& opts, + const std::string& desc, + std::shared_ptr value + = ::cxxopts::value(), + std::string arg_help = "" + ); + + private: + Options& m_options; + std::string m_group; + }; + + namespace + { + constexpr int OPTION_LONGEST = 30; + constexpr int OPTION_DESC_GAP = 2; + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + String + format_option + ( + const HelpOptionDetails& o + ) + { + auto& s = o.s; + auto& l = o.l; + + String result = " "; + + if (s.size() > 0) + { + result += "-" + toLocalString(s) + ","; + } + else + { + result += " "; + } + + if (l.size() > 0) + { + result += " --" + toLocalString(l); + } + + auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + + if (!o.is_boolean) + { + if (o.has_implicit) + { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } + else + { + result += " " + arg; + } + } + + return result; + } + + String + format_description + ( + const HelpOptionDetails& o, + size_t start, + size_t width + ) + { + auto desc = o.desc; + + if (o.has_default && (!o.is_boolean || o.default_value != "false")) + { + desc += toLocalString(" (default: " + o.default_value + ")"); + } + + String result; + + auto current = std::begin(desc); + auto startLine = current; + auto lastSpace = current; + + auto size = size_t{}; + + while (current != std::end(desc)) + { + if (*current == ' ') + { + lastSpace = current; + } + + if (*current == '\n') + { + startLine = current + 1; + lastSpace = startLine; + } + else if (size > width) + { + if (lastSpace == startLine) + { + stringAppend(result, startLine, current + 1); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = current + 1; + lastSpace = startLine; + } + else + { + stringAppend(result, startLine, lastSpace); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = lastSpace + 1; + lastSpace = startLine; + } + size = 0; + } + else + { + ++size; + } + + ++current; + } + + //append whatever is left + stringAppend(result, startLine, current); + + return result; + } + } + +inline +ParseResult::ParseResult +( + const std::shared_ptr< + std::unordered_map> + > options, + std::vector positional, + bool allow_unrecognised, + int& argc, char**& argv +) +: m_options(options) +, m_positional(std::move(positional)) +, m_next_positional(m_positional.begin()) +, m_allow_unrecognised(allow_unrecognised) +{ + parse(argc, argv); +} + +inline +OptionAdder +Options::add_options(std::string group) +{ + return OptionAdder(*this, std::move(group)); +} + +inline +OptionAdder& +OptionAdder::operator() +( + const std::string& opts, + const std::string& desc, + std::shared_ptr value, + std::string arg_help +) +{ + std::match_results result; + std::regex_match(opts.c_str(), result, option_specifier); + + if (result.empty()) + { + throw invalid_option_format_error(opts); + } + + const auto& short_match = result[2]; + const auto& long_match = result[3]; + + if (!short_match.length() && !long_match.length()) + { + throw invalid_option_format_error(opts); + } else if (long_match.length() == 1 && short_match.length()) + { + throw invalid_option_format_error(opts); + } + + auto option_names = [] + ( + const std::sub_match& short_, + const std::sub_match& long_ + ) + { + if (long_.length() == 1) + { + return std::make_tuple(long_.str(), short_.str()); + } + else + { + return std::make_tuple(short_.str(), long_.str()); + } + }(short_match, long_match); + + m_options.add_option + ( + m_group, + std::get<0>(option_names), + std::get<1>(option_names), + desc, + value, + std::move(arg_help) + ); + + return *this; +} + +inline +void +ParseResult::parse_default(std::shared_ptr details) +{ + m_results[details].parse_default(details); +} + +inline +void +ParseResult::parse_option +( + std::shared_ptr value, + const std::string& /*name*/, + const std::string& arg +) +{ + auto& result = m_results[value]; + result.parse(value, arg); + + m_sequential.emplace_back(value->long_name(), arg); +} + +inline +void +ParseResult::checked_parse_arg +( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name +) +{ + if (current + 1 >= argc) + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + throw missing_argument_exception(name); + } + } + else + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + parse_option(value, name, argv[current + 1]); + ++current; + } + } +} + +inline +void +ParseResult::add_to_option(const std::string& option, const std::string& arg) +{ + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_exists_exception(option); + } + + parse_option(iter->second, option, arg); +} + +inline +bool +ParseResult::consume_positional(std::string a) +{ + while (m_next_positional != m_positional.end()) + { + auto iter = m_options->find(*m_next_positional); + if (iter != m_options->end()) + { + auto& result = m_results[iter->second]; + if (!iter->second->value().is_container()) + { + if (result.count() == 0) + { + add_to_option(*m_next_positional, a); + ++m_next_positional; + return true; + } + else + { + ++m_next_positional; + continue; + } + } + else + { + add_to_option(*m_next_positional, a); + return true; + } + } + else + { + throw option_not_exists_exception(*m_next_positional); + } + } + + return false; +} + +inline +void +Options::parse_positional(std::string option) +{ + parse_positional(std::vector{std::move(option)}); +} + +inline +void +Options::parse_positional(std::vector options) +{ + m_positional = std::move(options); + m_next_positional = m_positional.begin(); + + m_positional_set.insert(m_positional.begin(), m_positional.end()); +} + +inline +void +Options::parse_positional(std::initializer_list options) +{ + parse_positional(std::vector(std::move(options))); +} + +inline +ParseResult +Options::parse(int& argc, char**& argv) +{ + ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); + return result; +} + +inline +void +ParseResult::parse(int& argc, char**& argv) +{ + int current = 1; + + int nextKeep = 1; + + bool consume_remaining = false; + + while (current != argc) + { + if (strcmp(argv[current], "--") == 0) + { + consume_remaining = true; + ++current; + break; + } + + std::match_results result; + std::regex_match(argv[current], result, option_matcher); + + if (result.empty()) + { + //not a flag + + // but if it starts with a `-`, then it's an error + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!m_allow_unrecognised) { + throw option_syntax_exception(argv[current]); + } + } + + //if true is returned here then it was consumed, otherwise it is + //ignored + if (consume_positional(argv[current])) + { + } + else + { + argv[nextKeep] = argv[current]; + ++nextKeep; + } + //if we return from here then it was parsed successfully, so continue + } + else + { + //short or long option? + if (result[4].length() != 0) + { + const std::string& s = result[4]; + + for (std::size_t i = 0; i != s.size(); ++i) + { + std::string name(1, s[i]); + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto value = iter->second; + + if (i + 1 == s.size()) + { + //it must be the last argument + checked_parse_arg(argc, argv, current, value, name); + } + else if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + //error + throw option_requires_argument_exception(name); + } + } + } + else if (result[1].length() != 0) + { + const std::string& name = result[1]; + + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + // keep unrecognised options in argument list, skip to next argument + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto opt = iter->second; + + //equals provided for long option? + if (result[2].length() != 0) + { + //parse the option given + + parse_option(opt, name, result[3]); + } + else + { + //parse the next argument + checked_parse_arg(argc, argv, current, opt, name); + } + } + + } + + ++current; + } + + for (auto& opt : *m_options) + { + auto& detail = opt.second; + auto& value = detail->value(); + + auto& store = m_results[detail]; + + if(value.has_default() && !store.count() && !store.has_default()){ + parse_default(detail); + } + } + + if (consume_remaining) + { + while (current < argc) + { + if (!consume_positional(argv[current])) { + break; + } + ++current; + } + + //adjust argv for any that couldn't be swallowed + while (current != argc) { + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + } + } + + argc = nextKeep; + +} + +inline +void +Options::add_option +( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help +) +{ + auto stringDesc = toLocalString(std::move(desc)); + auto option = std::make_shared(s, l, stringDesc, value); + + if (s.size() > 0) + { + add_one_option(s, option); + } + + if (l.size() > 0) + { + add_one_option(l, option); + } + + //add the help details + auto& options = m_help[group]; + + options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, + value->has_default(), value->get_default_value(), + value->has_implicit(), value->get_implicit_value(), + std::move(arg_help), + value->is_container(), + value->is_boolean()}); +} + +inline +void +Options::add_one_option +( + const std::string& option, + std::shared_ptr details +) +{ + auto in = m_options->emplace(option, details); + + if (!in.second) + { + throw option_exists_error(option); + } +} + +inline +String +Options::help_one_group(const std::string& g) const +{ + typedef std::vector> OptionHelp; + + auto group = m_help.find(g); + if (group == m_help.end()) + { + return ""; + } + + OptionHelp format; + + size_t longest = 0; + + String result; + + if (!g.empty()) + { + result += toLocalString(" " + g + " options:\n"); + } + + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto s = format_option(o); + longest = (std::max)(longest, stringLength(s)); + format.push_back(std::make_pair(s, String())); + } + + longest = (std::min)(longest, static_cast(OPTION_LONGEST)); + + //widest allowed description + auto allowed = size_t{76} - longest - OPTION_DESC_GAP; + + auto fiter = format.begin(); + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); + + result += fiter->first; + if (stringLength(fiter->first) > longest) + { + result += '\n'; + result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); + } + else + { + result += toLocalString(std::string(longest + OPTION_DESC_GAP - + stringLength(fiter->first), + ' ')); + } + result += d; + result += '\n'; + + ++fiter; + } + + return result; +} + +inline +void +Options::generate_group_help +( + String& result, + const std::vector& print_groups +) const +{ + for (size_t i = 0; i != print_groups.size(); ++i) + { + const String& group_help_text = help_one_group(print_groups[i]); + if (empty(group_help_text)) + { + continue; + } + result += group_help_text; + if (i < print_groups.size() - 1) + { + result += '\n'; + } + } +} + +inline +void +Options::generate_all_groups_help(String& result) const +{ + std::vector all_groups; + all_groups.reserve(m_help.size()); + + for (auto& group : m_help) + { + all_groups.push_back(group.first); + } + + generate_group_help(result, all_groups); +} + +inline +std::string +Options::help(const std::vector& help_groups) const +{ + String result = m_help_string + "\nUsage:\n " + + toLocalString(m_program) + " " + toLocalString(m_custom_help); + + if (m_positional.size() > 0 && m_positional_help.size() > 0) { + result += " " + toLocalString(m_positional_help); + } + + result += "\n\n"; + + if (help_groups.size() == 0) + { + generate_all_groups_help(result); + } + else + { + generate_group_help(result, help_groups); + } + + return toUTF8String(result); +} + +inline +const std::vector +Options::groups() const +{ + std::vector g; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(g), + [] (const std::map::value_type& pair) + { + return pair.first; + } + ); + + return g; +} + +inline +const HelpGroupDetails& +Options::group_help(const std::string& group) const +{ + return m_help.at(group); +} + +} + +#endif //CXXOPTS_HPP_INCLUDED diff --git a/example/hello.c b/example/hello.c new file mode 100644 index 0000000..d9f01b9 --- /dev/null +++ b/example/hello.c @@ -0,0 +1,184 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * minimal example filesystem using high-level API + * + * Compile with: + * + * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello + * + * ## Source code ## + * \include hello.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include + +/* + * Command line options + * + * We can't set default values for the char* fields here because + * fuse_opt_parse would attempt to free() them when the user specifies + * different values on the command line. + */ +static struct options { + const char *filename; + const char *contents; + int show_help; +} options; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--name=%s", filename), + OPTION("--contents=%s", contents), + OPTION("-h", show_help), + OPTION("--help", show_help), + FUSE_OPT_END +}; + +static void *hello_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ); + + return NULL; +} + +static int hello_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int res = 0; + + memset(stbuf, 0, sizeof(struct stat)); + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path+1, options.filename) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(options.contents); + } else + res = -ENOENT; + + return res; +} + +static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void) offset; + (void) fi; + (void) flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int hello_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path+1, options.filename) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int hello_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + size_t len; + (void) fi; + if(strcmp(path+1, options.filename) != 0) + return -ENOENT; + + len = strlen(options.contents); + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, options.contents + offset, size); + } else + size = 0; + + return size; +} + +static const struct fuse_operations hello_oper = { + .init = hello_init, + .getattr = hello_getattr, + .readdir = hello_readdir, + .open = hello_open, + .read = hello_read, +}; + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --name= Name of the \"hello\" file\n" + " (default: \"hello\")\n" + " --contents= Contents \"hello\" file\n" + " (default \"Hello, World!\\n\")\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + int ret; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Set defaults -- we have to use strdup so that + fuse_opt_parse can free the defaults if other + values are specified */ + options.filename = strdup("hello"); + options.contents = strdup("Hello World!\n"); + + /* Parse options */ + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + /* When --help is specified, first print our own file-system + specific help text, then signal fuse_main to show + additional help (by adding `--help` to the options again) + without usage: line (by setting argv[0] to the empty + string) */ + if (options.show_help) { + show_help(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } + + ret = fuse_main(args.argc, args.argv, &hello_oper, NULL); + fuse_opt_free_args(&args); + return ret; +} diff --git a/example/hello_ll.c b/example/hello_ll.c new file mode 100644 index 0000000..12927cc --- /dev/null +++ b/example/hello_ll.c @@ -0,0 +1,293 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * minimal example filesystem using low-level API + * + * Compile with: + * + * gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll + * + * ## Source code ## + * \include hello_ll.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *hello_str = "Hello World!\n"; +static const char *hello_name = "hello"; + +static int hello_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + switch (ino) { + case 1: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + + case 2: + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(hello_str); + break; + + default: + return -1; + } + return 0; +} + +static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; +} + +static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (hello_stat(ino, &stbuf) == -1) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 1.0); +} + +static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != 1 || strcmp(name, hello_name) != 0) + fuse_reply_err(req, ENOENT); + else { + memset(&e, 0, sizeof(e)); + e.ino = 2; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + hello_stat(e.ino, &e.attr); + + fuse_reply_entry(req, &e); + } +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + if (ino != 1) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, ".", 1); + dirbuf_add(req, &b, "..", 1); + dirbuf_add(req, &b, hello_name, 2); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void hello_ll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + if (ino != 2) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else + fuse_reply_open(req, fi); +} + +static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + assert(ino == 2); + reply_buf_limited(req, hello_str, strlen(hello_str), off, size); +} + +static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + (void)size; + assert(ino == 1 || ino == 2); + if (strcmp(name, "hello_ll_getxattr_name") == 0) + { + const char *buf = "hello_ll_getxattr_value"; + fuse_reply_buf(req, buf, strlen(buf)); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + (void)flags; + (void)size; + assert(ino == 1 || ino == 2); + const char* exp_val = "hello_ll_setxattr_value"; + if (strcmp(name, "hello_ll_setxattr_name") == 0 && + strlen(exp_val) == size && + strncmp(value, exp_val, size) == 0) + { + fuse_reply_err(req, 0); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + assert(ino == 1 || ino == 2); + if (strcmp(name, "hello_ll_removexattr_name") == 0) + { + fuse_reply_err(req, 0); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static const struct fuse_lowlevel_ops hello_ll_oper = { + .init = hello_ll_init, + .lookup = hello_ll_lookup, + .getattr = hello_ll_getattr, + .readdir = hello_ll_readdir, + .open = hello_ll_open, + .read = hello_ll_read, + .setxattr = hello_ll_setxattr, + .getxattr = hello_ll_getxattr, + .removexattr = hello_ll_removexattr, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + int ret = -1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if(opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + se = fuse_session_new(&args, &hello_ll_oper, + sizeof(hello_ll_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/hello_ll_uds.c b/example/hello_ll_uds.c new file mode 100644 index 0000000..e9cd173 --- /dev/null +++ b/example/hello_ll_uds.c @@ -0,0 +1,367 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2022 Tofik Sonono + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * minimal example filesystem using low-level API and a custom io. This custom + * io is implemented using UNIX domain sockets (of type SOCK_STREAM) + * + * Compile with: + * + * gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds + * + * ## Source code ## + * \include hello_ll.c + */ + +#define FUSE_USE_VERSION 34 + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *hello_str = "Hello World!\n"; +static const char *hello_name = "hello"; + +static int hello_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + switch (ino) { + case 1: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + + case 2: + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(hello_str); + break; + + default: + return -1; + } + return 0; +} + +static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (hello_stat(ino, &stbuf) == -1) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 1.0); +} + +static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != 1 || strcmp(name, hello_name) != 0) + fuse_reply_err(req, ENOENT); + else { + memset(&e, 0, sizeof(e)); + e.ino = 2; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + hello_stat(e.ino, &e.attr); + + fuse_reply_entry(req, &e); + } +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + if (ino != 1) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, ".", 1); + dirbuf_add(req, &b, "..", 1); + dirbuf_add(req, &b, hello_name, 2); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void hello_ll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + if (ino != 2) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else + fuse_reply_open(req, fi); +} + +static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + assert(ino == 2); + reply_buf_limited(req, hello_str, strlen(hello_str), off, size); +} + +static const struct fuse_lowlevel_ops hello_ll_oper = { + .init = hello_ll_init, + .lookup = hello_ll_lookup, + .getattr = hello_ll_getattr, + .readdir = hello_ll_readdir, + .open = hello_ll_open, + .read = hello_ll_read, +}; + +static int create_socket(const char *socket_path) { + struct sockaddr_un addr; + + if (strnlen(socket_path, sizeof(addr.sun_path)) >= + sizeof(addr.sun_path)) { + printf("Socket path may not be longer than %zu characters\n", + sizeof(addr.sun_path) - 1); + return -1; + } + + if (remove(socket_path) == -1 && errno != ENOENT) { + printf("Could not delete previous socket file entry at %s. Error: " + "%s\n", socket_path, strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(struct sockaddr_un)); + strcpy(addr.sun_path, socket_path); + + int sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd == -1) { + printf("Could not create socket. Error: %s\n", strerror(errno)); + return -1; + } + + addr.sun_family = AF_UNIX; + if (bind(sfd, (struct sockaddr *) &addr, + sizeof(struct sockaddr_un)) == -1) { + printf("Could not bind socket. Error: %s\n", strerror(errno)); + return -1; + } + + if (listen(sfd, 1) == -1) + return -1; + + printf("Awaiting connection on socket at %s...\n", socket_path); + int cfd = accept(sfd, NULL, NULL); + if (cfd == -1) { + printf("Could not accept connection. Error: %s\n", + strerror(errno)); + return -1; + } else { + printf("Accepted connection!\n"); + } + return cfd; +} + +static ssize_t stream_writev(int fd, struct iovec *iov, int count, + void *userdata) { + (void)userdata; + + ssize_t written = 0; + int cur = 0; + for (;;) { + written = writev(fd, iov+cur, count-cur); + if (written < 0) + return written; + + while (cur < count && written >= iov[cur].iov_len) + written -= iov[cur++].iov_len; + if (cur == count) + break; + + iov[cur].iov_base = (char *)iov[cur].iov_base + written; + iov[cur].iov_len -= written; + } + return written; +} + + +static ssize_t readall(int fd, void *buf, size_t len) { + size_t count = 0; + + while (count < len) { + int i = read(fd, (char *)buf + count, len - count); + if (!i) + break; + + if (i < 0) + return i; + + count += i; + } + return count; +} + +static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) { + (void)userdata; + + int res = readall(fd, buf, sizeof(struct fuse_in_header)); + if (res == -1) + return res; + + + uint32_t packet_len = ((struct fuse_in_header *)buf)->len; + if (packet_len > buf_len) + return -1; + + int prev_res = res; + + res = readall(fd, (char *)buf + sizeof(struct fuse_in_header), + packet_len - sizeof(struct fuse_in_header)); + + return (res == -1) ? res : (res + prev_res); +} + +static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata) { + (void)userdata; + + size_t count = 0; + while (count < len) { + int i = splice(fdin, offin, fdout, offout, len - count, flags); + if (i < 1) + return i; + + count += i; + } + return count; +} + +static void fuse_cmdline_help_uds(void) +{ + printf(" -h --help print help\n" + " -V --version print version\n" + " -d -o debug enable debug output (implies -f)\n"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + const struct fuse_custom_io io = { + .writev = stream_writev, + .read = stream_read, + .splice_receive = NULL, + .splice_send = stream_splice_send, + }; + int cfd = -1; + int ret = -1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options]\n\n", argv[0]); + fuse_cmdline_help_uds(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + se = fuse_session_new(&args, &hello_ll_oper, + sizeof(hello_ll_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + cfd = create_socket("/tmp/libfuse-hello-ll.sock"); + if (cfd == -1) + goto err_out3; + + if (fuse_session_custom_io(se, &io, cfd) != 0) + goto err_out3; + + /* Block until ctrl+c */ + ret = fuse_session_loop(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/invalidate_path.c b/example/invalidate_path.c new file mode 100644 index 0000000..0e8d77f --- /dev/null +++ b/example/invalidate_path.c @@ -0,0 +1,292 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + (C) 2017 EditShare LLC + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. + */ + +/** @file + * + * This example implements a file system with two files: + * * 'current-time', whose contents change dynamically: + * it always contains the current time (same as in + * notify_inval_inode.c). + * * 'growing', whose size changes dynamically, growing + * by 1 byte after each update. This aims to check + * if cached file metadata is also invalidated. + * + * ## Compilation ## + * + * gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path + * + * ## Source code ## + * \include invalidate_path.c + */ + +#define FUSE_USE_VERSION 34 + +#include +#include /* for fuse_cmdline_opts */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define TIME_FILE_NAME "current_time" +#define TIME_FILE_INO 2 +#define GROW_FILE_NAME "growing" +#define GROW_FILE_INO 3 + +static char time_file_contents[MAX_STR_LEN]; +static size_t grow_file_size; + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + FUSE_OPT_END +}; + +static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + (void) conn; + cfg->entry_timeout = NO_TIMEOUT; + cfg->attr_timeout = NO_TIMEOUT; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, + struct stat *stbuf, struct fuse_file_info* fi) { + (void) fi; + if (strcmp(path, "/") == 0) { + stbuf->st_ino = 1; + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) { + stbuf->st_ino = TIME_FILE_INO; + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(time_file_contents); + } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) { + stbuf->st_ino = GROW_FILE_INO; + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = grow_file_size; + } else { + return -ENOENT; + } + + return 0; +} + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) { + (void) fi; + (void) offset; + (void) flags; + if (strcmp(path, "/") != 0) { + return -ENOTDIR; + } else { + (void) filler; + (void) buf; + struct stat file_stat; + xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL); + filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS); + xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL); + filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS); + return 0; + } +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) { + (void) path; + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) { + (void) fi; + (void) offset; + if (strcmp(path, "/" TIME_FILE_NAME) == 0) { + int file_length = strlen(time_file_contents); + int to_copy = offset + size <= file_length + ? size + : file_length - offset; + memcpy(buf, time_file_contents, to_copy); + return to_copy; + } else { + assert(strcmp(path, "/" GROW_FILE_NAME) == 0); + int to_copy = offset + size <= grow_file_size + ? size + : grow_file_size - offset; + memset(buf, 'x', to_copy); + return to_copy; + } +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .readdir = xmp_readdir, + .open = xmp_open, + .read = xmp_read, +}; + +static void update_fs(void) { + static int count = 0; + struct tm *now; + time_t t; + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + int time_file_size = strftime(time_file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(time_file_size != 0); + + grow_file_size = count++; +} + +static int invalidate(struct fuse *fuse, const char *path) { + int status = fuse_invalidate_path(fuse, path); + if (status == -ENOENT) { + return 0; + } else { + return status; + } +} + +static void* update_fs_loop(void *data) { + struct fuse *fuse = (struct fuse*) data; + + while (1) { + update_fs(); + if (!options.no_notify) { + assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0); + assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0); + } + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse *fuse; + struct fuse_cmdline_opts opts; + struct fuse_loop_config config; + int res; + + /* Initialize the files */ + update_fs(); + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + res = 0; + goto out1; + } else if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lib_help(&args); + res = 0; + goto out1; + } else if (!opts.mountpoint) { + fprintf(stderr, "error: no mountpoint specified\n"); + res = 1; + goto out1; + } + + fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL); + if (fuse == NULL) { + res = 1; + goto out1; + } + + if (fuse_mount(fuse,opts.mountpoint) != 0) { + res = 1; + goto out2; + } + + if (fuse_daemonize(opts.foreground) != 0) { + res = 1; + goto out3; + } + + pthread_t updater; /* Start thread to update file contents */ + int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", strerror(ret)); + return 1; + }; + + struct fuse_session *se = fuse_get_session(fuse); + if (fuse_set_signal_handlers(se) != 0) { + res = 1; + goto out3; + } + + if (opts.singlethread) + res = fuse_loop(fuse); + else { + config.clone_fd = opts.clone_fd; + config.max_idle_threads = opts.max_idle_threads; + res = fuse_loop_mt(fuse, &config); + } + if (res) + res = 1; + + fuse_remove_signal_handlers(se); +out3: + fuse_unmount(fuse); +out2: + fuse_destroy(fuse); +out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + return res; +} diff --git a/example/ioctl.c b/example/ioctl.c new file mode 100644 index 0000000..9fe5c5b --- /dev/null +++ b/example/ioctl.c @@ -0,0 +1,230 @@ +/* + FUSE fioc: FUSE ioctl example + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * @tableofcontents + * + * This example illustrates how to write a FUSE file system that can + * process (a restricted set of) ioctls. It can be tested with the + * ioctl_client.c program. + * + * Compile with: + * + * gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl + * + * ## Source code ## + * \include ioctl.c + */ + +#define FUSE_USE_VERSION 35 + +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +#define FIOC_NAME "fioc" + +enum { + FIOC_NONE, + FIOC_ROOT, + FIOC_FILE, +}; + +static void *fioc_buf; +static size_t fioc_size; + +static int fioc_resize(size_t new_size) +{ + void *new_buf; + + if (new_size == fioc_size) + return 0; + + new_buf = realloc(fioc_buf, new_size); + if (!new_buf && new_size) + return -ENOMEM; + + if (new_size > fioc_size) + memset(new_buf + fioc_size, 0, new_size - fioc_size); + + fioc_buf = new_buf; + fioc_size = new_size; + + return 0; +} + +static int fioc_expand(size_t new_size) +{ + if (new_size > fioc_size) + return fioc_resize(new_size); + return 0; +} + +static int fioc_file_type(const char *path) +{ + if (strcmp(path, "/") == 0) + return FIOC_ROOT; + if (strcmp(path, "/" FIOC_NAME) == 0) + return FIOC_FILE; + return FIOC_NONE; +} + +static int fioc_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + stbuf->st_atime = stbuf->st_mtime = time(NULL); + + switch (fioc_file_type(path)) { + case FIOC_ROOT: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + case FIOC_FILE: + stbuf->st_mode = S_IFREG | 0644; + stbuf->st_nlink = 1; + stbuf->st_size = fioc_size; + break; + case FIOC_NONE: + return -ENOENT; + } + + return 0; +} + +static int fioc_open(const char *path, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_NONE) + return 0; + return -ENOENT; +} + +static int fioc_do_read(char *buf, size_t size, off_t offset) +{ + if (offset >= fioc_size) + return 0; + + if (size > fioc_size - offset) + size = fioc_size - offset; + + memcpy(buf, fioc_buf + offset, size); + + return size; +} + +static int fioc_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_do_read(buf, size, offset); +} + +static int fioc_do_write(const char *buf, size_t size, off_t offset) +{ + if (fioc_expand(offset + size)) + return -ENOMEM; + + memcpy(fioc_buf + offset, buf, size); + + return size; +} + +static int fioc_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_do_write(buf, size, offset); +} + +static int fioc_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + (void) fi; + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_resize(size); +} + +static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void) fi; + (void) offset; + (void) flags; + + if (fioc_file_type(path) != FIOC_ROOT) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int fioc_ioctl(const char *path, unsigned int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, void *data) +{ + (void) arg; + (void) fi; + (void) flags; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + if (flags & FUSE_IOCTL_COMPAT) + return -ENOSYS; + + switch (cmd) { + case FIOC_GET_SIZE: + *(size_t *)data = fioc_size; + return 0; + + case FIOC_SET_SIZE: + fioc_resize(*(size_t *)data); + return 0; + } + + return -EINVAL; +} + +static const struct fuse_operations fioc_oper = { + .getattr = fioc_getattr, + .readdir = fioc_readdir, + .truncate = fioc_truncate, + .open = fioc_open, + .read = fioc_read, + .write = fioc_write, + .ioctl = fioc_ioctl, +}; + +int main(int argc, char *argv[]) +{ + return fuse_main(argc, argv, &fioc_oper, NULL); +} diff --git a/example/ioctl.h b/example/ioctl.h new file mode 100644 index 0000000..a4f054c --- /dev/null +++ b/example/ioctl.h @@ -0,0 +1,42 @@ +/* + FUSE-ioctl: ioctl support for FUSE + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * @tableofcontents + * + * Header file to share definitions between the ioctl.c example file + * system and the ioctl_client.c test program. + * + * \include ioctl.h + */ + + +#include +#include +#include + +enum { + FIOC_GET_SIZE = _IOR('E', 0, size_t), + FIOC_SET_SIZE = _IOW('E', 1, size_t), + + /* + * The following two ioctls don't follow usual encoding rules + * and transfer variable amount of data. + */ + FIOC_READ = _IO('E', 2), + FIOC_WRITE = _IO('E', 3), +}; + +struct fioc_rw_arg { + off_t offset; + void *buf; + size_t size; + size_t prev_size; /* out param for previous total size */ + size_t new_size; /* out param for new total size */ +}; diff --git a/example/ioctl_client.c b/example/ioctl_client.c new file mode 100644 index 0000000..019d030 --- /dev/null +++ b/example/ioctl_client.c @@ -0,0 +1,76 @@ +/* + FUSE fioclient: FUSE ioctl example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program tests the ioctl.c example file systsem. + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This program tests the ioctl.c example file systsem. + * + * Compile with: + * + * gcc -Wall ioctl_client.c -o ioctl_client + * + * ## Source code ## + * \include ioctl_client.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ioctl.h" + +const char *usage = +"Usage: fioclient FIOC_FILE [size]\n" +"\n" +"Get size if is omitted, set size otherwise\n" +"\n"; + +int main(int argc, char **argv) +{ + size_t size; + int fd; + int ret = 0; + + if (argc < 2) { + fprintf(stderr, "%s", usage); + return 1; + } + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + + if (argc == 2) { + if (ioctl(fd, FIOC_GET_SIZE, &size)) { + perror("ioctl"); + ret = 1; + goto out; + } + printf("%zu\n", size); + } else { + size = strtoul(argv[2], NULL, 0); + if (ioctl(fd, FIOC_SET_SIZE, &size)) { + perror("ioctl"); + ret = 1; + goto out; + } + } +out: + close(fd); + return ret; +} diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc new file mode 100644 index 0000000..6038850 --- /dev/null +++ b/example/memfs_ll.cc @@ -0,0 +1,1157 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2024 DataDirect Networks. + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +#include +#define FUSE_USE_VERSION 317 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MEMFS_ATTR_TIMEOUT 0.0 +#define MEMFS_ENTRY_TIMEOUT 0.0 + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +class Inodes; +class Inode; +class Dentry; + +static void memfs_panic(std::string_view message); + +struct DirHandle { + std::vector > entries; + size_t offset; + + DirHandle(const std::vector > + &entries) + : entries(entries) + , offset(0) + { + } +}; + +class Inode { + private: + uint64_t ino; // Unique inode number + std::string name; + bool is_dir_; + time_t ctime; + time_t mtime; + time_t atime; + mode_t mode; + std::vector content; + std::vector dentries; + mutable std::mutex mutex; + uint64_t nlookup; + mutable std::mutex attr_mutex; + std::atomic nlink; + uid_t uid; + gid_t gid; + + friend class Inodes; + + public: + Inode(uint64_t ino, const std::string &n, bool dir) + : ino(ino) + , name(n) + , is_dir_(dir) + , ctime(time(NULL)) + , mtime(ctime) + , atime(ctime) + , mode(dir ? S_IFDIR | 0755 : S_IFREG | 0644) + , nlookup(1) + , nlink(dir ? 2 : 1) + , uid(0) + , gid(0) + { + } + + uint64_t get_ino() const + { + return ino; + } + + // Method to lock the mutex + void lock() const + { + mutex.lock(); + } + + // Method to unlock the mutex + void unlock() const + { + mutex.unlock(); + } + + void inc_lookup() + { + std::lock_guard lock(mutex); + nlookup++; + } + + uint64_t dec_lookup(uint64_t count) + { + std::unique_lock lock(mutex); + if (nlookup < count) { + lock.unlock(); + memfs_panic("Lookup count mismatch detected"); + } + nlookup -= count; + return nlookup; + } + + const std::string &get_name() const + { + return name; + } + bool is_dir() const + { + return is_dir_; + } + time_t get_ctime() const + { + return ctime; + } + time_t get_mtime() const + { + return mtime; + } + mode_t get_mode() const + { + return mode; + } + + size_t content_size() const + { + return content.size(); + } + + void read_content(char *buf, size_t size, off_t offset) const + { + size_t bytes_to_read = std::min(size, content.size() - (size_t)offset); + std::copy(content.begin() + offset, + content.begin() + offset + bytes_to_read, buf); + } + + void write_content(const char *buf, size_t size, off_t offset) + { + std::lock_guard lock(mutex); + if (offset + size > content.size()) { + content.resize(offset + size); + } + std::copy(buf, buf + size, content.begin() + offset); + mtime = time(NULL); + } + + void set_uid(uid_t _uid) + { + std::lock_guard lock(attr_mutex); + uid = _uid; + } + + void set_gid(gid_t _gid) + { + std::lock_guard lock(attr_mutex); + gid = _gid; + } + + void set_mode(mode_t new_mode) + { + std::lock_guard lock(attr_mutex); + mode = new_mode; + } + + void set_atime(const struct timespec &_atime) + { + std::lock_guard lock(attr_mutex); + atime = _atime.tv_sec; + } + + void set_mtime(const struct timespec &_mtime) + { + std::lock_guard lock(attr_mutex); + mtime = _mtime.tv_sec; + } + + void truncate(off_t size) + { + std::lock_guard lock(mutex); + std::lock_guard attr_lock(attr_mutex); + if (size < content.size()) { + content.resize(size); + } else if (size > content.size()) { + content.resize(size, 0); + } + mtime = time(NULL); + } + + void get_attr(struct stat *stbuf) const + { + std::lock_guard lock(attr_mutex); + stbuf->st_ino = ino; + stbuf->st_mode = mode; + stbuf->st_nlink = nlink; + stbuf->st_uid = uid; + stbuf->st_gid = gid; + stbuf->st_size = content.size(); + stbuf->st_blocks = DIV_ROUND_UP(content.size(), 512); + stbuf->st_atime = atime; + stbuf->st_mtime = mtime; + stbuf->st_ctime = ctime; + } + + bool is_empty() const + { + return dentries.empty(); + } + + void inc_nlink() + { + nlink++; + } + + nlink_t dec_nlink() + { + nlink_t old_value = + nlink.fetch_sub(1, std::memory_order_relaxed); + if (old_value == 0) { + memfs_panic("Attempting to decrement nlink below zero"); + } + return old_value - 1; + } + + /** + * Methods that need Dentry knowledge + */ + int add_child_locked(const std::string &name, Dentry *child_dentry); + int add_child(const std::string &name, Dentry *child_dentry); + int remove_child(const std::string &name); + std::vector > + get_children() const; + Dentry *find_child_locked(const std::string &name) const; + Dentry *find_child(const std::string &name) const; +}; + +class Dentry { + public: + std::string name; + Inode *inode; + + Dentry(const std::string &n, Inode *i) + : name(n) + , inode(i) + { + } + + uint64_t get_ino() const + { + return inode->get_ino(); + } + bool is_dir() const + { + return inode->is_dir(); + } + const std::string &get_name() const + { + return name; + } + + time_t get_ctime() const + { + return inode->get_ctime(); + } + time_t get_mtime() const + { + return inode->get_mtime(); + } + mode_t get_mode() const + { + return inode->get_mode(); + } + size_t content_size() const + { + return inode->content_size(); + } + + Inode *get_inode() const + { + return inode; + } + + void inc_lookup() + { + inode->inc_lookup(); + } +}; + +class Inodes { + private: + std::unordered_map > inodes; + mutable std::shared_mutex inodes_mutex; + std::atomic next_ino{ FUSE_ROOT_ID + 1 }; + std::mutex mutex; + + public: + Inodes() + { + auto root = std::make_unique(FUSE_ROOT_ID, "/", true); + root->mode = S_IFDIR | 0755; + root->nlink = 2; // . and .. + inodes[FUSE_ROOT_ID] = std::move(root); + } + + // New lock method + void lock() + { + inodes_mutex.lock(); + } + + // New unlock method + void unlock() + { + inodes_mutex.unlock(); + } + + void erase_locked(Inode *inode) + { + if (inode) { + inodes.erase(inode->get_ino()); + } + } + + void erase(Inode *inode) + { + std::unique_lock lock(inodes_mutex); + erase_locked(inode); + } + + Inode *find_locked(fuse_ino_t ino) + { + auto it = inodes.find(ino); + if (it == inodes.end()) { + return nullptr; + } + return it->second.get(); + } + + Inode *find(fuse_ino_t ino) + { + std::shared_lock lock(inodes_mutex); + return find_locked(ino); + } + + Inode *create(const std::string &name, bool is_dir, mode_t mode) + { + std::unique_lock lock(inodes_mutex); + + uint64_t ino = next_ino.fetch_add(1, std::memory_order_relaxed); + auto new_inode = std::make_unique(ino, name, is_dir); + new_inode->set_mode(mode); + + auto [it, inserted] = inodes.emplace(ino, std::move(new_inode)); + + if (!inserted) { + // This should never happen, but let's handle it just in case + return nullptr; + } + + return it->second.get(); + } + + size_t size() + { + std::lock_guard lock(mutex); + return inodes.size(); + } +}; + +int Inode::add_child_locked(const std::string &name, Dentry *child_dentry) +{ + if (!is_dir_) { + return ENOTDIR; + } + + // Check if a child with this name already exists + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it != dentries.end()) { + return EEXIST; + } + + dentries.push_back(child_dentry); + if (child_dentry->is_dir()) { + nlink++; + } + return 0; +} + +int Inode::add_child(const std::string &name, Dentry *child_dentry) +{ + std::lock_guard lock(mutex); + return add_child_locked(name, child_dentry); +} + +int Inode::remove_child(const std::string &name) +{ + if (!is_dir_) { + return ENOTDIR; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it == dentries.end()) { + return ENOENT; + } + + Dentry *child_dentry = *it; + dentries.erase(it); + + if (child_dentry->is_dir()) { + nlink--; + } + + delete child_dentry; + return 0; +} +Dentry *Inode::find_child_locked(const std::string &name) const +{ + if (!is_dir_) { + return nullptr; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + return (it != dentries.end()) ? *it : nullptr; +} + +Dentry *Inode::find_child(const std::string &name) const +{ + std::lock_guard lock(mutex); + return find_child_locked(name); +} + +std::vector > Inode::get_children() const +{ + if (!is_dir_) { + return {}; // Return an empty vector if this is not a directory + } + + std::vector > children; + children.reserve(dentries.size()); + + for (size_t i = 0; i < dentries.size(); ++i) { + const Dentry *dentry = dentries[i]; + std::string name = dentry->get_name(); + children.emplace_back(name, dentry); + } + + return children; +} +static Inodes Inodes; + +static void memfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + + if (!parentInode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (!parentInode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + Dentry *child = parentInode->find_child(name); + if (!child) { + fuse_reply_err(req, ENOENT); + return; + } + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = child->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + e.attr.st_ino = child->get_ino(); + e.attr.st_mode = child->get_mode(); + e.attr.st_nlink = child->is_dir() ? 2 : 1; + + child->inc_lookup(); + + fuse_reply_entry(req, &e); +} + +static void memfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto *inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + struct stat stbuf; + inode_data->get_attr(&stbuf); + stbuf.st_ino = actual_ino; // Ensure the correct inode number is set + + fuse_reply_attr(req, &stbuf, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + if (parentInode->find_child(name)) { + fuse_reply_err(req, EEXIST); + return; + } + + Inode *new_inode = Inodes.create(name, false, mode); + if (!new_inode) { + fuse_reply_err(req, EIO); + return; + } + + // Create a new Dentry and add it to the parent + Dentry *new_dentry = new Dentry(name, new_inode); + + //std::cout << "Debug: Created new Dentry at address " + // << (void *)new_dentry << ", name: '" << name + // << "', inode address: " << (void *)new_inode << std::endl; + + parentInode->add_child(name, new_dentry); + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + + fi->fh = e.ino; + fuse_reply_create(req, &e, fi); +} + +static void memfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t offset, + [[maybe_unused]] struct fuse_file_info *fi) +{ + Inode *inode = Inodes.find(ino); + if (!inode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (inode->is_dir()) { + fuse_reply_err(req, EISDIR); + return; + } + + inode->write_content(buf, size, offset); + fuse_reply_write(req, size); +} + +static void memfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, [[maybe_unused]] struct fuse_file_info *fi) +{ + Inode *inode = Inodes.find(ino); + if (!inode || inode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + inode->lock(); + + if (offset >= inode->content_size()) { + fuse_reply_buf(req, nullptr, 0); + inode->unlock(); + return; + } + + std::vector content( + std::min(size, inode->content_size() - (size_t)offset)); + inode->read_content(content.data(), content.size(), offset); + + inode->unlock(); + + fuse_reply_buf(req, content.data(), content.size()); +} + +static void memfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *inode_data = Inodes.find(ino); + if (!inode_data || inode_data->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + // Use the inode number as the file handle + fi->fh = ino; + fuse_reply_open(req, fi); +} + +static void memfs_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *inode = Inodes.find(ino); + if (!inode || !inode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + // Create a new DirHandle + auto dir_handle = new DirHandle(inode->get_children()); + + // Store the pointer to the DirHandle in fi->fh + fi->fh = reinterpret_cast(dir_handle); + + fuse_reply_open(req, fi); +} + +static void memfs_readdir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + if (!dir_handle) { + fuse_reply_err(req, EBADF); + return; + } + + char *buffer = new char[size]; + size_t buf_size = 0; + + for (off_t i = offset; + i < static_cast(dir_handle->entries.size()); ++i) { + const auto &entry = dir_handle->entries[i]; + const std::string &name = entry.first; + const Dentry *dentry = entry.second; + + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = dentry->get_inode()->get_ino(); + dentry->get_inode()->get_attr(&stbuf); + + size_t entry_size = fuse_add_direntry(req, nullptr, 0, + name.c_str(), nullptr, 0); + if (buf_size + entry_size > size) { + break; + } + + fuse_add_direntry(req, buffer + buf_size, size - buf_size, + name.c_str(), &stbuf, i + 1); + buf_size += entry_size; + } + + fuse_reply_buf(req, buffer, buf_size); + delete[] buffer; +} + +static void memfs_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + // No need to remove file handle + (void)fi; + (void)ino; + fuse_reply_err(req, 0); +} + +static void memfs_releasedir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + delete dir_handle; + fuse_reply_err(req, 0); +} + +static void memfs_panic(std::string_view message) +{ + std::cerr << "MEMFS PANIC: " << message << std::endl; + std::abort(); +} + +static void memfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + Inodes.lock(); + Inode *inode = Inodes.find_locked(ino); + uint64_t res; + if (inode) { + res = inode->dec_lookup(nlookup); + if (res == 0) + Inodes.erase_locked(inode); + } + Inodes.unlock(); + fuse_reply_none(req); +} + +static void memfs_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + for (size_t i = 0; i < count; i++) { + fuse_ino_t ino = forgets[i].ino; + uint64_t nlookup = forgets[i].nlookup; + auto *inode_data = Inodes.find(ino); + if (inode_data) { + inode_data->dec_lookup(nlookup); + } + } + fuse_reply_none(req); +} + +static void memfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto *inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + inode_data->lock(); + + if (to_set & FUSE_SET_ATTR_MODE) + inode_data->set_mode(attr->st_mode); + if (to_set & FUSE_SET_ATTR_UID) + inode_data->set_uid(attr->st_uid); + if (to_set & FUSE_SET_ATTR_GID) + inode_data->set_gid(attr->st_gid); + if (to_set & FUSE_SET_ATTR_SIZE) + inode_data->truncate(attr->st_size); + if (to_set & FUSE_SET_ATTR_ATIME) + inode_data->set_atime(attr->st_atim); + if (to_set & FUSE_SET_ATTR_MTIME) + inode_data->set_mtime(attr->st_mtim); + + struct stat st; + inode_data->get_attr(&st); + inode_data->unlock(); + + fuse_reply_attr(req, &st, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + int error = 0; + Inode *parentInode = nullptr; + Inode *new_inode = nullptr; + Dentry *new_dentry = nullptr; + struct fuse_entry_param e; + + parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + error = ENOENT; + goto out; + } + + new_inode = Inodes.create(name, true, mode | S_IFDIR); + if (!new_inode) { + error = EIO; + goto out; + } + + new_dentry = new Dentry(name, new_inode); + error = parentInode->add_child(name, new_dentry); + if (error != 0) { + goto out_cleanup; + } + + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + +out: + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } + return; + +out_cleanup: + delete new_dentry; + Inodes.erase_locked(new_inode); + goto out; +} + +static void memfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || !child->is_dir() || !child->is_empty()) { + parentInode->unlock(); + fuse_reply_err(req, child ? (child->is_empty() ? ENOTDIR : + ENOTEMPTY) : + ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); // This should handle removal if nlink reaches 0 + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto *parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || child->is_dir()) { + parentInode->unlock(); + fuse_reply_err(req, child ? EISDIR : ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + int error = 0; + Inode *parentInode = nullptr; + Inode *newparentInode = nullptr; + Dentry *child_dentry = nullptr; + Dentry *child_dentry_copy = nullptr; + Dentry *existing_dentry = nullptr; + + if (flags & (RENAME_EXCHANGE | RENAME_NOREPLACE)) { + fuse_reply_err(req, EINVAL); + return; + } + + Inodes.lock(); + + parentInode = Inodes.find_locked(parent); + newparentInode = Inodes.find_locked(newparent); + if (!parentInode || !parentInode->is_dir() || !newparentInode || + !newparentInode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + parentInode->lock(); + if (parent != newparent) { + newparentInode->lock(); + } + + child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + error = ENOENT; + goto out_unlock; + } + + existing_dentry = newparentInode->find_child_locked(newname); + if (existing_dentry) { + if (existing_dentry->is_dir()) { + if (!existing_dentry->get_inode()->is_empty()) { + error = ENOTEMPTY; + goto out_unlock; + } + newparentInode->dec_nlink(); + } + newparentInode->remove_child(newname); + existing_dentry->get_inode()->dec_nlink(); + } + + child_dentry_copy = new Dentry(newname, child_dentry->get_inode()); + parentInode->remove_child(name); + newparentInode->add_child_locked(newname, child_dentry_copy); + +out_unlock: + parentInode->unlock(); + if (parent != newparent) { + newparentInode->unlock(); + } + +out_unlock_global: + Inodes.unlock(); + fuse_reply_err(req, error); +} + +static void memfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + int error = 0; + Inode *src_inode = nullptr; + Inode *parent_inode = nullptr; + struct fuse_entry_param e; + std::unique_ptr new_dentry; + + Inodes.lock(); + + src_inode = Inodes.find(ino); + if (!src_inode) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode = Inodes.find(newparent); + if (!parent_inode || !parent_inode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode->lock(); + + // Check if the new name already exists in the parent directory + if (parent_inode->find_child_locked(newname) != nullptr) { + error = EEXIST; + goto out_unlock_parent; + } + + src_inode->inc_nlink(); + + new_dentry = std::make_unique(newname, src_inode); + parent_inode->add_child(newname, new_dentry.get()); + + memset(&e, 0, sizeof(e)); + e.ino = ino; + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + src_inode->get_attr(&e.attr); + +out_unlock_parent: + parent_inode->unlock(); + +out_unlock_global: + Inodes.unlock(); + + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } +} + +static void memfs_statfs(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino) +{ + struct statvfs stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + + stbuf.f_bsize = 4096; + stbuf.f_frsize = 4096; + stbuf.f_namemax = PATH_MAX; // Maximum filename length + + stbuf.f_files = Inodes.size(); // Total inodes (files + directories) + + stbuf.f_ffree = std::numeric_limits::max() - + stbuf.f_files; // Free inodes + + // Set total and free blocks + // For simplicity, we'll set a fixed total number of blocks and calculate free blocks based on used inodes + stbuf.f_blocks = 1000000; // arbitrary number, needs to be a parameter + stbuf.f_bfree = stbuf.f_blocks - + (stbuf.f_files * + 10); // Assume each file uses 10 blocks on average + stbuf.f_bavail = stbuf.f_bfree; + + stbuf.f_fsid = 0; + + // Set flags + stbuf.f_flag = ST_NOSUID; + + fuse_reply_statfs(req, &stbuf); +} + +static const struct fuse_lowlevel_ops memfs_oper = { + .init = nullptr, + .destroy = nullptr, + .lookup = memfs_lookup, + .forget = memfs_forget, + .getattr = memfs_getattr, + .setattr = memfs_setattr, + .readlink = nullptr, + .mknod = nullptr, + .mkdir = memfs_mkdir, + .unlink = memfs_unlink, + .rmdir = memfs_rmdir, + .symlink = nullptr, + .rename = memfs_rename, + .link = memfs_link, + .open = memfs_open, + .read = memfs_read, + .write = memfs_write, + .flush = nullptr, + .release = memfs_release, + .fsync = nullptr, + .opendir = memfs_opendir, + .readdir = memfs_readdir, + .releasedir = memfs_releasedir, + .fsyncdir = nullptr, + .statfs = memfs_statfs, + .setxattr = nullptr, + .getxattr = nullptr, + .listxattr = nullptr, + .removexattr = nullptr, + .access = nullptr, + .create = memfs_create, + .getlk = nullptr, + .setlk = nullptr, + .bmap = nullptr, + .ioctl = nullptr, + .poll = nullptr, + .write_buf = nullptr, + .retrieve_reply = nullptr, + .forget_multi = memfs_forget_multi, + .flock = nullptr, + .fallocate = nullptr, + .readdirplus = nullptr, + .copy_file_range = nullptr, + .lseek = nullptr, + .tmpfile = nullptr, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + int ret = -1; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + + if (config == NULL) { + std::cerr << "fuse_loop_cfg_create failed" << std::endl; + exit(EXIT_FAILURE); + } + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + printf("File-system specific options:\n" + " -o opt,[opt...] mount options\n" + " -h --help print help\n" + "\n"); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if (opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + se = fuse_session_new(&args, &memfs_oper, sizeof(memfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + ret = fuse_session_loop_mt(se, config); + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/meson.build b/example/meson.build new file mode 100644 index 0000000..b2e896c --- /dev/null +++ b/example/meson.build @@ -0,0 +1,44 @@ +examples = [ 'passthrough', 'passthrough_fh', + 'hello', 'hello_ll', + 'printcap', 'ioctl_client', 'poll_client', + 'ioctl', 'cuse', 'cuse_client' ] + +if not platform.endswith('bsd') and platform != 'dragonfly' + examples += [ 'passthrough_ll', 'hello_ll_uds' ] + + # According to Conrad Meyer , FreeBSD doesn't + # support mounting files, This is enforced in vfs_domount_first() + # with the v_type != VDIR check. + examples += [ 'null' ] +endif + +threaded_examples = [ 'notify_inval_inode', + 'invalidate_path', + 'notify_store_retrieve', + 'notify_inval_entry', + 'poll' ] + +foreach ex : examples + executable(ex, ex + '.c', + dependencies: [ libfuse_dep ], + install: false) +endforeach + + +foreach ex : threaded_examples + executable(ex, ex + '.c', + dependencies: [ thread_dep, libfuse_dep ], + install: false) +endforeach + +if not platform.endswith('bsd') and platform != 'dragonfly' and add_languages('cpp', required : false) + executable('passthrough_hp', 'passthrough_hp.cc', + dependencies: [ thread_dep, libfuse_dep ], + install: false) + executable('memfs_ll', 'memfs_ll.cc', + dependencies: [ thread_dep, libfuse_dep ], + cpp_args : '-std=c++17', + install: false) +endif + +# TODO: Link passthrough_fh with ulockmgr if available diff --git a/example/notify_inval_entry.c b/example/notify_inval_entry.c new file mode 100644 index 0000000..1060d50 --- /dev/null +++ b/example/notify_inval_entry.c @@ -0,0 +1,400 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * file name changes dynamically to reflect the current time. + * + * It illustrates the use of the fuse_lowlevel_notify_inval_entry() and + * fuse_lowlevel_notify_expire_entry() functions. + * + * To see the effect, first start the file system with the + * ``--no-notify`` + * + * $ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/ + * + * Observe that `ls` always prints the correct directory contents + * (since `readdir` output is not cached):: + * + * $ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt + * Time_is_15h_48m_33s current_time + * Time_is_15h_48m_34s current_time + * Time_is_15h_48m_35s current_time + * + * However, if you try to access a file by name the kernel will + * report that it still exists: + * + * $ file=$(ls mnt/); echo $file + * Time_is_15h_50m_09s + * $ sleep 5; stat mnt/$file + * File: ‘mnt/Time_is_15h_50m_09s’ + * Size: 32 Blocks: 0 IO Block: 4096 regular file + * Device: 2ah/42d Inode: 3 Links: 1 + * Access: (0444/-r--r--r--) Uid: ( 0/ root) Gid: ( 0/ root) + * Access: 1969-12-31 16:00:00.000000000 -0800 + * Modify: 1969-12-31 16:00:00.000000000 -0800 + * Change: 1969-12-31 16:00:00.000000000 -0800 + * Birth: - + * + * Only once the kernel cache timeout has been reached will the stat + * call fail: + * + * $ sleep 30; stat mnt/$file + * stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory + * + * In contrast, if you enable notifications you will be unable to stat + * the file as soon as the file system updates its name: + * + * $ notify_inval_entry --update-interval=1 --timeout=30 mnt/ + * $ file=$(ls mnt/); stat mnt/$file + * File: ‘mnt/Time_is_20h_42m_11s’ + * Size: 0 Blocks: 0 IO Block: 4096 regular empty file + * Device: 2ah/42d Inode: 2 Links: 1 + * Access: (0000/----------) Uid: ( 0/ root) Gid: ( 0/ root) + * Access: 1969-12-31 16:00:00.000000000 -0800 + * Modify: 1969-12-31 16:00:00.000000000 -0800 + * Change: 1969-12-31 16:00:00.000000000 -0800 + * Birth: - + * $ sleep 1; stat mnt/$file + * stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory + * + * To use the function fuse_lowlevel_notify_expire_entry() instead of + * fuse_lowlevel_notify_inval_entry(), use the command line option --only-expire + * + * ## Compilation ## + * + * gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry + * + * ## Source code ## + * \include notify_inval_entry.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_STR_LEN 128 +static char file_name[MAX_STR_LEN]; +static fuse_ino_t file_ino = 2; +static int lookup_cnt = 0; +static pthread_t main_thread; + +/* Command line parsing */ +struct options { + int no_notify; + float timeout; + int update_interval; + int only_expire; +}; +static struct options options = { + .timeout = 5, + .no_notify = 0, + .update_interval = 1, + .only_expire = 0, +}; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + OPTION("--timeout=%f", timeout), + OPTION("--only-expire", only_expire), + FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == file_ino) { + stbuf->st_mode = S_IFREG | 0000; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) { + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, file_name) == 0) { + e.ino = file_ino; + lookup_cnt++; + } else + goto err_out; + + e.attr_timeout = options.timeout; + e.entry_timeout = options.timeout; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget (fuse_req_t req, fuse_ino_t ino, + uint64_t nlookup) { + (void) req; + if(ino == file_ino) + lookup_cnt -= nlookup; + else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, options.timeout); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) { + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) { + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, file_name, file_ino); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .forget = tfs_forget, +}; + +static void update_fs(void) { + time_t t; + struct tm *now; + ssize_t ret; + + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + ret = strftime(file_name, MAX_STR_LEN, + "Time_is_%Hh_%Mm_%Ss", now); + assert(ret != 0); +} + +static void* update_fs_loop(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + char *old_name; + + + while(!fuse_session_exited(se)) { + old_name = strdup(file_name); + update_fs(); + + if (!options.no_notify && lookup_cnt) { + if(options.only_expire) { // expire entry + int ret = fuse_lowlevel_notify_expire_entry + (se, FUSE_ROOT_ID, old_name, strlen(old_name)); + + // no kernel support + if (ret == -ENOSYS) { + printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n"); + printf("Exiting...\n"); + + fuse_session_exit(se); + // Make sure to exit now, rather than on next request from userspace + pthread_kill(main_thread, SIGPIPE); + + break; + } + // 1) ret == 0: successful expire of an existing entry + // 2) ret == -ENOENT: kernel has already expired the entry / + // entry does not exist anymore in the kernel + assert(ret == 0 || ret == -ENOENT); + } else { // invalidate entry + assert(fuse_lowlevel_notify_inval_entry + (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0); + } + } + free(old_name); + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --timeout= Timeout for kernel caches\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + " --only-expire Expire entries instead of invalidating them\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + pthread_t updater; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), &se); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + // Needed to ensure that the main thread continues/restarts processing as soon + // as the fuse session ends (immediately after calling fuse_session_exit() ) + // and not only on the next request from userspace + main_thread = pthread_self(); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) { + ret = fuse_session_loop(se); + } else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/notify_inval_inode.c b/example/notify_inval_inode.c new file mode 100644 index 0000000..99631cc --- /dev/null +++ b/example/notify_inval_inode.c @@ -0,0 +1,402 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * contents change dynamically: it always contains the current time. + * + * While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to + * actively push the updated data into the kernel cache, this example + * uses fuse_lowlevel_notify_inval_inode() to notify the kernel that + * the cache has to be invalidated - but the kernel still has to + * explicitly request the updated data on the next read. + * + * To see the effect, first start the file system with the + * ``--no-notify`` option: + * + * $ notify_inval_inode --update-interval=1 --no-notify mnt/ + * + * Observe that the output never changes, even though the file system + * updates it once per second. This is because the contents are cached + * in the kernel: + * + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * + * If you instead enable the notification functions, the changes become + * visible: + * + * $ notify_inval_inode --update-interval=1 mnt/ + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:40 + * The current time is 15:58:41 + * The current time is 15:58:42 + * The current time is 15:58:43 + * The current time is 15:58:44 + * + * ## Compilation ## + * + * gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode + * + * ## Source code ## + * \include notify_inval_inode.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define FILE_INO 2 +#define FILE_NAME "current_time" +static char file_contents[MAX_STR_LEN]; +static int lookup_cnt = 0; +static size_t file_size; +static _Atomic bool is_stop = false; + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = file_size; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) { + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_destroy(void *userarg) +{ + (void)userarg; + + is_stop = true; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) { + e.ino = FILE_INO; + lookup_cnt++; + } else + goto err_out; + + e.attr_timeout = NO_TIMEOUT; + e.entry_timeout = NO_TIMEOUT; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget (fuse_req_t req, fuse_ino_t ino, + uint64_t nlookup) { + (void) req; + if(ino == FILE_INO) + lookup_cnt -= nlookup; + else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, NO_TIMEOUT); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) { + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) { + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, FILE_NAME, FILE_INO); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else if (ino == FILE_INO) + fuse_reply_open(req, fi); + else { + // This should not happen + fprintf(stderr, "Got open for non-existing inode!\n"); + fuse_reply_err(req, ENOENT); + } +} + +static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + assert(ino == FILE_INO); + reply_buf_limited(req, file_contents, file_size, off, size); +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .destroy = tfs_destroy, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .open = tfs_open, + .read = tfs_read, + .forget = tfs_forget, +}; + +static void update_fs(void) { + struct tm *now; + time_t t; + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + file_size = strftime(file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(file_size != 0); +} + +static void* update_fs_loop(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + + while(!is_stop) { + update_fs(); + if (!options.no_notify && lookup_cnt) { + /* Only send notification if the kernel is aware of the inode */ + + /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they + * might come up during umount, when kernel side already releases + * all inodes, but does not send FUSE_DESTROY yet. + */ + int ret = + fuse_lowlevel_notify_inval_inode(se, FILE_INO, 0, 0); + if ((ret != 0 && !is_stop) && + ret != -ENOENT && ret != -EBADF && ret != -ENODEV) { + fprintf(stderr, + "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n", + strerror(-ret), -ret); + abort(); + } + } + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + pthread_t updater; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) { + ret = 1; + goto err_out1; + } + + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + fuse_opt_free_args(&args); + free(opts.mountpoint); + + return ret ? 1 : 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/notify_store_retrieve.c b/example/notify_store_retrieve.c new file mode 100644 index 0000000..42e6eb0 --- /dev/null +++ b/example/notify_store_retrieve.c @@ -0,0 +1,475 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * contents change dynamically: it always contains the current time. + * + * While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() + * to let the kernel know that it has to invalidate the cache, this + * example actively pushes the updated data into the kernel cache + * using fuse_lowlevel_notify_store(). + * + * To see the effect, first start the file system with the + * ``--no-notify`` option: + * + * $ notify_store_retrieve --update-interval=1 --no-notify mnt/ + * + * Observe that the output never changes, even though the file system + * updates it once per second. This is because the contents are cached + * in the kernel: + * + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * + * If you instead enable the notification functions, the changes become + * visible: + * + * $ notify_store_retrieve --update-interval=1 mnt/ + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:40 + * The current time is 15:58:41 + * The current time is 15:58:42 + * The current time is 15:58:43 + * The current time is 15:58:44 + * + * ## Compilation ## + * + * gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve + * + * ## Source code ## + * \include notify_store_retrieve.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define FILE_INO 2 +#define FILE_NAME "current_time" +static char file_contents[MAX_STR_LEN]; +static int lookup_cnt = 0; +static int open_cnt = 0; +static size_t file_size; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +/* Keep track if we ever stored data (==1), and + received it back correctly (==2) */ +static int retrieve_status = 0; + +static bool is_umount = false; + +/* updater thread tid */ +static pthread_t updater; + + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = file_size; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) { + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) { + e.ino = FILE_INO; + } else + goto err_out; + + e.attr_timeout = NO_TIMEOUT; + e.entry_timeout = NO_TIMEOUT; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + + /* + * must only be set when the kernel knows about the entry, + * otherwise update_fs_loop() might see a positive count, but kernel + * would not have the entry yet + */ + if (e.ino == FILE_INO) { + pthread_mutex_lock(&lock); + lookup_cnt++; + pthread_mutex_unlock(&lock); + } + + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget (fuse_req_t req, fuse_ino_t ino, + uint64_t nlookup) { + (void) req; + if(ino == FILE_INO) { + pthread_mutex_lock(&lock); + lookup_cnt -= nlookup; + pthread_mutex_unlock(&lock); + } else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, NO_TIMEOUT); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) { + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) { + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, FILE_NAME, FILE_INO); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else if (ino == FILE_INO) { + fuse_reply_open(req, fi); + pthread_mutex_lock(&lock); + open_cnt++; + pthread_mutex_unlock(&lock); + } else { + // This should not happen + fprintf(stderr, "Got open for non-existing inode!\n"); + fuse_reply_err(req, ENOENT); + } +} + +static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) { + (void) fi; + + assert(ino == FILE_INO); + reply_buf_limited(req, file_contents, file_size, off, size); +} + +static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *data) { + struct fuse_bufvec bufv; + char buf[MAX_STR_LEN]; + char *expected; + ssize_t ret; + + assert(ino == FILE_INO); + assert(offset == 0); + expected = (char*) cookie; + + bufv.count = 1; + bufv.idx = 0; + bufv.off = 0; + bufv.buf[0].size = MAX_STR_LEN; + bufv.buf[0].mem = buf; + bufv.buf[0].flags = 0; + + ret = fuse_buf_copy(&bufv, data, 0); + assert(ret > 0); + assert(strncmp(buf, expected, ret) == 0); + free(expected); + retrieve_status = 2; + fuse_reply_none(req); +} + +static void tfs_destroy(void *userdata) +{ + (void)userdata; + + is_umount = true; + + pthread_join(updater, NULL); +} + + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .open = tfs_open, + .read = tfs_read, + .forget = tfs_forget, + .retrieve_reply = tfs_retrieve_reply, + .destroy = tfs_destroy, +}; + +static void update_fs(void) { + struct tm *now; + time_t t; + t = time(NULL); + now = localtime(&t); + assert(now != NULL); + + file_size = strftime(file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(file_size != 0); +} + +static void* update_fs_loop(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + struct fuse_bufvec bufv; + int ret; + + while(!is_umount) { + update_fs(); + pthread_mutex_lock(&lock); + if (!options.no_notify && open_cnt && lookup_cnt) { + /* Only send notification if the kernel + is aware of the inode */ + bufv.count = 1; + bufv.idx = 0; + bufv.off = 0; + bufv.buf[0].size = file_size; + bufv.buf[0].mem = file_contents; + bufv.buf[0].flags = 0; + + /* + * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they + * might come up during umount, when kernel side already releases + * all inodes, but does not send FUSE_DESTROY yet. + */ + + ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0); + if ((ret != 0 && !is_umount) && + ret != -ENOENT && ret != -EBADF && ret != -ENODEV) { + fprintf(stderr, + "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n", + strerror(-ret), -ret); + abort(); + } + + /* To make sure that everything worked correctly, ask the + kernel to send us back the stored data */ + ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN, + 0, (void*) strdup(file_contents)); + assert((ret == 0 || is_umount) || ret == -ENOENT || ret == -EBADF || + ret != -ENODEV); + if(retrieve_status == 0) + retrieve_status = 1; + } + pthread_mutex_unlock(&lock); + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + assert(retrieve_status != 1); + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/null.c b/example/null.c new file mode 100644 index 0000000..2dd5e8b --- /dev/null +++ b/example/null.c @@ -0,0 +1,143 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This "filesystem" provides only a single file. The mountpoint + * needs to be a file rather than a directory. All writes to the + * file will be discarded, and reading the file always returns + * \0. + * + * Compile with: + * + * gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null + * + * ## Source code ## + * \include passthrough_fh.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include + +static int null_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + stbuf->st_mode = S_IFREG | 0644; + stbuf->st_nlink = 1; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + stbuf->st_size = (1ULL << 32); /* 4G */ + stbuf->st_blocks = 0; + stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL); + + return 0; +} + +static int null_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + (void) size; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return 0; +} + +static int null_open(const char *path, struct fuse_file_info *fi) +{ + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return 0; +} + +static int null_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) buf; + (void) offset; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + if (offset >= (1ULL << 32)) + return 0; + + memset(buf, 0, size); + return size; +} + +static int null_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) buf; + (void) offset; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return size; +} + +static const struct fuse_operations null_oper = { + .getattr = null_getattr, + .truncate = null_truncate, + .open = null_open, + .read = null_read, + .write = null_write, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_cmdline_opts opts; + struct stat stbuf; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + fuse_opt_free_args(&args); + + if (!opts.mountpoint) { + fprintf(stderr, "missing mountpoint parameter\n"); + return 1; + } + + if (stat(opts.mountpoint, &stbuf) == -1) { + fprintf(stderr ,"failed to access mountpoint %s: %s\n", + opts.mountpoint, strerror(errno)); + free(opts.mountpoint); + return 1; + } + free(opts.mountpoint); + if (!S_ISREG(stbuf.st_mode)) { + fprintf(stderr, "mountpoint is not a regular file\n"); + return 1; + } + + return fuse_main(argc, argv, &null_oper, NULL); +} diff --git a/example/passthrough.c b/example/passthrough.c new file mode 100644 index 0000000..cc93f53 --- /dev/null +++ b/example/passthrough.c @@ -0,0 +1,588 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2011 Sebastian Pipping + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. Its performance is terrible. + * + * Compile with + * + * gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough + * + * ## Source code ## + * \include passthrough.c + */ + + +#define FUSE_USE_VERSION 31 + +#define _GNU_SOURCE + +#ifdef linux +/* For pread()/pwrite()/utimensat() */ +#define _XOPEN_SOURCE 700 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __FreeBSD__ +#include +#include +#endif +#include +#ifdef HAVE_SETXATTR +#include +#endif + +#include "passthrough_helpers.h" + +static int fill_dir_plus = 0; + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->use_ino = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need either set cfg->direct_io + in current function (recommended in high level API) or set fi->direct_io + in xmp_create() or xmp_open(). */ + // cfg->direct_io = 1; + cfg->parallel_direct_writes = 1; + + /* Pick up changes from lower filesystem right away. This is + also necessary for better hardlink support. When the kernel + calls the unlink() handler, it does not know the inode of + the to-be-removed entry and can therefore not invalidate + the cache of the associated inode - resulting in an + incorrect st_nlink value being reported for any remaining + hardlinks to this inode. */ + if (!cfg->auto_cache) { + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + } + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_access(const char *path, int mask) +{ + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_readlink(const char *path, char *buf, size_t size) +{ + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + DIR *dp; + struct dirent *de; + + (void) offset; + (void) fi; + (void) flags; + + dp = opendir(path); + if (dp == NULL) + return -errno; + + while ((de = readdir(dp)) != NULL) { + struct stat st; + if (fill_dir_plus) { + fstatat(dirfd(dp), de->d_name, &st, + AT_SYMLINK_NOFOLLOW); + } else { + memset(&st, 0, sizeof(st)); + st.st_ino = de->d_ino; + st.st_mode = de->d_type << 12; + } + if (filler(buf, de->d_name, &st, 0, fill_dir_plus)) + break; + } + + closedir(dp); + return 0; +} + +static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) +{ + int res; + + res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_mkdir(const char *path, mode_t mode) +{ + int res; + + res = mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rmdir(const char *path) +{ + int res; + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_symlink(const char *from, const char *to) +{ + int res; + + res = symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + if (flags) + return -EINVAL; + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_link(const char *from, const char *to) +{ + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + int res; + + if (fi != NULL) + res = ftruncate(fi->fh, size); + else + res = truncate(path, size); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +static int xmp_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + /* don't use utime/utimes since they follow symlinks */ + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static int xmp_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int res; + + res = open(path, fi->flags, mode); + if (res == -1) + return -errno; + + fi->fh = res; + return 0; +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) +{ + int res; + + res = open(path, fi->flags); + if (res == -1) + return -errno; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file). */ + if (fi->flags & O_DIRECT) { + fi->direct_io = 1; + fi->parallel_direct_writes = 1; + } + + fi->fh = res; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int fd; + int res; + + if(fi == NULL) + fd = open(path, O_RDONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = pread(fd, buf, size, offset); + if (res == -1) + res = -errno; + + if(fi == NULL) + close(fd); + return res; +} + +static int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + int fd; + int res; + + (void) fi; + if(fi == NULL) + fd = open(path, O_WRONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = pwrite(fd, buf, size, offset); + if (res == -1) + res = -errno; + + if(fi == NULL) + close(fd); + return res; +} + +static int xmp_statfs(const char *path, struct statvfs *stbuf) +{ + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + close(fi->fh); + return 0; +} + +static int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + /* Just a stub. This method is optional and can safely be left + unimplemented */ + + (void) path; + (void) isdatasync; + (void) fi; + return 0; +} + +#ifdef HAVE_POSIX_FALLOCATE +static int xmp_fallocate(const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + int fd; + int res; + + (void) fi; + + if (mode) + return -EOPNOTSUPP; + + if(fi == NULL) + fd = open(path, O_WRONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = -posix_fallocate(fd, offset, length); + + if(fi == NULL) + close(fd); + return res; +} +#endif + +#ifdef HAVE_SETXATTR +/* xattr operations are optional and can safely be left unimplemented */ +static int xmp_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) +{ + int res = lsetxattr(path, name, value, size, flags); + if (res == -1) + return -errno; + return 0; +} + +static int xmp_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + int res = lgetxattr(path, name, value, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_listxattr(const char *path, char *list, size_t size) +{ + int res = llistxattr(path, list, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_removexattr(const char *path, const char *name) +{ + int res = lremovexattr(path, name); + if (res == -1) + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + +#ifdef HAVE_COPY_FILE_RANGE +static ssize_t xmp_copy_file_range(const char *path_in, + struct fuse_file_info *fi_in, + off_t offset_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t offset_out, size_t len, int flags) +{ + int fd_in, fd_out; + ssize_t res; + + if(fi_in == NULL) + fd_in = open(path_in, O_RDONLY); + else + fd_in = fi_in->fh; + + if (fd_in == -1) + return -errno; + + if(fi_out == NULL) + fd_out = open(path_out, O_WRONLY); + else + fd_out = fi_out->fh; + + if (fd_out == -1) { + close(fd_in); + return -errno; + } + + res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len, + flags); + if (res == -1) + res = -errno; + + if (fi_out == NULL) + close(fd_out); + if (fi_in == NULL) + close(fd_in); + + return res; +} +#endif + +static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi) +{ + int fd; + off_t res; + + if (fi == NULL) + fd = open(path, O_RDONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = lseek(fd, off, whence); + if (res == -1) + res = -errno; + + if (fi == NULL) + close(fd); + return res; +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .readdir = xmp_readdir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .open = xmp_open, + .create = xmp_create, + .read = xmp_read, + .write = xmp_write, + .statfs = xmp_statfs, + .release = xmp_release, + .fsync = xmp_fsync, +#ifdef HAVE_POSIX_FALLOCATE + .fallocate = xmp_fallocate, +#endif +#ifdef HAVE_SETXATTR + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +#endif +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = xmp_copy_file_range, +#endif + .lseek = xmp_lseek, +}; + +int main(int argc, char *argv[]) +{ + enum { MAX_ARGS = 10 }; + int i,new_argc; + char *new_argv[MAX_ARGS]; + + umask(0); + /* Process the "--plus" option apart */ + for (i=0, new_argc=0; (i + Copyright (C) 2011 Sebastian Pipping + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. This implementation is a little more sophisticated + * than the one in passthrough.c, so performance is not quite as bad. + * + * Compile with: + * + * gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh + * + * ## Source code ## + * \include passthrough_fh.c + */ + +#define FUSE_USE_VERSION 31 + +#define _GNU_SOURCE + +#include + +#ifdef HAVE_LIBULOCKMGR +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SETXATTR +#include +#endif +#include /* flock(2) */ + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->use_ino = 1; + cfg->nullpath_ok = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need either set cfg->direct_io + in current function (recommended in high level API) or set fi->direct_io + in xmp_create() or xmp_open(). */ + // cfg->direct_io = 1; + cfg->parallel_direct_writes = 1; + + /* Pick up changes from lower filesystem right away. This is + also necessary for better hardlink support. When the kernel + calls the unlink() handler, it does not know the inode of + the to-be-removed entry and can therefore not invalidate + the cache of the associated inode - resulting in an + incorrect st_nlink value being reported for any remaining + hardlinks to this inode. */ + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + + if(fi) + res = fstat(fi->fh, stbuf); + else + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_access(const char *path, int mask) +{ + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_readlink(const char *path, char *buf, size_t size) +{ + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + +struct xmp_dirp { + DIR *dp; + struct dirent *entry; + off_t offset; +}; + +static int xmp_opendir(const char *path, struct fuse_file_info *fi) +{ + int res; + struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp)); + if (d == NULL) + return -ENOMEM; + + d->dp = opendir(path); + if (d->dp == NULL) { + res = -errno; + free(d); + return res; + } + d->offset = 0; + d->entry = NULL; + + fi->fh = (unsigned long) d; + return 0; +} + +static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi) +{ + return (struct xmp_dirp *) (uintptr_t) fi->fh; +} + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct xmp_dirp *d = get_dirp(fi); + + (void) path; + if (offset != d->offset) { +#ifndef __FreeBSD__ + seekdir(d->dp, offset); +#else + /* Subtract the one that we add when calling + telldir() below */ + seekdir(d->dp, offset-1); +#endif + d->entry = NULL; + d->offset = offset; + } + while (1) { + struct stat st; + off_t nextoff; + enum fuse_fill_dir_flags fill_flags = FUSE_FILL_DIR_DEFAULTS; + + if (!d->entry) { + d->entry = readdir(d->dp); + if (!d->entry) + break; + } +#ifdef HAVE_FSTATAT + if (flags & FUSE_READDIR_PLUS) { + int res; + + res = fstatat(dirfd(d->dp), d->entry->d_name, &st, + AT_SYMLINK_NOFOLLOW); + if (res != -1) + fill_flags |= FUSE_FILL_DIR_PLUS; + } +#endif + if (!(fill_flags & FUSE_FILL_DIR_PLUS)) { + memset(&st, 0, sizeof(st)); + st.st_ino = d->entry->d_ino; + st.st_mode = d->entry->d_type << 12; + } + nextoff = telldir(d->dp); +#ifdef __FreeBSD__ + /* Under FreeBSD, telldir() may return 0 the first time + it is called. But for libfuse, an offset of zero + means that offsets are not supported, so we shift + everything by one. */ + nextoff++; +#endif + if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags)) + break; + + d->entry = NULL; + d->offset = nextoff; + } + + return 0; +} + +static int xmp_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct xmp_dirp *d = get_dirp(fi); + (void) path; + closedir(d->dp); + free(d); + return 0; +} + +static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) +{ + int res; + + if (S_ISFIFO(mode)) + res = mkfifo(path, mode); + else + res = mknod(path, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_mkdir(const char *path, mode_t mode) +{ + int res; + + res = mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rmdir(const char *path) +{ + int res; + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_symlink(const char *from, const char *to) +{ + int res; + + res = symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + /* When we have renameat2() in libc, then we can implement flags */ + if (flags) + return -EINVAL; + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_link(const char *from, const char *to) +{ + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int res; + + if(fi) + res = fchmod(fi->fh, mode); + else + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + int res; + + if (fi) + res = fchown(fi->fh, uid, gid); + else + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + int res; + + if(fi) + res = ftruncate(fi->fh, size); + else + res = truncate(path, size); + + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +static int xmp_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + int res; + + /* don't use utime/utimes since they follow symlinks */ + if (fi) + res = futimens(fi->fh, ts); + else + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags, mode); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags); + if (fd == -1) + return -errno; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file). */ + if (fi->flags & O_DIRECT) { + fi->direct_io = 1; + fi->parallel_direct_writes = 1; + } + + fi->fh = fd; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pread(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec *src; + + (void) path; + + src = malloc(sizeof(struct fuse_bufvec)); + if (src == NULL) + return -ENOMEM; + + *src = FUSE_BUFVEC_INIT(size); + + src->buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + src->buf[0].fd = fi->fh; + src->buf[0].pos = offset; + + *bufp = src; + + return 0; +} + +static int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pwrite(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf)); + + (void) path; + + dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + dst.buf[0].fd = fi->fh; + dst.buf[0].pos = offset; + + return fuse_buf_copy(&dst, buf, FUSE_BUF_SPLICE_NONBLOCK); +} + +static int xmp_statfs(const char *path, struct statvfs *stbuf) +{ + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_flush(const char *path, struct fuse_file_info *fi) +{ + int res; + + (void) path; + /* This is called from every close on an open file, so call the + close on the underlying filesystem. But since flush may be + called multiple times for an open file, this must not really + close the file. This is important if used on a network + filesystem like NFS which flush the data/metadata on close() */ + res = close(dup(fi->fh)); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + close(fi->fh); + + return 0; +} + +static int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + int res; + (void) path; + +#ifndef HAVE_FDATASYNC + (void) isdatasync; +#else + if (isdatasync) + res = fdatasync(fi->fh); + else +#endif + res = fsync(fi->fh); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_POSIX_FALLOCATE +static int xmp_fallocate(const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + (void) path; + + if (mode) + return -EOPNOTSUPP; + + return -posix_fallocate(fi->fh, offset, length); +} +#endif + +#ifdef HAVE_SETXATTR +/* xattr operations are optional and can safely be left unimplemented */ +static int xmp_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) +{ + int res = lsetxattr(path, name, value, size, flags); + if (res == -1) + return -errno; + return 0; +} + +static int xmp_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + int res = lgetxattr(path, name, value, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_listxattr(const char *path, char *list, size_t size) +{ + int res = llistxattr(path, list, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_removexattr(const char *path, const char *name) +{ + int res = lremovexattr(path, name); + if (res == -1) + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + +#ifdef HAVE_LIBULOCKMGR +static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + (void) path; + + return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner, + sizeof(fi->lock_owner)); +} +#endif + +static int xmp_flock(const char *path, struct fuse_file_info *fi, int op) +{ + int res; + (void) path; + + res = flock(fi->fh, op); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_COPY_FILE_RANGE +static ssize_t xmp_copy_file_range(const char *path_in, + struct fuse_file_info *fi_in, + off_t off_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t off_out, size_t len, int flags) +{ + ssize_t res; + (void) path_in; + (void) path_out; + + res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, + flags); + if (res == -1) + return -errno; + + return res; +} +#endif + +static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi) +{ + off_t res; + (void) path; + + res = lseek(fi->fh, off, whence); + if (res == -1) + return -errno; + + return res; +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .opendir = xmp_opendir, + .readdir = xmp_readdir, + .releasedir = xmp_releasedir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .create = xmp_create, + .open = xmp_open, + .read = xmp_read, + .read_buf = xmp_read_buf, + .write = xmp_write, + .write_buf = xmp_write_buf, + .statfs = xmp_statfs, + .flush = xmp_flush, + .release = xmp_release, + .fsync = xmp_fsync, +#ifdef HAVE_POSIX_FALLOCATE + .fallocate = xmp_fallocate, +#endif +#ifdef HAVE_SETXATTR + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +#endif +#ifdef HAVE_LIBULOCKMGR + .lock = xmp_lock, +#endif + .flock = xmp_flock, +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = xmp_copy_file_range, +#endif + .lseek = xmp_lseek, +}; + +int main(int argc, char *argv[]) +{ + umask(0); + return fuse_main(argc, argv, &xmp_oper, NULL); +} diff --git a/example/passthrough_helpers.h b/example/passthrough_helpers.h new file mode 100644 index 0000000..6b77c33 --- /dev/null +++ b/example/passthrough_helpers.h @@ -0,0 +1,76 @@ +/* + * FUSE: Filesystem in Userspace + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE + */ + +/* + * Creates files on the underlying file system in response to a FUSE_MKNOD + * operation + */ +static int mknod_wrapper(int dirfd, const char *path, const char *link, + int mode, dev_t rdev) +{ + int res; + + if (S_ISREG(mode)) { + res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode); + if (res >= 0) + res = close(res); + } else if (S_ISDIR(mode)) { + res = mkdirat(dirfd, path, mode); + } else if (S_ISLNK(mode) && link != NULL) { + res = symlinkat(link, dirfd, path); + } else if (S_ISFIFO(mode)) { + res = mkfifoat(dirfd, path, mode); +#ifdef __FreeBSD__ + } else if (S_ISSOCK(mode)) { + struct sockaddr_un su; + int fd; + + if (strlen(path) >= sizeof(su.sun_path)) { + errno = ENAMETOOLONG; + return -1; + } + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd >= 0) { + /* + * We must bind the socket to the underlying file + * system to create the socket file, even though + * we'll never listen on this socket. + */ + su.sun_family = AF_UNIX; + strncpy(su.sun_path, path, sizeof(su.sun_path)); + res = bindat(dirfd, fd, (struct sockaddr*)&su, + sizeof(su)); + if (res == 0) + close(fd); + } else { + res = -1; + } +#endif + } else { + res = mknodat(dirfd, path, mode, rdev); + } + + return res; +} diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc new file mode 100644 index 0000000..8b5214c --- /dev/null +++ b/example/passthrough_hp.cc @@ -0,0 +1,1576 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2017 Nikolaus Rath + Copyright (C) 2018 Valve, Inc + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This is a "high-performance" version of passthrough_ll.c. While + * passthrough_ll.c is designed to be as simple as possible, this + * example intended to be as efficient and correct as possible. + * + * passthrough_hp.cc mirrors a specified "source" directory under a + * specified the mountpoint with as much fidelity and performance as + * possible. + * + * If --nocache is specified, the source directory may be changed + * directly even while mounted and the filesystem will continue + * to work correctly. + * + * Without --nocache, the source directory is assumed to be modified + * only through the passthrough filesystem. This enables much better + * performance, but if changes are made directly to the source, they + * may not be immediately visible under the mountpoint and further + * access to the mountpoint may result in incorrect behavior, + * including data-loss. + * + * On its own, this filesystem fulfills no practical purpose. It is + * intended as a template upon which additional functionality can be + * built. + * + * Unless --nocache is specified, is only possible to write to files + * for which the mounting user has read permissions. This is because + * the writeback cache requires the kernel to be able to issue read + * requests for all files (which the passthrough filesystem cannot + * satisfy if it can't read the file in the underlying filesystem). + * + * ## Source code ## + * \include passthrough_hp.cc + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +// C includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// C++ includes +#include +#include +#include +#include "cxxopts.hpp" +#include +#include + +using namespace std; + +#define SFS_DEFAULT_THREADS "-1" // take libfuse value as default +#define SFS_DEFAULT_CLONE_FD "0" + +/* We are re-using pointers to our `struct sfs_inode` and `struct + sfs_dirp` elements as inodes and file handles. This means that we + must be able to store pointer a pointer in both a fuse_ino_t + variable and a uint64_t variable (used for file handles). */ +static_assert(sizeof(fuse_ino_t) >= sizeof(void*), + "void* must fit into fuse_ino_t"); +static_assert(sizeof(fuse_ino_t) >= sizeof(uint64_t), + "fuse_ino_t must be at least 64 bits"); + + +/* Forward declarations */ +struct Inode; +static Inode& get_inode(fuse_ino_t ino); +static void forget_one(fuse_ino_t ino, uint64_t n); + +// Uniquely identifies a file in the source directory tree. This could +// be simplified to just ino_t since we require the source directory +// not to contain any mountpoints. This hasn't been done yet in case +// we need to reconsider this constraint (but relaxing this would have +// the drawback that we can no longer reuse inode numbers, and thus +// readdir() would need to do a full lookup() in order to report the +// right inode number). +typedef std::pair SrcId; + +// Define a hash function for SrcId +namespace std { + template<> + struct hash { + size_t operator()(const SrcId& id) const { + return hash{}(id.first) ^ hash{}(id.second); + } + }; +} + +// Maps files in the source directory tree to inodes +typedef std::unordered_map InodeMap; + +struct Inode { + int fd {-1}; + dev_t src_dev {0}; + ino_t src_ino {0}; + int generation {0}; + int backing_id {0}; + uint64_t nopen {0}; + uint64_t nlookup {0}; + std::mutex m; + + // Delete copy constructor and assignments. We could implement + // move if we need it. + Inode() = default; + Inode(const Inode&) = delete; + Inode(Inode&& inode) = delete; + Inode& operator=(Inode&& inode) = delete; + Inode& operator=(const Inode&) = delete; + + ~Inode() { + if(fd > 0) + close(fd); + } +}; + +struct Fs { + // Must be acquired *after* any Inode.m locks. + std::mutex mutex; + InodeMap inodes; // protected by mutex + Inode root; + double timeout; + bool debug; + bool debug_fuse; + bool foreground; + std::string source; + size_t blocksize; + dev_t src_dev; + bool nosplice; + bool nocache; + size_t num_threads; + bool clone_fd; + std::string fuse_mount_options; + bool direct_io; + bool passthrough; +}; +static Fs fs{}; + + +#define FUSE_BUF_COPY_FLAGS \ + (fs.nosplice ? \ + FUSE_BUF_NO_SPLICE : \ + static_cast(FUSE_BUF_SPLICE_MOVE)) + + +static Inode& get_inode(fuse_ino_t ino) { + if (ino == FUSE_ROOT_ID) + return fs.root; + + Inode* inode = reinterpret_cast(ino); + if(inode->fd == -1) { + cerr << "INTERNAL ERROR: Unknown inode " << ino << endl; + abort(); + } + return *inode; +} + + +static int get_fs_fd(fuse_ino_t ino) { + int fd = get_inode(ino).fd; + return fd; +} + + +static void sfs_init(void *userdata, fuse_conn_info *conn) { + (void)userdata; + + if (!fuse_set_feature_flag(conn, FUSE_CAP_PASSTHROUGH)) + fs.passthrough = false; + + /* Passthrough and writeback cache are conflicting modes */ + if (fs.timeout && !fs.passthrough) + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + + fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + + if (fs.nosplice) { + // FUSE_CAP_SPLICE_READ is enabled in libfuse3 by default, + // see do_init() in in fuse_lowlevel.c + // Just unset all, in case FUSE_CAP_SPLICE_WRITE or + // FUSE_CAP_SPLICE_MOVE would also get enabled by default. + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); + } else { + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ); + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); + } + + /* This is a local file system - no network coherency needed */ + fuse_set_feature_flag(conn, FUSE_CAP_DIRECT_IO_ALLOW_MMAP); + + /* Disable NFS export support, which also disabled name_to_handle_at. + * Goal is to make xfstests that test name_to_handle_at to fail with + * the right error code (EOPNOTSUPP) than to open_by_handle_at to fail with + * ESTALE and let those test fail. + * Perfect NFS export support is not possible with this FUSE filesystem needs + * more kernel work, in order to passthrough nfs handle encode/decode to + * fuse-server/daemon. + */ + fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT); + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + + /* Try a large IO by default */ + conn->max_write = 4 * 1024 * 1024; +} + + +static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + struct stat attr; + int fd = fi ? fi->fh : get_inode(ino).fd; + + auto res = fstatat(fd, "", &attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + fuse_reply_attr(req, &attr, fs.timeout); +} + + +static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info* fi) { + Inode& inode = get_inode(ino); + int ifd = inode.fd; + int res; + + if (valid & FUSE_SET_ATTR_MODE) { + if (fi) { + res = fchmod(fi->fh, attr->st_mode); + } else { + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", ifd); + res = chmod(procname, attr->st_mode); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? attr->st_uid : static_cast(-1); + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? attr->st_gid : static_cast(-1); + + res = fchownat(ifd, "", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + } + if (valid & FUSE_SET_ATTR_SIZE) { + if (fi) { + res = ftruncate(fi->fh, attr->st_size); + } else { + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", ifd); + res = truncate(procname, attr->st_size); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + if (fi) + res = futimens(fi->fh, tv); + else { +#ifdef HAVE_UTIMENSAT + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", ifd); + res = utimensat(AT_FDCWD, procname, tv, 0); +#else + res = -1; + errno = EOPNOTSUPP; +#endif + } + if (res == -1) + goto out_err; + } + return sfs_getattr(req, ino, fi); + +out_err: + fuse_reply_err(req, errno); +} + + +static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, fuse_file_info *fi) { + (void) ino; + do_setattr(req, ino, attr, valid, fi); +} + + +static int do_lookup(fuse_ino_t parent, const char *name, + fuse_entry_param *e) { + if (fs.debug) + cerr << "DEBUG: lookup(): name=" << name + << ", parent=" << parent << endl; + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW); + if (newfd == -1) + return errno; + + auto res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + auto saveerr = errno; + close(newfd); + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return saveerr; + } + + if (e->attr.st_dev != fs.src_dev) { + cerr << "WARNING: Mountpoints in the source directory tree will be hidden." << endl; + return ENOTSUP; + } else if (e->attr.st_ino == FUSE_ROOT_ID) { + cerr << "ERROR: Source directory tree must not include inode " + << FUSE_ROOT_ID << endl; + return EIO; + } + + SrcId id {e->attr.st_ino, e->attr.st_dev}; + unique_lock fs_lock {fs.mutex}; + Inode* inode_p; + try { + inode_p = &fs.inodes[id]; + } catch (std::bad_alloc&) { + return ENOMEM; + } + e->ino = reinterpret_cast(inode_p); + Inode& inode {*inode_p}; + e->generation = inode.generation; + + if (inode.fd == -ENOENT) { // found unlinked inode + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " recycled; generation=" << inode.generation << endl; + /* fallthrough to new inode but keep existing inode.nlookup */ + } + + if (inode.fd > 0) { // found existing inode + fs_lock.unlock(); + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " (userspace) already known; fd = " << inode.fd << endl; + lock_guard g {inode.m}; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino + << " count " << inode.nlookup << endl; + + + close(newfd); + } else { // no existing inode + /* This is just here to make Helgrind happy. It violates the + lock ordering requirement (inode.m must be acquired before + fs.mutex), but this is of no consequence because at this + point no other thread has access to the inode mutex */ + lock_guard g {inode.m}; + inode.src_ino = e->attr.st_ino; + inode.src_dev = e->attr.st_dev; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino + << " count " << inode.nlookup << endl; + + inode.fd = newfd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " << e->attr.st_ino + << "; fd = " << inode.fd << endl; + } + + return 0; +} + + +static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { + fuse_entry_param e {}; + auto err = do_lookup(parent, name, &e); + if (err == ENOENT) { + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + e.ino = e.attr.st_ino = 0; + fuse_reply_entry(req, &e); + } else if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, err); + } else { + fuse_reply_entry(req, &e); + } +} + + +static void mknod_symlink(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, dev_t rdev, + const char *link) { + int res; + Inode& inode_p = get_inode(parent); + auto saverr = ENOMEM; + + if (S_ISDIR(mode)) + res = mkdirat(inode_p.fd, name, mode); + else if (S_ISLNK(mode)) + res = symlinkat(link, inode_p.fd, name); + else + res = mknodat(inode_p.fd, name, mode, rdev); + saverr = errno; + if (res == -1) + goto out; + + fuse_entry_param e; + saverr = do_lookup(parent, name, &e); + if (saverr) + goto out; + + fuse_reply_entry(req, &e); + return; + +out: + if (saverr == ENFILE || saverr == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, saverr); +} + + +static void sfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev) { + mknod_symlink(req, parent, name, mode, rdev, nullptr); +} + + +static void sfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) { + mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr); +} + + +static void sfs_symlink(fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name) { + mknod_symlink(req, parent, name, S_IFLNK, 0, link); +} + + +static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, + const char *name) { + Inode& inode = get_inode(ino); + Inode& inode_p = get_inode(parent); + fuse_entry_param e {}; + + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + auto res = linkat(AT_FDCWD, procname, inode_p.fd, name, AT_SYMLINK_FOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + + res = fstatat(inode.fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + e.ino = reinterpret_cast(&inode); + { + lock_guard g {inode.m}; + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino + << " count " << inode.nlookup << endl; + } + + fuse_reply_entry(req, &e); + return; +} + + +static void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) { + Inode& inode_p = get_inode(parent); + lock_guard g {inode_p.m}; + auto res = unlinkat(inode_p.fd, name, AT_REMOVEDIR); + fuse_reply_err(req, res == -1 ? errno : 0); +} + + +static void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) { + Inode& inode_p = get_inode(parent); + Inode& inode_np = get_inode(newparent); + if (flags) { + fuse_reply_err(req, EINVAL); + return; + } + + auto res = renameat(inode_p.fd, name, inode_np.fd, newname); + fuse_reply_err(req, res == -1 ? errno : 0); +} + + +static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) { + Inode& inode_p = get_inode(parent); + // Release inode.fd before last unlink like nfsd EXPORT_OP_CLOSE_BEFORE_UNLINK + // to test reused inode numbers. + // Skip this when inode has an open file and when writeback cache is enabled. + if (!fs.timeout) { + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + fuse_reply_err(req, err); + return; + } + if (e.attr.st_nlink == 1) { + Inode& inode = get_inode(e.ino); + lock_guard g {inode.m}; + if (inode.fd > 0 && !inode.nopen) { + if (fs.debug) + cerr << "DEBUG: unlink: release inode " << e.attr.st_ino + << "; fd=" << inode.fd << endl; + lock_guard g_fs {fs.mutex}; + close(inode.fd); + inode.fd = -ENOENT; + inode.generation++; + } + } + + // decrease the ref which lookup above had increased + forget_one(e.ino, 1); + } + auto res = unlinkat(inode_p.fd, name, 0); + fuse_reply_err(req, res == -1 ? errno : 0); +} + + +static void forget_one(fuse_ino_t ino, uint64_t n) { + Inode& inode = get_inode(ino); + unique_lock l {inode.m}; + + if(n > inode.nlookup) { + cerr << "INTERNAL ERROR: Negative lookup count for inode " + << inode.src_ino << endl; + abort(); + } + inode.nlookup -= n; + + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino + << " count " << inode.nlookup << endl; + + if (!inode.nlookup) { + if (fs.debug) + cerr << "DEBUG: forget: cleaning up inode " << inode.src_ino << endl; + { + lock_guard g_fs {fs.mutex}; + l.unlock(); + fs.inodes.erase({inode.src_ino, inode.src_dev}); + } + } else if (fs.debug) + cerr << "DEBUG: forget: inode " << inode.src_ino + << " lookup count now " << inode.nlookup << endl; +} + +static void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) { + forget_one(ino, nlookup); + fuse_reply_none(req); +} + + +static void sfs_forget_multi(fuse_req_t req, size_t count, + fuse_forget_data *forgets) { + for (int i = 0; i < count; i++) + forget_one(forgets[i].ino, forgets[i].nlookup); + fuse_reply_none(req); +} + + +static void sfs_readlink(fuse_req_t req, fuse_ino_t ino) { + Inode& inode = get_inode(ino); + char buf[PATH_MAX + 1]; + auto res = readlinkat(inode.fd, "", buf, sizeof(buf)); + if (res == -1) + fuse_reply_err(req, errno); + else if (res == sizeof(buf)) + fuse_reply_err(req, ENAMETOOLONG); + else { + buf[res] = '\0'; + fuse_reply_readlink(req, buf); + } +} + + +struct DirHandle { + DIR *dp {nullptr}; + off_t offset; + + DirHandle() = default; + DirHandle(const DirHandle&) = delete; + DirHandle& operator=(const DirHandle&) = delete; + + ~DirHandle() { + if(dp) + closedir(dp); + } +}; + + +static DirHandle *get_dir_handle(fuse_file_info *fi) { + return reinterpret_cast(fi->fh); +} + + +static void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { + Inode& inode = get_inode(ino); + auto d = new (nothrow) DirHandle; + if (d == nullptr) { + fuse_reply_err(req, ENOMEM); + return; + } + + // Make Helgrind happy - it can't know that there's an implicit + // synchronization due to the fact that other threads cannot + // access d until we've called fuse_reply_*. + lock_guard g {inode.m}; + + auto fd = openat(inode.fd, ".", O_RDONLY); + if (fd == -1) + goto out_errno; + + // On success, dir stream takes ownership of fd, so we + // do not have to close it. + d->dp = fdopendir(fd); + if(d->dp == nullptr) + goto out_errno; + + d->offset = 0; + + fi->fh = reinterpret_cast(d); + if(fs.timeout) { + fi->keep_cache = 1; + fi->cache_readdir = 1; + } + fuse_reply_open(req, fi); + return; + +out_errno: + auto error = errno; + delete d; + if (error == ENFILE || error == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, error); +} + + +static bool is_dot_or_dotdot(const char *name) { + return name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + + +static void do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi, const int plus) { + auto d = get_dir_handle(fi); + Inode& inode = get_inode(ino); + lock_guard g {inode.m}; + char *p; + auto rem = size; + int err = 0, count = 0; + + if (fs.debug) + cerr << "DEBUG: readdir(): started with offset " + << offset << endl; + + auto buf = new (nothrow) char[size]; + if (!buf) { + fuse_reply_err(req, ENOMEM); + return; + } + p = buf; + + if (offset != d->offset) { + if (fs.debug) + cerr << "DEBUG: readdir(): seeking to " << offset << endl; + seekdir(d->dp, offset); + d->offset = offset; + } + + while (1) { + bool did_lookup = false; + struct dirent *entry; + errno = 0; + entry = readdir(d->dp); + if (!entry) { + if(errno) { + err = errno; + if (fs.debug) + warn("DEBUG: readdir(): readdir failed with"); + goto error; + } + break; // End of stream + } + d->offset = entry->d_off; + + fuse_entry_param e{}; + size_t entsize; + if (plus) { + if (is_dot_or_dotdot(entry->d_name)) { + /* fuse kernel ignores attributes for these and also does + * not increase lookup count (see fuse_direntplus_link) */ + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + } else { + err = do_lookup(ino, entry->d_name, &e); + if (err) + goto error; + did_lookup = true; + } + entsize = fuse_add_direntry_plus(req, p, rem, entry->d_name, &e, entry->d_off); + } else { + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + entsize = fuse_add_direntry(req, p, rem, entry->d_name, &e.attr, entry->d_off); + } + + if (entsize > rem) { + if (fs.debug) + cerr << "DEBUG: readdir(): buffer full, returning data. " << endl; + if (did_lookup) + forget_one(e.ino, 1); + break; + } + + p += entsize; + rem -= entsize; + count++; + if (fs.debug) { + cerr << "DEBUG: readdir(): added to buffer: " << entry->d_name + << ", ino " << e.attr.st_ino << ", offset " << entry->d_off << endl; + } + } + err = 0; +error: + + // If there's an error, we can only signal it if we haven't stored + // any entries yet - otherwise we'd end up with wrong lookup + // counts for the entries that are already in the buffer. So we + // return what we've collected until that point. + if (err && rem == size) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, err); + } else { + if (fs.debug) + cerr << "DEBUG: readdir(): returning " << count + << " entries, curr offset " << d->offset << endl; + fuse_reply_buf(req, buf, size - rem); + } + delete[] buf; + return; +} + + +static void sfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi) { + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 0); +} + + +static void sfs_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi) { + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 1); +} + + +static void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { + (void) ino; + auto d = get_dir_handle(fi); + delete d; + fuse_reply_err(req, 0); +} + + +static void do_passthrough_open(fuse_req_t req, fuse_ino_t ino, int fd, + fuse_file_info *fi) { + Inode& inode = get_inode(ino); + /* Setup a shared backing file on first open of an inode */ + if (inode.backing_id) { + if (fs.debug) + cerr << "DEBUG: reusing shared backing file " + << inode.backing_id << " for inode " << ino << endl; + fi->backing_id = inode.backing_id; + } else if (!(inode.backing_id = fuse_passthrough_open(req, fd))) { + cerr << "DEBUG: fuse_passthrough_open failed for inode " << ino + << ", disabling rw passthrough." << endl; + fs.passthrough = false; + } else { + if (fs.debug) + cerr << "DEBUG: setup shared backing file " + << inode.backing_id << " for inode " << ino << endl; + fi->backing_id = inode.backing_id; + } + /* open in passthrough mode must drop old page cache */ + if (fi->backing_id) + fi->keep_cache = false; +} + +static void sfs_create_open_flags(fuse_file_info *fi) +{ + if (fs.direct_io) + fi->direct_io = 1; + + /* + * fi->direct_io (FOPEN_DIRECT_IO) is set to benefit from + * parallel_direct_writes, which kernel cannot do for plain O_DIRECT. + * However, passthrough is preferred, but which is not possible when + * FOPEN_DIRECT_IO is set. + */ + if (!fs.passthrough) { + if (fi->flags & O_DIRECT) + fi->direct_io = 1; + } + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + fi->keep_cache = (fs.timeout != 0); + fi->noflush = (fs.timeout == 0 && (fi->flags & O_ACCMODE) == O_RDONLY); +} + +static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, fuse_file_info *fi) { + Inode& inode_p = get_inode(parent); + + auto fd = openat(inode_p.fd, name, + (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, err); + return; + } + + Inode& inode = get_inode(e.ino); + lock_guard g {inode.m}; + inode.nopen++; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + fuse_reply_create(req, &e, fi); +} + +static Inode *create_new_inode(int fd, fuse_entry_param *e) +{ + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return NULL; + } + + SrcId id {e->attr.st_ino, e->attr.st_dev}; + unique_lock fs_lock {fs.mutex}; + Inode* p_inode; + try { + p_inode = &fs.inodes[id]; + } catch (std::bad_alloc&) { + return NULL; + } + + e->ino = reinterpret_cast(p_inode); + e->generation = p_inode->generation; + + lock_guard g {p_inode->m}; + p_inode->src_ino = e->attr.st_ino; + p_inode->src_dev = e->attr.st_dev; + + p_inode->nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << p_inode->src_ino + << " count " << p_inode->nlookup << endl; + + p_inode->fd = fd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " << e->attr.st_ino + << "; fd = " << p_inode->fd << endl; + return p_inode; +} + +static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, + mode_t mode, fuse_file_info *fi) { + Inode& parent_inode = get_inode(parent); + + auto fd = openat(parent_inode.fd, ".", + (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + + Inode *inode = create_new_inode(dup(fd), &e); + if (inode == NULL) { + auto err = errno; + cerr << "ERROR: could not create new inode." << endl; + close(fd); + fuse_reply_err(req, err); + return; + } + + lock_guard g {inode->m}; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + + fuse_reply_create(req, &e, fi); +} + + +static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + fuse_file_info *fi) { + (void) ino; + int res; + int fd = dirfd(get_dir_handle(fi)->dp); + if (datasync) + res = fdatasync(fd); + else + res = fsync(fd); + fuse_reply_err(req, res == -1 ? errno : 0); +} + + +static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { + Inode& inode = get_inode(ino); + + /* With writeback cache, kernel may send read requests even + when userspace opened write-only */ + if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) { + fi->flags &= ~O_ACCMODE; + fi->flags |= O_RDWR; + } + + /* With writeback cache, O_APPEND is handled by the kernel. This + breaks atomicity (since the file may change in the underlying + filesystem, so that the kernel's idea of the end of the file + isn't accurate anymore). However, no process should modify the + file in the underlying filesystem once it has been read, so + this is not a problem. */ + if (fs.timeout && fi->flags & O_APPEND) + fi->flags &= ~O_APPEND; + + /* Unfortunately we cannot use inode.fd, because this was opened + with O_PATH (so it doesn't allow read/write access). */ + char buf[64]; + sprintf(buf, "/proc/self/fd/%i", inode.fd); + auto fd = open(buf, fi->flags & ~O_NOFOLLOW); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." << endl; + fuse_reply_err(req, err); + return; + } + + lock_guard g {inode.m}; + inode.nopen++; + + sfs_create_open_flags(fi); + + fi->fh = fd; + if (fs.passthrough) + do_passthrough_open(req, ino, fd, fi); + fuse_reply_open(req, fi); +} + + +static void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { + Inode& inode = get_inode(ino); + lock_guard g {inode.m}; + inode.nopen--; + + /* Close the shared backing file on last file close of an inode */ + if (inode.backing_id && !inode.nopen) { + if (fuse_passthrough_close(req, inode.backing_id) < 0) { + cerr << "DEBUG: fuse_passthrough_close failed for inode " + << ino << " backing file " << inode.backing_id << endl; + } else if (fs.debug) { + cerr << "DEBUG: closed backing file " << inode.backing_id + << " for inode " << ino << endl; + } + inode.backing_id = 0; + } + + close(fi->fh); + fuse_reply_err(req, 0); +} + + +static void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { + (void) ino; + auto res = close(dup(fi->fh)); + fuse_reply_err(req, res == -1 ? errno : 0); +} + + +static void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + fuse_file_info *fi) { + (void) ino; + int res; + if (datasync) + res = fdatasync(fi->fh); + else + res = fsync(fi->fh); + fuse_reply_err(req, res == -1 ? errno : 0); +} + + +static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) { + + fuse_bufvec buf = FUSE_BUFVEC_INIT(size); + buf.buf[0].flags = static_cast( + FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = off; + + fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS); +} + +static void sfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + fuse_file_info *fi) { + (void) ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough read failed." << endl; + fuse_reply_err(req, EIO); + return; + } + do_read(req, size, off, fi); +} + + +static void do_write_buf(fuse_req_t req, size_t size, off_t off, + fuse_bufvec *in_buf, fuse_file_info *fi) { + fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size); + out_buf.buf[0].flags = static_cast( + FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + out_buf.buf[0].fd = fi->fh; + out_buf.buf[0].pos = off; + + auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS); + if (res < 0) + fuse_reply_err(req, -res); + else + fuse_reply_write(req, (size_t)res); +} + + +static void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec *in_buf, + off_t off, fuse_file_info *fi) { + (void) ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough write failed." << endl; + fuse_reply_err(req, EIO); + return; + } + auto size {fuse_buf_size(in_buf)}; + do_write_buf(req, size, off, in_buf, fi); +} + + +static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) { + struct statvfs stbuf; + + auto res = fstatvfs(get_fs_fd(ino), &stbuf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statfs(req, &stbuf); +} + + +#ifdef HAVE_POSIX_FALLOCATE +static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, fuse_file_info *fi) { + (void) ino; + if (mode) { + fuse_reply_err(req, EOPNOTSUPP); + return; + } + + auto err = posix_fallocate(fi->fh, offset, length); + fuse_reply_err(req, err); +} +#endif + +static void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi, + int op) { + (void) ino; + auto res = flock(fi->fh, op); + fuse_reply_err(req, res == -1 ? errno : 0); +} + + +#ifdef HAVE_SETXATTR +static void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) { + char *value = nullptr; + Inode& inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = getxattr(procname, name, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = getxattr(procname, name, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + delete[] value; + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + + +static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) { + char *value = nullptr; + Inode& inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = listxattr(procname, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = listxattr(procname, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + delete[] value; + return; +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + + +static void sfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) { + Inode& inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + ret = setxattr(procname, name, value, size, flags); + saverr = ret == -1 ? errno : 0; + + fuse_reply_err(req, saverr); +} + + +static void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) { + char procname[64]; + Inode& inode = get_inode(ino); + ssize_t ret; + int saverr; + + sprintf(procname, "/proc/self/fd/%i", inode.fd); + ret = removexattr(procname, name); + saverr = ret == -1 ? errno : 0; + + fuse_reply_err(req, saverr); +} +#endif + + +static void assign_operations(fuse_lowlevel_ops &sfs_oper) { + sfs_oper.init = sfs_init; + sfs_oper.lookup = sfs_lookup; + sfs_oper.mkdir = sfs_mkdir; + sfs_oper.mknod = sfs_mknod; + sfs_oper.symlink = sfs_symlink; + sfs_oper.link = sfs_link; + sfs_oper.unlink = sfs_unlink; + sfs_oper.rmdir = sfs_rmdir; + sfs_oper.rename = sfs_rename; + sfs_oper.forget = sfs_forget; + sfs_oper.forget_multi = sfs_forget_multi; + sfs_oper.getattr = sfs_getattr; + sfs_oper.setattr = sfs_setattr; + sfs_oper.readlink = sfs_readlink; + sfs_oper.opendir = sfs_opendir; + sfs_oper.readdir = sfs_readdir; + sfs_oper.readdirplus = sfs_readdirplus; + sfs_oper.releasedir = sfs_releasedir; + sfs_oper.fsyncdir = sfs_fsyncdir; + sfs_oper.create = sfs_create; + sfs_oper.tmpfile = sfs_tmpfile; + sfs_oper.open = sfs_open; + sfs_oper.release = sfs_release; + sfs_oper.flush = sfs_flush; + sfs_oper.fsync = sfs_fsync; + sfs_oper.read = sfs_read; + sfs_oper.write_buf = sfs_write_buf; + sfs_oper.statfs = sfs_statfs; +#ifdef HAVE_POSIX_FALLOCATE + sfs_oper.fallocate = sfs_fallocate; +#endif + sfs_oper.flock = sfs_flock; +#ifdef HAVE_SETXATTR + sfs_oper.setxattr = sfs_setxattr; + sfs_oper.getxattr = sfs_getxattr; + sfs_oper.listxattr = sfs_listxattr; + sfs_oper.removexattr = sfs_removexattr; +#endif +} + +static void print_usage(char *prog_name) { + cout << "Usage: " << prog_name << " --help\n" + << " " << prog_name << " [options] \n"; +} + +static cxxopts::ParseResult parse_wrapper(cxxopts::Options& parser, int& argc, char**& argv) { + try { + return parser.parse(argc, argv); + } catch (cxxopts::option_not_exists_exception& exc) { + std::cout << argv[0] << ": " << exc.what() << std::endl; + print_usage(argv[0]); + exit(2); + } +} + + +static void string_split(std::string s, std::vector& out, std::string delimiter) { + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::string token; + + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + out.push_back(token); + } + + out.push_back(s.substr(pos_start)); +} + + +static std::string string_join(const std::vector& elems, char delim) +{ + std::ostringstream out; + for (auto ii = elems.begin(); ii != elems.end(); ++ii) { + out << (*ii); + if (ii + 1 != elems.end()) { + out << delim; + } + } + return out.str(); +} + + +static cxxopts::ParseResult parse_options(int argc, char **argv) { + cxxopts::Options opt_parser(argv[0]); + std::vector mount_options; + opt_parser.add_options() + ("debug", "Enable filesystem debug messages") + ("debug-fuse", "Enable libfuse debug messages") + ("foreground", "Run in foreground") + ("help", "Print help") + ("nocache", "Disable attribute all caching") + ("nosplice", "Do not use splice(2) to transfer data") + ("nopassthrough", "Do not use pass-through mode for read/write") + ("single", "Run single-threaded") + ("o", "Mount options (see mount.fuse(5) - only use if you know what " + "you are doing)", cxxopts::value(mount_options)) + ("num-threads", "Number of libfuse worker threads", + cxxopts::value()->default_value(SFS_DEFAULT_THREADS)) + ("clone-fd", "use separate fuse device fd for each thread") + ("direct-io", "enable fuse kernel internal direct-io"); + + // FIXME: Find a better way to limit the try clause to just + // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146) + auto options = parse_wrapper(opt_parser, argc, argv); + + if (options.count("help")) { + print_usage(argv[0]); + // Strip everything before the option list from the + // default help string. + auto help = opt_parser.help(); + std::cout << std::endl << "options:" + << help.substr(help.find("\n\n") + 1, string::npos); + exit(0); + + } else if (argc != 3) { + std::cout << argv[0] << ": invalid number of arguments\n"; + print_usage(argv[0]); + exit(2); + } + + fs.debug = options.count("debug") != 0; + fs.debug_fuse = options.count("debug-fuse") != 0; + + fs.foreground = options.count("foreground") != 0; + if (fs.debug || fs.debug_fuse) + fs.foreground = true; + + fs.nosplice = options.count("nosplice") != 0; + fs.passthrough = options.count("nopassthrough") == 0; + fs.num_threads = options["num-threads"].as(); + fs.clone_fd = options.count("clone-fd"); + fs.direct_io = options.count("direct-io"); + char* resolved_path = realpath(argv[1], NULL); + if (resolved_path == NULL) + warn("WARNING: realpath() failed with"); + fs.source = std::string {resolved_path}; + free(resolved_path); + + std::vector flattened_mount_opts; + for (auto opt : mount_options) { + string_split(opt, flattened_mount_opts, ","); + } + + bool found_fsname = false; + for (auto opt : flattened_mount_opts) { + if (opt.find("fsname=") == 0) { + found_fsname = true; + continue; + } + + /* Filter out some obviously incorrect options. */ + if (opt == "fd") { + std::cout << argv[0] << ": Unsupported mount option: " << opt << "\n"; + print_usage(argv[0]); + exit(2); + } + } + if (!found_fsname) { + flattened_mount_opts.push_back("fsname=" + fs.source); + } + flattened_mount_opts.push_back("default_permissions"); + fs.fuse_mount_options = string_join(flattened_mount_opts, ','); + return options; +} + + +static void maximize_fd_limit() { + struct rlimit lim {}; + auto res = getrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) { + warn("WARNING: getrlimit() failed with"); + return; + } + lim.rlim_cur = lim.rlim_max; + res = setrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) + warn("WARNING: setrlimit() failed with"); +} + + +int main(int argc, char *argv[]) { + + struct fuse_loop_config *loop_config = NULL; + + // Parse command line options + auto options {parse_options(argc, argv)}; + + // We need an fd for every dentry in our the filesystem that the + // kernel knows about. This is way more than most processes need, + // so try to get rid of any resource softlimit. + maximize_fd_limit(); + + // Initialize filesystem root + fs.root.fd = -1; + fs.root.nlookup = 9999; + fs.timeout = options.count("nocache") ? 0 : 86400.0; + + struct stat stat; + auto ret = lstat(fs.source.c_str(), &stat); + if (ret == -1) + err(1, "ERROR: failed to stat source (\"%s\")", fs.source.c_str()); + if (!S_ISDIR(stat.st_mode)) + errx(1, "ERROR: source is not a directory"); + fs.src_dev = stat.st_dev; + + fs.root.fd = open(fs.source.c_str(), O_PATH); + if (fs.root.fd == -1) + err(1, "ERROR: open(\"%s\", O_PATH)", fs.source.c_str()); + + // Initialize fuse + fuse_args args = FUSE_ARGS_INIT(0, nullptr); + if (fuse_opt_add_arg(&args, argv[0]) || + fuse_opt_add_arg(&args, "-o") || + fuse_opt_add_arg(&args, fs.fuse_mount_options.c_str()) || + (fs.debug_fuse && fuse_opt_add_arg(&args, "-odebug"))) + errx(3, "ERROR: Out of memory"); + + ret = -1; + fuse_lowlevel_ops sfs_oper {}; + assign_operations(sfs_oper); + auto se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs); + if (se == nullptr) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_set_fail_signal_handlers(se) != 0) + goto err_out2; + + // Don't apply umask, use modes exactly as specified + umask(0); + + // Mount and run main loop + loop_config = fuse_loop_cfg_create(); + + if (fs.num_threads != -1) + fuse_loop_cfg_set_max_threads(loop_config, fs.num_threads); + + fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd); + + if (fuse_session_mount(se, argv[2]) != 0) + goto err_out3; + + fuse_daemonize(fs.foreground); + + if (!fs.foreground) + fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS, LOG_DAEMON); + + if (options.count("single")) + ret = fuse_session_loop(se); + else + ret = fuse_session_loop_mt(se, loop_config); + + fuse_session_unmount(se); + +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + + fuse_loop_cfg_destroy(loop_config); + fuse_opt_free_args(&args); + + if (!fs.foreground) + fuse_log_close_syslog(); + + return ret ? 1 : 0; +} + diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c new file mode 100644 index 0000000..fa8abb8 --- /dev/null +++ b/example/passthrough_ll.c @@ -0,0 +1,1405 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. In contrast to passthrough.c and passthrough_fh.c, + * this implementation uses the low-level API. Its performance should + * be the least bad among the three, but many operations are not + * implemented. In particular, it is not possible to remove files (or + * directories) because the code necessary to defer actual removal + * until the file is not opened anymore would make the example much + * more complicated. + * + * When writeback caching is enabled (-o writeback mount option), it + * is only possible to write to files for which the mounting user has + * read permissions. This is because the writeback cache requires the + * kernel to be able to issue read requests for all files (which the + * passthrough filesystem cannot satisfy if it can't read the file in + * the underlying filesystem). + * + * Compile with: + * + * gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll + * + * ## Source code ## + * \include passthrough_ll.c + */ + +#define _GNU_SOURCE +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "passthrough_helpers.h" + +/* We are re-using pointers to our `struct lo_inode` and `struct + lo_dirp` elements as inodes. This means that we must be able to + store uintptr_t values in a fuse_ino_t variable. The following + incantation checks this condition at compile time. */ +#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus +_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t), + "fuse_ino_t too small to hold uintptr_t values!"); +#else +struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \ + { unsigned _uintptr_to_must_hold_fuse_ino_t: + ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); }; +#endif + +struct lo_inode { + struct lo_inode *next; /* protected by lo->mutex */ + struct lo_inode *prev; /* protected by lo->mutex */ + int fd; + ino_t ino; + dev_t dev; + uint64_t refcount; /* protected by lo->mutex */ +}; + +enum { + CACHE_NEVER, + CACHE_NORMAL, + CACHE_ALWAYS, +}; + +struct lo_data { + pthread_mutex_t mutex; + int debug; + int writeback; + int flock; + int xattr; + char *source; + double timeout; + int cache; + int timeout_set; + struct lo_inode root; /* protected by lo->mutex */ +}; + +static const struct fuse_opt lo_opts[] = { + { "writeback", + offsetof(struct lo_data, writeback), 1 }, + { "no_writeback", + offsetof(struct lo_data, writeback), 0 }, + { "source=%s", + offsetof(struct lo_data, source), 0 }, + { "flock", + offsetof(struct lo_data, flock), 1 }, + { "no_flock", + offsetof(struct lo_data, flock), 0 }, + { "xattr", + offsetof(struct lo_data, xattr), 1 }, + { "no_xattr", + offsetof(struct lo_data, xattr), 0 }, + { "timeout=%lf", + offsetof(struct lo_data, timeout), 0 }, + { "timeout=", + offsetof(struct lo_data, timeout_set), 1 }, + { "cache=never", + offsetof(struct lo_data, cache), CACHE_NEVER }, + { "cache=auto", + offsetof(struct lo_data, cache), CACHE_NORMAL }, + { "cache=always", + offsetof(struct lo_data, cache), CACHE_ALWAYS }, + + FUSE_OPT_END +}; + +static void passthrough_ll_help(void) +{ + printf( +" -o writeback Enable writeback\n" +" -o no_writeback Disable write back\n" +" -o source=/home/dir Source directory to be mounted\n" +" -o flock Enable flock\n" +" -o no_flock Disable flock\n" +" -o xattr Enable xattr\n" +" -o no_xattr Disable xattr\n" +" -o timeout=1.0 Caching timeout\n" +" -o timeout=0/1 Timeout is set\n" +" -o cache=never Disable cache\n" +" -o cache=auto Auto enable cache\n" +" -o cache=always Cache always\n"); +} + +static struct lo_data *lo_data(fuse_req_t req) +{ + return (struct lo_data *) fuse_req_userdata(req); +} + +static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino) +{ + if (ino == FUSE_ROOT_ID) + return &lo_data(req)->root; + else + return (struct lo_inode *) (uintptr_t) ino; +} + +static int lo_fd(fuse_req_t req, fuse_ino_t ino) +{ + return lo_inode(req, ino)->fd; +} + +static bool lo_debug(fuse_req_t req) +{ + return lo_data(req)->debug != 0; +} + +static void lo_init(void *userdata, + struct fuse_conn_info *conn) +{ + struct lo_data *lo = (struct lo_data *)userdata; + bool has_flag; + + if (lo->writeback) { + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating writeback\n"); + } + if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating flock locks\n"); + } + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void lo_destroy(void *userdata) +{ + struct lo_data *lo = (struct lo_data*) userdata; + + while (lo->root.next != &lo->root) { + struct lo_inode* next = lo->root.next; + lo->root.next = next->next; + close(next->fd); + free(next); + } +} + +static void lo_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + int res; + struct stat buf; + struct lo_data *lo = lo_data(req); + int fd = fi ? fi->fh : lo_fd(req, ino); + + (void) fi; + + res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + return (void) fuse_reply_err(req, errno); + + fuse_reply_attr(req, &buf, lo->timeout); +} + +static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + int saverr; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + int ifd = inode->fd; + int res; + + if (valid & FUSE_SET_ATTR_MODE) { + if (fi) { + res = fchmod(fi->fh, attr->st_mode); + } else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = chmod(procname, attr->st_mode); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : (uid_t) -1; + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : (gid_t) -1; + + res = fchownat(ifd, "", uid, gid, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + } + if (valid & FUSE_SET_ATTR_SIZE) { + if (fi) { + res = ftruncate(fi->fh, attr->st_size); + } else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = truncate(procname, attr->st_size); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + if (fi) + res = futimens(fi->fh, tv); + else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = utimensat(AT_FDCWD, procname, tv, 0); + } + if (res == -1) + goto out_err; + } + + return lo_getattr(req, ino, fi); + +out_err: + saverr = errno; + fuse_reply_err(req, saverr); +} + +static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st) +{ + struct lo_inode *p; + struct lo_inode *ret = NULL; + + pthread_mutex_lock(&lo->mutex); + for (p = lo->root.next; p != &lo->root; p = p->next) { + if (p->ino == st->st_ino && p->dev == st->st_dev) { + assert(p->refcount > 0); + ret = p; + ret->refcount++; + break; + } + } + pthread_mutex_unlock(&lo->mutex); + return ret; +} + + +static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo) +{ + struct lo_inode *inode = NULL; + struct lo_inode *prev, *next; + + inode = calloc(1, sizeof(struct lo_inode)); + if (!inode) + return NULL; + + inode->refcount = 1; + inode->fd = fd; + inode->ino = e->attr.st_ino; + inode->dev = e->attr.st_dev; + + pthread_mutex_lock(&lo->mutex); + prev = &lo->root; + next = prev->next; + next->prev = inode; + inode->next = next; + inode->prev = prev; + prev->next = inode; + pthread_mutex_unlock(&lo->mutex); + return inode; +} + +static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e) +{ + int res; + struct lo_data *lo = lo_data(req); + + memset(e, 0, sizeof(*e)); + e->attr_timeout = lo->timeout; + e->entry_timeout = lo->timeout; + + res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + return errno; + + e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n", + (unsigned long long) parent, fd, (unsigned long long) e->ino); + + return 0; + +} + +static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name, + struct fuse_entry_param *e) +{ + int newfd; + int res; + int saverr; + struct lo_data *lo = lo_data(req); + struct lo_inode *inode; + + memset(e, 0, sizeof(*e)); + e->attr_timeout = lo->timeout; + e->entry_timeout = lo->timeout; + + newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW); + if (newfd == -1) + goto out_err; + + res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + + inode = lo_find(lo_data(req), &e->attr); + if (inode) { + close(newfd); + newfd = -1; + } else { + inode = create_new_inode(newfd, e, lo); + if (!inode) + goto out_err; + } + e->ino = (uintptr_t) inode; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, (unsigned long long) e->ino); + + return 0; + +out_err: + saverr = errno; + if (newfd != -1) + close(newfd); + return saverr; +} + +static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n", + parent, name); + + err = lo_do_lookup(req, parent, name, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_entry(req, &e); +} + +static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, dev_t rdev, + const char *link) +{ + int res; + int saverr; + struct lo_inode *dir = lo_inode(req, parent); + struct fuse_entry_param e; + + res = mknod_wrapper(dir->fd, name, link, mode, rdev); + + saverr = errno; + if (res == -1) + goto out; + + saverr = lo_do_lookup(req, parent, name, &e); + if (saverr) + goto out; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, (unsigned long long) e.ino); + + fuse_reply_entry(req, &e); + return; + +out: + fuse_reply_err(req, saverr); +} + +static void lo_mknod(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, dev_t rdev) +{ + lo_mknod_symlink(req, parent, name, mode, rdev, NULL); +} + +static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL); +} + +static void lo_symlink(fuse_req_t req, const char *link, + fuse_ino_t parent, const char *name) +{ + lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link); +} + +static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, + const char *name) +{ + int res; + struct lo_data *lo = lo_data(req); + struct lo_inode *inode = lo_inode(req, ino); + struct fuse_entry_param e; + char procname[64]; + int saverr; + + memset(&e, 0, sizeof(struct fuse_entry_param)); + e.attr_timeout = lo->timeout; + e.entry_timeout = lo->timeout; + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name, + AT_SYMLINK_FOLLOW); + if (res == -1) + goto out_err; + + res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + + pthread_mutex_lock(&lo->mutex); + inode->refcount++; + pthread_mutex_unlock(&lo->mutex); + e.ino = (uintptr_t) inode; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, + (unsigned long long) e.ino); + + fuse_reply_entry(req, &e); + return; + +out_err: + saverr = errno; + fuse_reply_err(req, saverr); +} + +static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + int res; + + res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + int res; + + if (flags) { + fuse_reply_err(req, EINVAL); + return; + } + + res = renameat(lo_fd(req, parent), name, + lo_fd(req, newparent), newname); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + int res; + + res = unlinkat(lo_fd(req, parent), name, 0); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n) +{ + if (!inode) + return; + + pthread_mutex_lock(&lo->mutex); + assert(inode->refcount >= n); + inode->refcount -= n; + if (!inode->refcount) { + struct lo_inode *prev, *next; + + prev = inode->prev; + next = inode->next; + next->prev = prev; + prev->next = next; + + pthread_mutex_unlock(&lo->mutex); + close(inode->fd); + free(inode); + + } else { + pthread_mutex_unlock(&lo->mutex); + } +} + +static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + struct lo_data *lo = lo_data(req); + struct lo_inode *inode = lo_inode(req, ino); + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n", + (unsigned long long) ino, + (unsigned long long) inode->refcount, + (unsigned long long) nlookup); + } + + unref_inode(lo, inode, nlookup); +} + +static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + lo_forget_one(req, ino, nlookup); + fuse_reply_none(req); +} + +static void lo_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + int i; + + for (i = 0; i < count; i++) + lo_forget_one(req, forgets[i].ino, forgets[i].nlookup); + fuse_reply_none(req); +} + +static void lo_readlink(fuse_req_t req, fuse_ino_t ino) +{ + char buf[PATH_MAX + 1]; + int res; + + res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf)); + if (res == -1) + return (void) fuse_reply_err(req, errno); + + if (res == sizeof(buf)) + return (void) fuse_reply_err(req, ENAMETOOLONG); + + buf[res] = '\0'; + + fuse_reply_readlink(req, buf); +} + +struct lo_dirp { + DIR *dp; + struct dirent *entry; + off_t offset; +}; + +static struct lo_dirp *lo_dirp(struct fuse_file_info *fi) +{ + return (struct lo_dirp *) (uintptr_t) fi->fh; +} + +static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int error = ENOMEM; + struct lo_data *lo = lo_data(req); + struct lo_dirp *d; + int fd = -1; + + d = calloc(1, sizeof(struct lo_dirp)); + if (d == NULL) + goto out_err; + + fd = openat(lo_fd(req, ino), ".", O_RDONLY); + if (fd == -1) + goto out_errno; + + d->dp = fdopendir(fd); + if (d->dp == NULL) + goto out_errno; + + d->offset = 0; + d->entry = NULL; + + fi->fh = (uintptr_t) d; + if (lo->cache != CACHE_NEVER) + fi->cache_readdir = 1; + if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + fuse_reply_open(req, fi); + return; + +out_errno: + error = errno; +out_err: + if (d) { + if (fd != -1) + close(fd); + free(d); + } + fuse_reply_err(req, error); +} + +static int is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); +} + +static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi, int plus) +{ + struct lo_dirp *d = lo_dirp(fi); + char *buf; + char *p; + size_t rem = size; + int err; + + (void) ino; + + buf = calloc(1, size); + if (!buf) { + err = ENOMEM; + goto error; + } + p = buf; + + if (offset != d->offset) { + seekdir(d->dp, offset); + d->entry = NULL; + d->offset = offset; + } + while (1) { + size_t entsize; + off_t nextoff; + const char *name; + + if (!d->entry) { + errno = 0; + d->entry = readdir(d->dp); + if (!d->entry) { + if (errno) { // Error + err = errno; + goto error; + } else { // End of stream + break; + } + } + } + nextoff = d->entry->d_off; + name = d->entry->d_name; + fuse_ino_t entry_ino = 0; + if (plus) { + struct fuse_entry_param e; + if (is_dot_or_dotdot(name)) { + e = (struct fuse_entry_param) { + .attr.st_ino = d->entry->d_ino, + .attr.st_mode = d->entry->d_type << 12, + }; + } else { + err = lo_do_lookup(req, ino, name, &e); + if (err) + goto error; + entry_ino = e.ino; + } + + entsize = fuse_add_direntry_plus(req, p, rem, name, + &e, nextoff); + } else { + struct stat st = { + .st_ino = d->entry->d_ino, + .st_mode = d->entry->d_type << 12, + }; + entsize = fuse_add_direntry(req, p, rem, name, + &st, nextoff); + } + if (entsize > rem) { + if (entry_ino != 0) + lo_forget_one(req, entry_ino, 1); + break; + } + + p += entsize; + rem -= entsize; + + d->entry = NULL; + d->offset = nextoff; + } + + err = 0; +error: + // If there's an error, we can only signal it if we haven't stored + // any entries yet - otherwise we'd end up with wrong lookup + // counts for the entries that are already in the buffer. So we + // return what we've collected until that point. + if (err && rem == size) + fuse_reply_err(req, err); + else + fuse_reply_buf(req, buf, size - rem); + free(buf); +} + +static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + lo_do_readdir(req, ino, size, offset, fi, 0); +} + +static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + lo_do_readdir(req, ino, size, offset, fi, 1); +} + +static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + struct lo_dirp *d = lo_dirp(fi); + (void) ino; + closedir(d->dp); + free(d); + fuse_reply_err(req, 0); +} + +static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent, + mode_t mode, struct fuse_file_info *fi) +{ + int fd; + struct lo_data *lo = lo_data(req); + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n", + parent); + + fd = openat(lo_fd(req, parent), ".", + (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + err = fill_entry_param_new_inode(req, parent, fd, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_create(req, &e, fi); +} + +static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi) +{ + int fd; + struct lo_data *lo = lo_data(req); + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n", + parent, name); + + fd = openat(lo_fd(req, parent), name, + (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + err = lo_do_lookup(req, parent, name, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_create(req, &e, fi); +} + +static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + int res; + int fd = dirfd(lo_dirp(fi)->dp); + (void) ino; + if (datasync) + res = fdatasync(fd); + else + res = fsync(fd); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int fd; + char buf[64]; + struct lo_data *lo = lo_data(req); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n", + ino, fi->flags); + + /* With writeback cache, kernel may send read requests even + when userspace opened write-only */ + if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) { + fi->flags &= ~O_ACCMODE; + fi->flags |= O_RDWR; + } + + /* With writeback cache, O_APPEND is handled by the kernel. + This breaks atomicity (since the file may change in the + underlying filesystem, so that the kernel's idea of the + end of the file isn't accurate anymore). In this example, + we just accept that. A more rigorous filesystem may want + to return an error here */ + if (lo->writeback && (fi->flags & O_APPEND)) + fi->flags &= ~O_APPEND; + + sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino)); + fd = open(buf, fi->flags & ~O_NOFOLLOW); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file in the kernel). */ + if (fi->flags & O_DIRECT) + fi->direct_io = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + fuse_reply_open(req, fi); +} + +static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + (void) ino; + + close(fi->fh); + fuse_reply_err(req, 0); +} + +static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int res; + (void) ino; + res = close(dup(fi->fh)); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + int res; + (void) ino; + if (datasync) + res = fdatasync(fi->fh); + else + res = fsync(fi->fh); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, " + "off=%lu)\n", ino, size, (unsigned long) offset); + + buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = offset; + + fuse_reply_data(req, &buf, FUSE_BUF_SPLICE_MOVE); +} + +static void lo_write_buf(fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *in_buf, off_t off, + struct fuse_file_info *fi) +{ + (void) ino; + ssize_t res; + struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf)); + + out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + out_buf.buf[0].fd = fi->fh; + out_buf.buf[0].pos = off; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n", + ino, out_buf.buf[0].size, (unsigned long) off); + + res = fuse_buf_copy(&out_buf, in_buf, 0); + if(res < 0) + fuse_reply_err(req, -res); + else + fuse_reply_write(req, (size_t) res); +} + +static void lo_statfs(fuse_req_t req, fuse_ino_t ino) +{ + int res; + struct statvfs stbuf; + + res = fstatvfs(lo_fd(req, ino), &stbuf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statfs(req, &stbuf); +} + +static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + int err = EOPNOTSUPP; + (void) ino; + +#ifdef HAVE_FALLOCATE + err = fallocate(fi->fh, mode, offset, length); + if (err < 0) + err = errno; + +#elif defined(HAVE_POSIX_FALLOCATE) + if (mode) { + fuse_reply_err(req, EOPNOTSUPP); + return; + } + + err = posix_fallocate(fi->fh, offset, length); +#endif + + fuse_reply_err(req, err); +} + +static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, + int op) +{ + int res; + (void) ino; + + res = flock(fi->fh, op); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + char *value = NULL; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n", + ino, name, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + if (size) { + value = malloc(size); + if (!value) + goto out_err; + + ret = getxattr(procname, name, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = getxattr(procname, name, NULL, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + free(value); + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + char *value = NULL; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n", + ino, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + if (size) { + value = malloc(size); + if (!value) + goto out_err; + + ret = listxattr(procname, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = listxattr(procname, NULL, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + free(value); + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n", + ino, name, value, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + ret = setxattr(procname, name, value, size, flags); + saverr = ret == -1 ? errno : 0; + +out: + fuse_reply_err(req, saverr); +} + +static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n", + ino, name); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + ret = removexattr(procname, name); + saverr = ret == -1 ? errno : 0; + +out: + fuse_reply_err(req, saverr); +} + +#ifdef HAVE_COPY_FILE_RANGE +static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, + struct fuse_file_info *fi_in, + fuse_ino_t ino_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags) +{ + ssize_t res; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, " + "off=%lu, ino=%" PRIu64 "/fd=%lu, " + "off=%lu, size=%zd, flags=0x%x)\n", + ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out, + len, flags); + + res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, + flags); + if (res < 0) + fuse_reply_err(req, errno); + else + fuse_reply_write(req, res); +} +#endif + +static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi) +{ + off_t res; + + (void)ino; + res = lseek(fi->fh, off, whence); + if (res != -1) + fuse_reply_lseek(req, res); + else + fuse_reply_err(req, errno); +} + +static const struct fuse_lowlevel_ops lo_oper = { + .init = lo_init, + .destroy = lo_destroy, + .lookup = lo_lookup, + .mkdir = lo_mkdir, + .mknod = lo_mknod, + .symlink = lo_symlink, + .link = lo_link, + .unlink = lo_unlink, + .rmdir = lo_rmdir, + .rename = lo_rename, + .forget = lo_forget, + .forget_multi = lo_forget_multi, + .getattr = lo_getattr, + .setattr = lo_setattr, + .readlink = lo_readlink, + .opendir = lo_opendir, + .readdir = lo_readdir, + .readdirplus = lo_readdirplus, + .releasedir = lo_releasedir, + .fsyncdir = lo_fsyncdir, + .create = lo_create, + .tmpfile = lo_tmpfile, + .open = lo_open, + .release = lo_release, + .flush = lo_flush, + .fsync = lo_fsync, + .read = lo_read, + .write_buf = lo_write_buf, + .statfs = lo_statfs, + .fallocate = lo_fallocate, + .flock = lo_flock, + .getxattr = lo_getxattr, + .listxattr = lo_listxattr, + .setxattr = lo_setxattr, + .removexattr = lo_removexattr, +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = lo_copy_file_range, +#endif + .lseek = lo_lseek, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + struct lo_data lo = { .debug = 0, + .writeback = 0 }; + int ret = -1; + + /* Don't mask creation mode, kernel already did that */ + umask(0); + + pthread_mutex_init(&lo.mutex, NULL); + lo.root.next = lo.root.prev = &lo.root; + lo.root.fd = -1; + lo.cache = CACHE_NORMAL; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + passthrough_ll_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if(opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1) + return 1; + + lo.debug = opts.debug; + lo.root.refcount = 2; + if (lo.source) { + struct stat stat; + int res; + + res = lstat(lo.source, &stat); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n", + lo.source); + exit(1); + } + if (!S_ISDIR(stat.st_mode)) { + fuse_log(FUSE_LOG_ERR, "source is not a directory\n"); + exit(1); + } + + } else { + lo.source = strdup("/"); + if(!lo.source) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + exit(1); + } + } + if (!lo.timeout_set) { + switch (lo.cache) { + case CACHE_NEVER: + lo.timeout = 0.0; + break; + + case CACHE_NORMAL: + lo.timeout = 1.0; + break; + + case CACHE_ALWAYS: + lo.timeout = 86400.0; + break; + } + } else if (lo.timeout < 0) { + fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", + lo.timeout); + exit(1); + } + + lo.root.fd = open(lo.source, O_PATH); + if (lo.root.fd == -1) { + fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n", + lo.source); + exit(1); + } + + se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + if (lo.root.fd >= 0) + close(lo.root.fd); + + free(lo.source); + return ret ? 1 : 0; +} diff --git a/example/poll.c b/example/poll.c new file mode 100644 index 0000000..ffcb4f1 --- /dev/null +++ b/example/poll.c @@ -0,0 +1,304 @@ +/* + FUSE fsel: FUSE select example + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This example illustrates how to write a FUSE file system that + * supports polling for changes that don't come through the kernel. It + * can be tested with the poll_client.c program. + * + * Compile with: + * + * gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll + * + * ## Source code ## + * \include poll.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * fsel_open_mask is used to limit the number of opens to 1 per file. + * This is to use file index (0-F) as fh as poll support requires + * unique fh per open file. Lifting this would require proper open + * file management. + */ +static unsigned fsel_open_mask; +static const char fsel_hex_map[] = "0123456789ABCDEF"; +static struct fuse *fsel_fuse; /* needed for poll notification */ + +#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */ +#define FSEL_FILES 16 + +static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */ +static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */ +static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */ +static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */ +static _Atomic bool fsel_stop = false; +static pthread_t fsel_producer_thread; + + +static int fsel_path_index(const char *path) +{ + char ch = path[1]; + + if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch)) + return -1; + return ch <= '9' ? ch - '0' : ch - 'A' + 10; +} + +static void fsel_destroy(void *private_data) +{ + (void)private_data; + + fsel_stop = true; + + pthread_join(fsel_producer_thread, NULL); +} + +static int fsel_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int idx; + + memset(stbuf, 0, sizeof(struct stat)); + + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; + } + + idx = fsel_path_index(path); + if (idx < 0) + return -ENOENT; + + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = fsel_cnt[idx]; + return 0; +} + +static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + char name[2] = { }; + int i; + + (void) offset; + (void) fi; + (void) flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + for (i = 0; i < FSEL_FILES; i++) { + name[0] = fsel_hex_map[i]; + filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + } + + return 0; +} + +static int fsel_open(const char *path, struct fuse_file_info *fi) +{ + int idx = fsel_path_index(path); + + if (idx < 0) + return -ENOENT; + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + if (fsel_open_mask & (1 << idx)) + return -EBUSY; + fsel_open_mask |= (1 << idx); + + /* + * fsel files are nonseekable somewhat pipe-like files which + * gets filled up periodically by producer thread and consumed + * on read. Tell FUSE as such. + */ + fi->fh = idx; + fi->direct_io = 1; + fi->nonseekable = 1; + + return 0; +} + +static int fsel_release(const char *path, struct fuse_file_info *fi) +{ + int idx = fi->fh; + + (void) path; + + fsel_open_mask &= ~(1 << idx); + return 0; +} + +static int fsel_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int idx = fi->fh; + + (void) path; + (void) offset; + + pthread_mutex_lock(&fsel_mutex); + if (fsel_cnt[idx] < size) + size = fsel_cnt[idx]; + printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]); + fsel_cnt[idx] -= size; + pthread_mutex_unlock(&fsel_mutex); + + memset(buf, fsel_hex_map[idx], size); + return size; +} + +static int fsel_poll(const char *path, struct fuse_file_info *fi, + struct fuse_pollhandle *ph, unsigned *reventsp) +{ + static unsigned polled_zero; + int idx = fi->fh; + + (void) path; + + /* + * Poll notification requires pointer to struct fuse which + * can't be obtained when using fuse_main(). As notification + * happens only after poll is called, fill it here from + * fuse_context. + */ + if (!fsel_fuse) { + struct fuse_context *cxt = fuse_get_context(); + if (cxt) + fsel_fuse = cxt->fuse; + } + + pthread_mutex_lock(&fsel_mutex); + + if (ph != NULL) { + struct fuse_pollhandle *oldph = fsel_poll_handle[idx]; + + if (oldph) + fuse_pollhandle_destroy(oldph); + + fsel_poll_notify_mask |= (1 << idx); + fsel_poll_handle[idx] = ph; + } + + if (fsel_cnt[idx]) { + *reventsp |= POLLIN; + printf("POLL %X cnt=%u polled_zero=%u\n", + idx, fsel_cnt[idx], polled_zero); + polled_zero = 0; + } else + polled_zero++; + + pthread_mutex_unlock(&fsel_mutex); + return 0; +} + +static const struct fuse_operations fsel_oper = { + .destroy = fsel_destroy, + .getattr = fsel_getattr, + .readdir = fsel_readdir, + .open = fsel_open, + .release = fsel_release, + .read = fsel_read, + .poll = fsel_poll, +}; + +static void *fsel_producer(void *data) +{ + const struct timespec interval = { 0, 250000000 }; + unsigned idx = 0, nr = 1; + + (void) data; + + while (!fsel_stop) { + int i, t; + + pthread_mutex_lock(&fsel_mutex); + + /* + * This is the main producer loop which is executed + * ever 500ms. On each iteration, it fills one byte + * to 1, 2 or 4 files and sends poll notification if + * requested. + */ + for (i = 0, t = idx; i < nr; + i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) { + if (fsel_cnt[t] == FSEL_CNT_MAX) + continue; + + fsel_cnt[t]++; + if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) { + struct fuse_pollhandle *ph; + + printf("NOTIFY %X\n", t); + ph = fsel_poll_handle[t]; + fuse_notify_poll(ph); + fuse_pollhandle_destroy(ph); + fsel_poll_notify_mask &= ~(1 << t); + fsel_poll_handle[t] = NULL; + } + } + + idx = (idx + 1) % FSEL_FILES; + if (idx == 0) + nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */ + + pthread_mutex_unlock(&fsel_mutex); + + nanosleep(&interval, NULL); + } + + return NULL; +} + +int main(int argc, char *argv[]) +{ + pthread_attr_t attr; + int ret; + + errno = pthread_mutex_init(&fsel_mutex, NULL); + if (errno) { + perror("pthread_mutex_init"); + return 1; + } + + errno = pthread_attr_init(&attr); + if (errno) { + perror("pthread_attr_init"); + return 1; + } + + errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL); + if (errno) { + perror("pthread_create"); + return 1; + } + + ret = fuse_main(argc, argv, &fsel_oper, NULL); + + return ret; +} diff --git a/example/poll_client.c b/example/poll_client.c new file mode 100644 index 0000000..83c5823 --- /dev/null +++ b/example/poll_client.c @@ -0,0 +1,84 @@ +/* + FUSE fselclient: FUSE select example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * This program tests the poll.c example file systsem. + * + * Compile with: + * + * gcc -Wall poll_client.c -o poll_client + * + * ## Source code ## + * \include poll_client.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FSEL_FILES 16 + +int main(void) +{ + static const char hex_map[FSEL_FILES] = "0123456789ABCDEF"; + int fds[FSEL_FILES]; + int i, nfds, tries; + + for (i = 0; i < FSEL_FILES; i++) { + char name[] = { hex_map[i], '\0' }; + fds[i] = open(name, O_RDONLY); + if (fds[i] < 0) { + perror("open"); + return 1; + } + } + nfds = fds[FSEL_FILES - 1] + 1; + + for(tries=0; tries < 16; tries++) { + static char buf[4096]; + fd_set rfds; + int rc; + + FD_ZERO(&rfds); + for (i = 0; i < FSEL_FILES; i++) + FD_SET(fds[i], &rfds); + + rc = select(nfds, &rfds, NULL, NULL, NULL); + + if (rc < 0) { + perror("select"); + return 1; + } + + for (i = 0; i < FSEL_FILES; i++) { + if (!FD_ISSET(fds[i], &rfds)) { + printf("_: "); + continue; + } + printf("%X:", i); + rc = read(fds[i], buf, sizeof(buf)); + if (rc < 0) { + perror("read"); + return 1; + } + printf("%02d ", rc); + } + printf("\n"); + } + return 0; +} diff --git a/example/printcap.c b/example/printcap.c new file mode 100644 index 0000000..82a7598 --- /dev/null +++ b/example/printcap.c @@ -0,0 +1,136 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2017 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +/** @file + * + * minimal example filesystem that prints out all capabilities + * supported by the kernel and then exits. + * + * Compile with: + * + * gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap + * + * ## Source code ## + * \include printcap.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include + +struct fuse_session *se; + +// Define a structure to hold capability information +struct cap_info { + uint64_t flag; + const char *name; +}; + +// Define an array of all capabilities +static const struct cap_info capabilities[] = { + {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"}, + {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"}, + {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"}, + {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"}, + {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"}, + {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"}, + {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"}, + {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"}, + {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"}, + {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"}, + {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"}, + {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"}, + {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"}, + {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"}, + {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"}, + {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"}, + {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"}, + {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"}, + {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"}, + {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"}, + {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"}, + {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"}, + {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"}, + {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"}, + {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"}, + {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"}, + {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"}, + {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"}, + // Add any new capabilities here + {0, NULL} // Sentinel to mark the end of the array +}; + +static void print_capabilities(struct fuse_conn_info *conn) +{ + printf("Capabilities:\n"); + for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) { + if (fuse_get_feature_flag(conn, cap->flag)) { + printf("\t%s\n", cap->name); + } + } +} + +static void pc_init(void *userdata, struct fuse_conn_info *conn) +{ + (void) userdata; + + printf("Protocol version: %d.%d\n", conn->proto_major, + conn->proto_minor); + print_capabilities(conn); + fuse_session_exit(se); +} + + +static const struct fuse_lowlevel_ops pc_oper = { + .init = pc_init, +}; + +int main(int argc, char **argv) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + char *mountpoint; + int ret = -1; + + mountpoint = strdup("/tmp/fuse_printcap_XXXXXX"); + if(mkdtemp(mountpoint) == NULL) { + perror("mkdtemp"); + return 1; + } + + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + + se = fuse_session_new(&args, &pc_oper, + sizeof(pc_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, mountpoint) != 0) + goto err_out3; + + ret = fuse_session_loop(se); + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + rmdir(mountpoint); + free(mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/include/cuse_lowlevel.h b/include/cuse_lowlevel.h new file mode 100644 index 0000000..80476c2 --- /dev/null +++ b/include/cuse_lowlevel.h @@ -0,0 +1,87 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. + + Read example/cusexmp.c for usages. +*/ + +#ifndef CUSE_LOWLEVEL_H_ +#define CUSE_LOWLEVEL_H_ + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION 29 +#endif + +#include "fuse_lowlevel.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */ + +struct fuse_session; + +struct cuse_info { + unsigned dev_major; + unsigned dev_minor; + unsigned dev_info_argc; + const char **dev_info_argv; + unsigned flags; +}; + +/* + * Most ops behave almost identically to the matching fuse_lowlevel + * ops except that they don't take @ino. + * + * init_done : called after initialization is complete + * read/write : always direct IO, simultaneous operations allowed + * ioctl : might be in unrestricted mode depending on ci->flags + */ +struct cuse_lowlevel_ops { + void (*init) (void *userdata, struct fuse_conn_info *conn); + void (*init_done) (void *userdata); + void (*destroy) (void *userdata); + void (*open) (fuse_req_t req, struct fuse_file_info *fi); + void (*read) (fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi); + void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off, + struct fuse_file_info *fi); + void (*flush) (fuse_req_t req, struct fuse_file_info *fi); + void (*release) (fuse_req_t req, struct fuse_file_info *fi); + void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi); + void (*ioctl) (fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); + void (*poll) (fuse_req_t req, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); +}; + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata); + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata); + +void cuse_lowlevel_teardown(struct fuse_session *se); + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* CUSE_LOWLEVEL_H_ */ diff --git a/include/fuse.h b/include/fuse.h new file mode 100644 index 0000000..4582cc7 --- /dev/null +++ b/include/fuse.h @@ -0,0 +1,1415 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#ifndef FUSE_H_ +#define FUSE_H_ + +/** @file + * + * This file defines the library interface of FUSE + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this header. + */ + +#include "fuse_common.h" + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Basic FUSE API * + * ----------------------------------------------------------- */ + +/** Handle for a FUSE filesystem */ +struct fuse; + +/** + * Readdir flags, passed to ->readdir() + */ +enum fuse_readdir_flags { + /** + * "Plus" mode. + * + * The kernel wants to prefill the inode cache during readdir. The + * filesystem may honour this by filling in the attributes and setting + * FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also + * just ignore this flag completely. + */ + FUSE_READDIR_DEFAULTS = 0, + FUSE_READDIR_PLUS = (1 << 0) +}; + +/** + * Readdir flags, passed to fuse_fill_dir_t callback. + */ +enum fuse_fill_dir_flags { + /** + * "Plus" mode: all file attributes are valid + * + * The attributes are used by the kernel to prefill the inode cache + * during a readdir. + * + * It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set + * and vice versa. + */ + FUSE_FILL_DIR_DEFAULTS = 0, + FUSE_FILL_DIR_PLUS = (1 << 1) +}; + +/** Function to add an entry in a readdir() operation + * + * The *off* parameter can be any non-zero value that enables the + * filesystem to identify the current point in the directory + * stream. It does not need to be the actual physical position. A + * value of zero is reserved to indicate that seeking in directories + * is not supported. + * + * @param buf the buffer passed to the readdir() operation + * @param name the file name of the directory entry + * @param stbuf file attributes, can be NULL + * @param off offset of the next entry or zero + * @param flags fill flags + * @return 1 if buffer is full, zero otherwise + */ +typedef int (*fuse_fill_dir_t) (void *buf, const char *name, + const struct stat *stbuf, off_t off, + enum fuse_fill_dir_flags flags); +/** + * Configuration of the high-level API + * + * This structure is initialized from the arguments passed to + * fuse_new(), and then passed to the file system's init() handler + * which should ensure that the configuration is compatible with the + * file system implementation. + * + * Note: this data structure is ABI sensitive, new options have to be + * appended at the end of the structure + */ +struct fuse_config { + /** + * If `set_gid` is non-zero, the st_gid attribute of each file + * is overwritten with the value of `gid`. + */ + int32_t set_gid; + uint32_t gid; + + /** + * If `set_uid` is non-zero, the st_uid attribute of each file + * is overwritten with the value of `uid`. + */ + int32_t set_uid; + uint32_t uid; + + /** + * If `set_mode` is non-zero, the any permissions bits set in + * `umask` are unset in the st_mode attribute of each file. + */ + int32_t set_mode; + uint32_t umask; + + /** + * The timeout in seconds for which name lookups will be + * cached. + */ + double entry_timeout; + + /** + * The timeout in seconds for which a negative lookup will be + * cached. This means, that if file did not exist (lookup + * returned ENOENT), the lookup will only be redone after the + * timeout, and the file/directory will be assumed to not + * exist until then. A value of zero means that negative + * lookups are not cached. + */ + double negative_timeout; + + /** + * The timeout in seconds for which file/directory attributes + * (as returned by e.g. the `getattr` handler) are cached. + */ + double attr_timeout; + + /** + * Allow requests to be interrupted + */ + int32_t intr; + + /** + * Specify which signal number to send to the filesystem when + * a request is interrupted. The default is hardcoded to + * USR1. + */ + int32_t intr_signal; + + /** + * Normally, FUSE assigns inodes to paths only for as long as + * the kernel is aware of them. With this option inodes are + * instead remembered for at least this many seconds. This + * will require more memory, but may be necessary when using + * applications that make use of inode numbers. + * + * A number of -1 means that inodes will be remembered for the + * entire life-time of the file-system process. + */ + int32_t remember; + + /** + * The default behavior is that if an open file is deleted, + * the file is renamed to a hidden file (.fuse_hiddenXXX), and + * only removed when the file is finally released. This + * relieves the filesystem implementation of having to deal + * with this problem. This option disables the hiding + * behavior, and files are removed immediately in an unlink + * operation (or in a rename operation which overwrites an + * existing file). + * + * It is recommended that you not use the hard_remove + * option. When hard_remove is set, the following libc + * functions fail on unlinked files (returning errno of + * ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2), + * ftruncate(2), fstat(2), fchmod(2), fchown(2) + */ + int32_t hard_remove; + + /** + * Honor the st_ino field in the functions getattr() and + * fill_dir(). This value is used to fill in the st_ino field + * in the stat(2), lstat(2), fstat(2) functions and the d_ino + * field in the readdir(2) function. The filesystem does not + * have to guarantee uniqueness, however some applications + * rely on this value being unique for the whole filesystem. + * + * Note that this does *not* affect the inode that libfuse + * and the kernel use internally (also called the "nodeid"). + */ + int32_t use_ino; + + /** + * If use_ino option is not given, still try to fill in the + * d_ino field in readdir(2). If the name was previously + * looked up, and is still in the cache, the inode number + * found there will be used. Otherwise it will be set to -1. + * If use_ino option is given, this option is ignored. + */ + int32_t readdir_ino; + + /** + * This option disables the use of page cache (file content cache) + * in the kernel for this filesystem. This has several affects: + * + * 1. Each read(2) or write(2) system call will initiate one + * or more read or write operations, data will not be + * cached in the kernel. + * + * 2. The return value of the read() and write() system calls + * will correspond to the return values of the read and + * write operations. This is useful for example if the + * file size is not known in advance (before reading it). + * + * Internally, enabling this option causes fuse to set the + * `direct_io` field of `struct fuse_file_info` - overwriting + * any value that was put there by the file system. + */ + int32_t direct_io; + + /** + * This option disables flushing the cache of the file + * contents on every open(2). This should only be enabled on + * filesystems where the file data is never changed + * externally (not through the mounted FUSE filesystem). Thus + * it is not suitable for network filesystems and other + * intermediate filesystems. + * + * NOTE: if this option is not specified (and neither + * direct_io) data is still cached after the open(2), so a + * read(2) system call will not always initiate a read + * operation. + * + * Internally, enabling this option causes fuse to set the + * `keep_cache` field of `struct fuse_file_info` - overwriting + * any value that was put there by the file system. + */ + int32_t kernel_cache; + + /** + * This option is an alternative to `kernel_cache`. Instead of + * unconditionally keeping cached data, the cached data is + * invalidated on open(2) if if the modification time or the + * size of the file has changed since it was last opened. + */ + int32_t auto_cache; + + /* + * The timeout in seconds for which file attributes are cached + * for the purpose of checking if auto_cache should flush the + * file data on open. + */ + int32_t ac_attr_timeout_set; + double ac_attr_timeout; + + /** + * If this option is given the file-system handlers for the + * following operations will not receive path information: + * read, write, flush, release, fallocate, fsync, readdir, + * releasedir, fsyncdir, lock, ioctl and poll. + * + * For the truncate, getattr, chmod, chown and utimens + * operations the path will be provided only if the struct + * fuse_file_info argument is NULL. + */ + int32_t nullpath_ok; + + /** + * These 3 options are used by libfuse internally and + * should not be touched. + */ + int32_t show_help; + char *modules; + int32_t debug; + + /** + * `fmask` and `dmask` function the same way as `umask`, but apply + * to files and directories separately. If non-zero, `fmask` and + * `dmask` take precedence over the `umask` setting. + */ + uint32_t fmask; + uint32_t dmask; + + /** + * By default, fuse waits for all pending writes to complete + * and calls the FLUSH operation on close(2) of every fuse fd. + * With this option, wait and FLUSH are not done for read-only + * fuse fd, similar to the behavior of NFS/SMB clients. + */ + int32_t no_rofd_flush; + + /** + * Allow parallel direct-io writes to operate on the same file. + * + * FUSE implementations which do not handle parallel writes on + * same file/region should NOT enable this option at all as it + * might lead to data inconsistencies. + * + * For the FUSE implementations which have their own mechanism + * of cache/data integrity are beneficiaries of this setting as + * it now open doors to parallel writes on the same file (without + * enabling this setting, all direct writes on the same file are + * serialized, resulting in huge data bandwidth loss). + */ + int32_t parallel_direct_writes; + + + /** + * Reserved for future use. + */ + uint32_t flags; + + /** + * Reserved for future use. + */ + uint64_t reserved[48]; +}; + + +/** + * The file system operations: + * + * Most of these should work very similarly to the well known UNIX + * file system operations. A major exception is that instead of + * returning an error in 'errno', the operation should return the + * negated error value (-errno) directly. + * + * All methods are optional, but some are essential for a useful + * filesystem (e.g. getattr). Open, flush, release, fsync, opendir, + * releasedir, fsyncdir, access, create, truncate, lock, init and + * destroy are special purpose methods, without which a full featured + * filesystem can still be implemented. + * + * In general, all methods are expected to perform any necessary + * permission checking. However, a filesystem may delegate this task + * to the kernel by passing the `default_permissions` mount option to + * `fuse_new()`. In this case, methods will only be called if + * the kernel's permission check has succeeded. + * + * Almost all operations take a path which can be of any length. + */ +struct fuse_operations { + /** Get file attributes. + * + * Similar to stat(). The 'st_dev' and 'st_blksize' fields are + * ignored. The 'st_ino' field is ignored except if the 'use_ino' + * mount option is given. In that case it is passed to userspace, + * but libfuse and the kernel will still assign a different + * inode for internal use (called the "nodeid"). + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + */ + int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi); + + /** Read the target of a symbolic link + * + * The buffer should be filled with a null terminated string. The + * buffer size argument includes the space for the terminating + * null character. If the linkname is too long to fit in the + * buffer, it should be truncated. The return value should be 0 + * for success. + */ + int (*readlink) (const char *, char *, size_t); + + /** Create a file node + * + * This is called for creation of all non-directory, non-symlink + * nodes. If the filesystem defines a create() method, then for + * regular files that will be called instead. + */ + int (*mknod) (const char *, mode_t, dev_t); + + /** Create a directory + * + * Note that the mode argument may not have the type specification + * bits set, i.e. S_ISDIR(mode) can be false. To obtain the + * correct directory type bits use mode|S_IFDIR + * */ + int (*mkdir) (const char *, mode_t); + + /** Remove a file */ + int (*unlink) (const char *); + + /** Remove a directory */ + int (*rmdir) (const char *); + + /** Create a symbolic link */ + int (*symlink) (const char *, const char *); + + /** Rename a file + * + * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If + * RENAME_NOREPLACE is specified, the filesystem must not + * overwrite *newname* if it exists and return an error + * instead. If `RENAME_EXCHANGE` is specified, the filesystem + * must atomically exchange the two files, i.e. both must + * exist and neither may be deleted. + */ + int (*rename) (const char *, const char *, unsigned int flags); + + /** Create a hard link to a file */ + int (*link) (const char *, const char *); + + /** Change the permission bits of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + */ + int (*chmod) (const char *, mode_t, struct fuse_file_info *fi); + + /** Change the owner and group of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi); + + /** Change the size of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*truncate) (const char *, off_t, struct fuse_file_info *fi); + + /** Open a file + * + * Open flags are available in fi->flags. The following rules + * apply. + * + * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be + * filtered out / handled by the kernel. + * + * - Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) + * should be used by the filesystem to check if the operation is + * permitted. If the ``-o default_permissions`` mount option is + * given, this check is already done by the kernel before calling + * open() and may thus be omitted by the filesystem. + * + * - When writeback caching is enabled, the kernel may send + * read requests even for files opened with O_WRONLY. The + * filesystem should be prepared to handle this. + * + * - When writeback caching is disabled, the filesystem is + * expected to properly handle the O_APPEND flag and ensure + * that each write is appending to the end of the file. + * + * - When writeback caching is enabled, the kernel will + * handle O_APPEND. However, unless all changes to the file + * come through the kernel this will not work reliably. The + * filesystem should thus either ignore the O_APPEND flag + * (and let the kernel handle it), or return an error + * (indicating that reliably O_APPEND is not available). + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in for more details. + * + * If this request is answered with an error code of ENOSYS + * and FUSE_CAP_NO_OPEN_SUPPORT is set in + * `fuse_conn_info.capable`, this is treated as success and + * future calls to open will also succeed without being sent + * to the filesystem process. + * + */ + int (*open) (const char *, struct fuse_file_info *); + + /** Read data from an open file + * + * Read should return exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the + * 'direct_io' mount option is specified, in which case the return + * value of the read system call will reflect the return value of + * this operation. + */ + int (*read) (const char *, char *, size_t, off_t, + struct fuse_file_info *); + + /** Write data to an open file + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the 'direct_io' + * mount option is specified (see read operation). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*write) (const char *, const char *, size_t, off_t, + struct fuse_file_info *); + + /** Get file system statistics + * + * The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored + */ + int (*statfs) (const char *, struct statvfs *); + + /** Possibly flush cached data + * + * BIG NOTE: This is not equivalent to fsync(). It's not a + * request to sync dirty data. + * + * Flush is called on each close() of a file descriptor, as opposed to + * release which is called on the close of the last file descriptor for + * a file. Under Linux, errors returned by flush() will be passed to + * userspace as errors from close(), so flush() is a good place to write + * back any cached dirty data. However, many applications ignore errors + * on close(), and on non-Linux systems, close() may succeed even if flush() + * returns an error. For these reasons, filesystems should not assume + * that errors returned by flush will ever be noticed or even + * delivered. + * + * NOTE: The flush() method may be called more than once for each + * open(). This happens if more than one file descriptor refers to an + * open file handle, e.g. due to dup(), dup2() or fork() calls. It is + * not possible to determine if a flush is final, so each flush should + * be treated equally. Multiple write-flush sequences are relatively + * rare, so this shouldn't be a problem. + * + * Filesystems shouldn't assume that flush will be called at any + * particular point. It may be called more times than expected, or not + * at all. + * + * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html + */ + int (*flush) (const char *, struct fuse_file_info *); + + /** Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open() call there will be exactly one release() call + * with the same flags and file handle. It is possible to + * have a file opened more than once, in which case only the last + * release will mean, that no more reads/writes will happen on the + * file. The return value of release is ignored. + */ + int (*release) (const char *, struct fuse_file_info *); + + /** Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + */ + int (*fsync) (const char *, int, struct fuse_file_info *); + + /** Set extended attributes */ + int (*setxattr) (const char *, const char *, const char *, size_t, int); + + /** Get extended attributes */ + int (*getxattr) (const char *, const char *, char *, size_t); + + /** List extended attributes */ + int (*listxattr) (const char *, char *, size_t); + + /** Remove extended attributes */ + int (*removexattr) (const char *, const char *); + + /** Open directory + * + * Unless the 'default_permissions' mount option is given, + * this method should check if opendir is permitted for this + * directory. Optionally opendir may also return an arbitrary + * filehandle in the fuse_file_info structure, which will be + * passed to readdir, releasedir and fsyncdir. + */ + int (*opendir) (const char *, struct fuse_file_info *); + + /** Read directory + * + * The filesystem may choose between two modes of operation: + * + * 1) The readdir implementation ignores the offset parameter, and + * passes zero to the filler function's offset. The filler + * function will not return '1' (unless an error happens), so the + * whole directory is read in a single readdir operation. + * + * 2) The readdir implementation keeps track of the offsets of the + * directory entries. It uses the offset parameter and always + * passes non-zero offset to the filler function. When the buffer + * is full (or an error happens) the filler function will return + * '1'. + * + * When FUSE_READDIR_PLUS is not set, only some parameters of the + * fill function (the fuse_fill_dir_t parameter) are actually used: + * The file type (which is part of stat::st_mode) is used. And if + * fuse_config::use_ino is set, the inode (stat::st_ino) is also + * used. The other fields are ignored when FUSE_READDIR_PLUS is not + * set. + */ + int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, + struct fuse_file_info *, enum fuse_readdir_flags); + + /** Release directory + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. + */ + int (*releasedir) (const char *, struct fuse_file_info *); + + /** Synchronize directory contents + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data + */ + int (*fsyncdir) (const char *, int, struct fuse_file_info *); + + /** + * Initialize filesystem + * + * The return value will passed in the `private_data` field of + * `struct fuse_context` to all file operations, and as a + * parameter to the destroy() method. It overrides the initial + * value provided to fuse_main() / fuse_new(). + */ + void *(*init) (struct fuse_conn_info *conn, + struct fuse_config *cfg); + + /** + * Clean up filesystem + * + * Called on filesystem exit. + */ + void (*destroy) (void *private_data); + + /** + * Check file access permissions + * + * This will be called for the access() system call. If the + * 'default_permissions' mount option is given, this method is not + * called. + * + * This method is not called under Linux kernel versions 2.4.x + */ + int (*access) (const char *, int); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + */ + int (*create) (const char *, mode_t, struct fuse_file_info *); + + /** + * Perform POSIX file locking operation + * + * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW. + * + * For the meaning of fields in 'struct flock' see the man page + * for fcntl(2). The l_whence field will always be set to + * SEEK_SET. + * + * For checking lock ownership, the 'fuse_file_info->owner' + * argument must be used. + * + * For F_GETLK operation, the library will first check currently + * held locks, and if a conflicting lock is found it will return + * information without calling this method. This ensures, that + * for local locks the l_pid field is correctly filled in. The + * results may not be accurate in case of race conditions and in + * the presence of hard links, but it's unlikely that an + * application would rely on accurate GETLK results in these + * cases. If a conflicting lock is not found, this method will be + * called, and the filesystem may fill out l_pid by a meaningful + * value, or it may leave this field zero. + * + * For F_SETLK and F_SETLKW the l_pid field will be set to the pid + * of the process performing the locking operation. + * + * Note: if this method is not implemented, the kernel will still + * allow file locking to work locally. Hence it is only + * interesting for network filesystems and similar. + */ + int (*lock) (const char *, struct fuse_file_info *, int cmd, + struct flock *); + + /** + * Change the access and modification times of a file with + * nanosecond resolution + * + * This supersedes the old utime() interface. New applications + * should use this. + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * See the utimensat(2) man page for details. + */ + int (*utimens) (const char *, const struct timespec tv[2], + struct fuse_file_info *fi); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + */ + int (*bmap) (const char *, size_t blocksize, uint64_t *idx); + +#if FUSE_USE_VERSION < 35 + int (*ioctl) (const char *, int cmd, void *arg, + struct fuse_file_info *, unsigned int flags, void *data); +#else + /** + * Ioctl + * + * flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in + * 64bit environment. The size and direction of data is + * determined by _IOC_*() decoding of cmd. For _IOC_NONE, + * data will be NULL, for _IOC_WRITE data is out area, for + * _IOC_READ in area and if both are set in/out area. In all + * non-NULL cases, the area is of _IOC_SIZE(cmd) bytes. + * + * If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a + * directory file handle. + * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits. + */ + int (*ioctl) (const char *, unsigned int cmd, void *arg, + struct fuse_file_info *, unsigned int flags, void *data); +#endif + + /** + * Poll for IO readiness events + * + * Note: If ph is non-NULL, the client should notify + * when IO readiness events occur by calling + * fuse_notify_poll() with the specified ph. + * + * Regardless of the number of times poll with a non-NULL ph + * is received, single notification is enough to clear all. + * Notifying more times incurs overhead but doesn't harm + * correctness. + * + * The callee is responsible for destroying ph with + * fuse_pollhandle_destroy() when no longer in use. + */ + int (*poll) (const char *, struct fuse_file_info *, + struct fuse_pollhandle *ph, unsigned *reventsp); + + /** Write contents of buffer to an open file + * + * Similar to the write() method, but data is supplied in a + * generic buffer. Use fuse_buf_copy() to transfer data to + * the destination. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *); + + /** Store data from an open file in a buffer + * + * Similar to the read() method, but data is stored and + * returned in a generic buffer. + * + * No actual copying of data has to take place, the source + * file descriptor may simply be stored in the buffer for + * later data transfer. + * + * The buffer must be allocated dynamically and stored at the + * location pointed to by bufp. If the buffer contains memory + * regions, they too must be allocated using malloc(). The + * allocated memory will be freed by the caller. + */ + int (*read_buf) (const char *, struct fuse_bufvec **bufp, + size_t size, off_t off, struct fuse_file_info *); + /** + * Perform BSD file locking operation + * + * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN + * + * Nonblocking requests will be indicated by ORing LOCK_NB to + * the above operations + * + * For more information see the flock(2) manual page. + * + * Additionally fi->owner will be set to a value unique to + * this open file. This same value will be supplied to + * ->release() when the file is released. + * + * Note: if this method is not implemented, the kernel will still + * allow file locking to work locally. Hence it is only + * interesting for network filesystems and similar. + */ + int (*flock) (const char *, struct fuse_file_info *, int op); + + /** + * Allocates space for an open file + * + * This function ensures that required space is allocated for specified + * file. If this function returns success then any subsequent write + * request to specified range is guaranteed not to fail because of lack + * of space on the file system media. + */ + int (*fallocate) (const char *, int, off_t, off_t, + struct fuse_file_info *); + + /** + * Copy a range of data from one file to another + * + * Performs an optimized copy between two file descriptors without the + * additional cost of transferring data through the FUSE kernel module + * to user space (glibc) and then back into the FUSE filesystem again. + * + * In case this method is not implemented, applications are expected to + * fall back to a regular file copy. (Some glibc versions did this + * emulation automatically, but the emulation has been removed from all + * glibc release branches.) + */ + ssize_t (*copy_file_range) (const char *path_in, + struct fuse_file_info *fi_in, + off_t offset_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t offset_out, size_t size, int flags); + + /** + * Find next data or hole after the specified offset + */ + off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *); +}; + +/** Extra context that may be needed by some filesystems + * + * The uid, gid and pid fields are not filled in case of a writepage + * operation. + */ +struct fuse_context { + /** Pointer to the fuse object */ + struct fuse *fuse; + + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** Process ID of the calling thread */ + pid_t pid; + + /** Private filesystem data */ + void *private_data; + + /** Umask of the calling process */ + mode_t umask; +}; + +/** + * The real main function + * + * Do not call this directly, use fuse_main() + */ +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +static inline int fuse_main_real(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 }; + + fuse_log(FUSE_LOG_ERR, + "%s is a libfuse internal function, please use fuse_main()\n", + __func__); + + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); +} + +/** + * Main function of FUSE. + * + * This is for the lazy. This is all that has to be called from the + * main() function. + * + * This function does the following: + * - parses command line options, and handles --help and + * --version + * - installs signal handlers for INT, HUP, TERM and PIPE + * - registers an exit handler to unmount the filesystem on program exit + * - creates a fuse handle + * - registers the operations + * - calls either the single-threaded or the multi-threaded event loop + * + * Most file systems will have to parse some file-system specific + * arguments before calling this function. It is recommended to do + * this with fuse_opt_parse() and a processing function that passes + * through any unknown options (this can also be achieved by just + * passing NULL as the processing function). That way, the remaining + * options can be passed directly to fuse_main(). + * + * fuse_main() accepts all options that can be passed to + * fuse_parse_cmdline(), fuse_new(), or fuse_session_new(). + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. This element must always be present and is used to + * construct a basic ``usage: `` message for the --help + * output. argv[0] may also be set to the empty string. In this case + * the usage message is suppressed. This can be used by file systems + * to print their own usage line first. See hello.c for an example of + * how to do this. + * + * Note: this is currently implemented as a macro. + * + * The following error codes may be returned from fuse_main(): + * 1: Invalid option arguments + * 2: No mount point specified + * 3: FUSE setup failed + * 4: Mounting failed + * 5: Failed to daemonize (detach from session) + * 6: Failed to set up signal handlers + * 7: An error occurred during the life of the file system + * + * @param argc the argument counter passed to the main() function + * @param argv the argument vector passed to the main() function + * @param op the file system operation + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return 0 on success, nonzero on failure + * + * Example usage, see hello.c + */ +static inline int fuse_main_fn(int argc, char *argv[], + const struct fuse_operations *op, + void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version, + user_data); +} +#define fuse_main(argc, argv, op, user_data) \ + fuse_main_fn(argc, argv, op, user_data) + +/* ----------------------------------------------------------- * + * More detailed API * + * ----------------------------------------------------------- */ + +/** + * Print available options (high- and low-level) to stdout. This is + * not an exhaustive list, but includes only those options that may be + * of interest to an end-user of a file system. + * + * The function looks at the argument vector only to determine if + * there are additional modules to be loaded (module=foo option), + * and attempts to call their help functions as well. + * + * @param args the argument vector. + */ +void fuse_lib_help(struct fuse_args *args); + +/* Do not call this directly, use fuse_new() instead */ +struct fuse *_fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); + +/** + * Create a new FUSE filesystem. + * + * This function accepts most file-system independent mount options + * (like context, nodev, ro - see mount(8)), as well as the + * FUSE-specific mount options from mount.fuse(8). + * + * If the --help option is specified, the function writes a help text + * to stdout and returns NULL. + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. This element must always be present and is used to + * construct a basic ``usage: `` message for the --help output. If + * argv[0] is set to the empty string, no usage message is included in + * the --help output. + * + * If an unknown option is passed in, an error message is written to + * stderr and the function returns NULL. + * + * @param args argument vector + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return the created FUSE handle + */ +#if FUSE_USE_VERSION == 30 +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return _fuse_new_30(args, op, op_size, &version, user_data); +} +#else /* FUSE_USE_VERSION */ +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return _fuse_new_31(args, op, op_size, &version, user_data); +} +#endif +#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data) + +/** + * Mount a FUSE file system. + * + * @param mountpoint the mount point path + * @param f the FUSE handle + * + * @return 0 on success, -1 on failure. + **/ +int fuse_mount(struct fuse *f, const char *mountpoint); + +/** + * Unmount a FUSE file system. + * + * See fuse_session_unmount() for additional information. + * + * @param f the FUSE handle + **/ +void fuse_unmount(struct fuse *f); + +/** + * Destroy the FUSE handle. + * + * NOTE: This function does not unmount the filesystem. If this is + * needed, call fuse_unmount() before calling this function. + * + * @param f the FUSE handle + */ +void fuse_destroy(struct fuse *f); + +/** + * FUSE event loop. + * + * Requests from the kernel are processed, and the appropriate + * operations are called. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * @param f the FUSE handle + * @return see fuse_session_loop() + * + * See also: fuse_loop_mt() + */ +int fuse_loop(struct fuse *f); + +/** + * Flag session as terminated + * + * This function will cause any running event loops to exit on + * the next opportunity. + * + * @param f the FUSE handle + */ +void fuse_exit(struct fuse *f); + +#if FUSE_USE_VERSION < 32 +int fuse_loop_mt_31(struct fuse *f, int clone_fd); +#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd) +#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config); +#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config) +#else +/** + * FUSE event loop with multiple threads + * + * Requests from the kernel are processed, and the appropriate + * operations are called. Request are processed in parallel by + * distributing them between multiple threads. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * Note: using fuse_loop() instead of fuse_loop_mt() means you are running in + * single-threaded mode, and that you will not have to worry about reentrancy, + * though you will have to worry about recursive lookups. In single-threaded + * mode, FUSE will wait for one callback to return before calling another. + * + * Enabling multiple threads, by using fuse_loop_mt(), will cause FUSE to make + * multiple simultaneous calls into the various callback functions given by your + * fuse_operations record. + * + * If you are using multiple threads, you can enjoy all the parallel execution + * and interactive response benefits of threads, and you get to enjoy all the + * benefits of race conditions and locking bugs, too. Ensure that any code used + * in the callback function of fuse_operations is also thread-safe. + * + * @param f the FUSE handle + * @param config loop configuration, may be NULL and defaults will be used then + * @return see fuse_session_loop() + * + * See also: fuse_loop() + */ +#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config); +#else +#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config) +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ +#endif + + +/** + * Get the current context + * + * The context is only valid for the duration of a filesystem + * operation, and thus must not be stored and used later. + * + * @return the context + */ +struct fuse_context *fuse_get_context(void); + +/** + * Get the current supplementary group IDs for the current request + * + * Similar to the getgroups(2) system call, except the return value is + * always the total number of group IDs, even if it is larger than the + * specified size. + * + * The current fuse kernel module in linux (as of 2.6.30) doesn't pass + * the group list to userspace, hence this function needs to parse + * "/proc/$TID/task/$TID/status" to get the group IDs. + * + * This feature may not be supported on all operating systems. In + * such a case this function will return -ENOSYS. + * + * @param size size of given array + * @param list array of group IDs to be filled in + * @return the total number of supplementary group IDs or -errno on failure + */ +int fuse_getgroups(int size, gid_t list[]); + +/** + * Check if the current request has already been interrupted + * + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_interrupted(void); + +/** + * Invalidates cache for the given path. + * + * This calls fuse_lowlevel_notify_inval_inode internally. + * + * @return 0 on successful invalidation, negative error value otherwise. + * This routine may return -ENOENT to indicate that there was + * no entry to be invalidated, e.g., because the path has not + * been seen before or has been forgotten; this should not be + * considered to be an error. + */ +int fuse_invalidate_path(struct fuse *f, const char *path); + +/** + * Start the cleanup thread when using option "remember". + * + * This is done automatically by fuse_loop_mt() + * @param fuse struct fuse pointer for fuse instance + * @return 0 on success and -1 on error + */ +int fuse_start_cleanup_thread(struct fuse *fuse); + +/** + * Stop the cleanup thread when using option "remember". + * + * This is done automatically by fuse_loop_mt() + * @param fuse struct fuse pointer for fuse instance + */ +void fuse_stop_cleanup_thread(struct fuse *fuse); + +/** + * Iterate over cache removing stale entries + * use in conjunction with "-oremember" + * + * NOTE: This is already done for the standard sessions + * + * @param fuse struct fuse pointer for fuse instance + * @return the number of seconds until the next cleanup + */ +int fuse_clean_cache(struct fuse *fuse); + +/* + * Stacking API + */ + +/** + * Fuse filesystem object + * + * This is opaque object represents a filesystem layer + */ +struct fuse_fs; + +/* + * These functions call the relevant filesystem operation, and return + * the result. + * + * If the operation is not defined, they return -ENOSYS, with the + * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir, + * fuse_fs_releasedir and fuse_fs_statfs, which return 0. + */ + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi); +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath, unsigned int flags); +int fuse_fs_unlink(struct fuse_fs *fs, const char *path); +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path); +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, + const char *path); +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath); +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size, + off_t off, struct fuse_file_info *fi); +int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec **bufp, size_t size, off_t off, + struct fuse_file_info *fi); +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi); +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi); +int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi); +int fuse_fs_flush(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf); +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, off_t off, + struct fuse_file_info *fi, enum fuse_readdir_flags flags); +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi); +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi); +int fuse_fs_lock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int cmd, struct flock *lock); +int fuse_fs_flock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int op); +int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi); +int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi); +int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size, + struct fuse_file_info *fi); +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2], struct fuse_file_info *fi); +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask); +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len); +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev); +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode); +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags); +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size); +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size); +int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, + const char *name); +int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, + uint64_t *idx); +#if FUSE_USE_VERSION < 35 +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data); +#else +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data); +#endif +int fuse_fs_poll(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, struct fuse_pollhandle *ph, + unsigned *reventsp); +int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi); +ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, + struct fuse_file_info *fi_in, off_t off_in, + const char *path_out, + struct fuse_file_info *fi_out, off_t off_out, + size_t len, int flags); +off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, + struct fuse_file_info *fi); +void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, + struct fuse_config *cfg); +void fuse_fs_destroy(struct fuse_fs *fs); + +int fuse_notify_poll(struct fuse_pollhandle *ph); + +/** + * Create a new fuse filesystem object + * + * This is usually called from the factory of a fuse module to create + * a new instance of a filesystem. + * + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return a new filesystem object + */ +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *private_data); + +/** + * Factory for creating filesystem objects + * + * The function may use and remove options from 'args' that belong + * to this module. + * + * For now the 'fs' vector always contains exactly one filesystem. + * This is the filesystem which will be below the newly created + * filesystem in the stack. + * + * @param args the command line arguments + * @param fs NULL terminated filesystem object vector + * @return the new filesystem object + */ +typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args, + struct fuse_fs *fs[]); +/** + * Register filesystem module + * + * If the "-omodules=*name*_:..." option is present, filesystem + * objects are created and pushed onto the stack with the *factory_* + * function. + * + * @param name_ the name of this filesystem module + * @param factory_ the factory function for this filesystem module + */ +#define FUSE_REGISTER_MODULE(name_, factory_) \ + fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_ + +/** Get session from fuse object */ +struct fuse_session *fuse_get_session(struct fuse *f); + +/** + * Open a FUSE file descriptor and set up the mount for the given + * mountpoint and flags. + * + * @param mountpoint reference to the mount in the file system + * @param options mount options + * @return the FUSE file descriptor or -1 upon error + */ +int fuse_open_channel(const char *mountpoint, const char *options); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_H_ */ diff --git a/include/fuse_common.h b/include/fuse_common.h new file mode 100644 index 0000000..582505f --- /dev/null +++ b/include/fuse_common.h @@ -0,0 +1,1148 @@ +/* FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +/** @file */ + +#include +#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_) +#error "Never include directly; use or instead." +#endif + +#ifndef FUSE_COMMON_H_ +#define FUSE_COMMON_H_ + +#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H +#include "fuse_config.h" +#endif + +#include "libfuse_config.h" + +#include "fuse_opt.h" +#include "fuse_log.h" +#include +#include +#include + +#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min)) +#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) + +#ifdef HAVE_STATIC_ASSERT +#define fuse_static_assert(condition, message) static_assert(condition, message) +#else +#define fuse_static_assert(condition, message) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Information about an open file. + * + * File Handles are created by the open, opendir, and create methods and closed + * by the release and releasedir methods. Multiple file handles may be + * concurrently open for the same file. Generally, a client will create one + * file handle per file descriptor, though in some cases multiple file + * descriptors can share a single file handle. + * + * Note: This data structure is ABI sensitive, new parameters have to be + * added within padding/padding2 bits and always below existing + * parameters. + */ +struct fuse_file_info { + /** Open flags. Available in open(), release() and create() */ + int32_t flags; + + /** In case of a write operation indicates if this was caused + by a delayed write from the page cache. If so, then the + context's pid, uid, and gid fields will not be valid, and + the *fh* value may not match the *fh* value that would + have been sent with the corresponding individual write + requests if write caching had been disabled. */ + uint32_t writepage : 1; + + /** Can be filled in by open/create, to use direct I/O on this file. */ + uint32_t direct_io : 1; + + /** Can be filled in by open and opendir. It signals the kernel that any + currently cached data (ie., data that the filesystem provided the + last time the file/directory was open) need not be invalidated when + the file/directory is closed. */ + uint32_t keep_cache : 1; + + /** Indicates a flush operation. Set in flush operation, also + maybe set in highlevel lock operation and lowlevel release + operation. */ + uint32_t flush : 1; + + /** Can be filled in by open, to indicate that the file is not + seekable. */ + uint32_t nonseekable : 1; + + /* Indicates that flock locks for this file should be + released. If set, lock_owner shall contain a valid value. + May only be set in ->release(). */ + uint32_t flock_release : 1; + + /** Can be filled in by opendir. It signals the kernel to + enable caching of entries returned by readdir(). Has no + effect when set in other contexts (in particular it does + nothing when set by open()). */ + uint32_t cache_readdir : 1; + + /** Can be filled in by open, to indicate that flush is not needed + on close. */ + uint32_t noflush : 1; + + /** Can be filled by open/create, to allow parallel direct writes on this + file */ + uint32_t parallel_direct_writes : 1; + + /** Padding. Reserved for future use*/ + uint32_t padding : 23; + uint32_t padding2 : 32; + uint32_t padding3 : 32; + + /** File handle id. May be filled in by filesystem in create, + * open, and opendir(). Available in most other file operations on the + * same file handle. */ + uint64_t fh; + + /** Lock owner id. Available in locking operations and flush */ + uint64_t lock_owner; + + /** Requested poll events. Available in ->poll. Only set on kernels + which support it. If unsupported, this field is set to zero. */ + uint32_t poll_events; + + /** Passthrough backing file id. May be filled in by filesystem in + * create and open. It is used to create a passthrough connection + * between FUSE file and backing file. */ + int32_t backing_id; + + /** struct fuse_file_info api and abi flags */ + uint64_t compat_flags; + + uint64_t reserved[2]; +}; +fuse_static_assert(sizeof(struct fuse_file_info) == 64, + "fuse_file_info size mismatch"); + +/** + * Configuration parameters passed to fuse_session_loop_mt() and + * fuse_loop_mt(). + * For FUSE API versions less than 312, use a public struct + * fuse_loop_config in applications and struct fuse_loop_config_v1 + * is used in library (i.e., libfuse.so). These two structs are binary + * compatible in earlier API versions and can be linked. + * Deprecated and replaced by a newer private struct in FUSE API + * version 312 (FUSE_MAKE_VERSION(3, 12)). + */ +#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +struct fuse_loop_config_v1; /* forward declaration */ +struct fuse_loop_config { +#else +struct fuse_loop_config_v1 { +#endif + /** + * whether to use separate device fds for each thread + * (may increase performance) + */ + int clone_fd; + + /** + * The maximum number of available worker threads before they + * start to get deleted when they become idle. If not + * specified, the default is 10. + * + * Adjusting this has performance implications; a very small number + * of threads in the pool will cause a lot of thread creation and + * deletion overhead and performance may suffer. When set to 0, a new + * thread will be created to service every operation. + */ + unsigned int max_idle_threads; +}; + + +/************************************************************************** + * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' * + **************************************************************************/ + +/** + * Indicates that the filesystem supports asynchronous read requests. + * + * If this capability is not requested/available, the kernel will + * ensure that there is at most one pending read request per + * file-handle at any time, and will attempt to order read requests by + * increasing offset. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_READ (1 << 0) + +/** + * Indicates that the filesystem supports "remote" locking. + * + * This feature is enabled by default when supported by the kernel, + * and if getlk() and setlk() handlers are implemented. + */ +#define FUSE_CAP_POSIX_LOCKS (1 << 1) + +/** + * Indicates that the filesystem supports the O_TRUNC open flag. If + * disabled, and an application specifies O_TRUNC, fuse first calls + * truncate() and then open() with O_TRUNC filtered out. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3) + +/** + * Indicates that the filesystem supports lookups of "." and "..". + * + * When this flag is set, the filesystem must be prepared to receive requests + * for invalid inodes (i.e., for which a FORGET request was received or + * which have been used in a previous instance of the filesystem daemon) and + * must not reuse node-ids (even when setting generation numbers). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPORT_SUPPORT (1 << 4) + +/** + * Indicates that the kernel should not apply the umask to the + * file mode on create operations. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_DONT_MASK (1 << 6) + +/** + * Indicates that libfuse should try to use splice() when writing to + * the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_WRITE (1 << 7) + +/** + * Indicates that libfuse should try to move pages instead of copying when + * writing to / reading from the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_MOVE (1 << 8) + +/** + * Indicates that libfuse should try to use splice() when reading from + * the fuse device. This may improve performance. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a write_buf() handler. + */ +#define FUSE_CAP_SPLICE_READ (1 << 9) + +/** + * If set, the calls to flock(2) will be emulated using POSIX locks and must + * then be handled by the filesystem's setlock() handler. + * + * If not set, flock(2) calls will be handled by the FUSE kernel module + * internally (so any access that does not go through the kernel cannot be taken + * into account). + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a flock() handler. + */ +#define FUSE_CAP_FLOCK_LOCKS (1 << 10) + +/** + * Indicates that the filesystem supports ioctl's on directories. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_IOCTL_DIR (1 << 11) + +/** + * Traditionally, while a file is open the FUSE kernel module only + * asks the filesystem for an update of the file's attributes when a + * client attempts to read beyond EOF. This is unsuitable for + * e.g. network filesystems, where the file contents may change + * without the kernel knowing about it. + * + * If this flag is set, FUSE will check the validity of the attributes + * on every read. If the attributes are no longer valid (i.e., if the + * *attr_timeout* passed to fuse_reply_attr() or set in `struct + * fuse_entry_param` has passed), it will first issue a `getattr` + * request. If the new mtime differs from the previous value, any + * cached file *contents* will be invalidated as well. + * + * This flag should always be set when available. If all file changes + * go through the kernel, *attr_timeout* should be set to a very large + * number to avoid unnecessary getattr() calls. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12) + +/** + * Indicates that the filesystem supports readdirplus. + * + * This feature is enabled by default when supported by the kernel and if the + * filesystem implements a readdirplus() handler. + */ +#define FUSE_CAP_READDIRPLUS (1 << 13) + +/** + * Indicates that the filesystem supports adaptive readdirplus. + * + * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel + * will always issue readdirplus() requests to retrieve directory + * contents. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel + * will issue both readdir() and readdirplus() requests, depending on + * how much information is expected to be required. + * + * As of Linux 4.20, the algorithm is as follows: when userspace + * starts to read directory entries, issue a READDIRPLUS request to + * the filesystem. If any entry attributes have been looked up by the + * time userspace requests the next batch of entries continue with + * READDIRPLUS, otherwise switch to plain READDIR. This will reasult + * in eg plain "ls" triggering READDIRPLUS first then READDIR after + * that because it doesn't do lookups. "ls -l" should result in all + * READDIRPLUS, except if dentries are already cached. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements both a readdirplus() and a readdir() + * handler. + */ +#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14) + +/** + * Indicates that the filesystem supports asynchronous direct I/O submission. + * + * If this capability is not requested/available, the kernel will ensure that + * there is at most one pending read and one pending write request per direct + * I/O file-handle at any time. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_DIO (1 << 15) + +/** + * Indicates that writeback caching should be enabled. This means that + * individual write request may be buffered and merged in the kernel + * before they are send to the filesystem. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_WRITEBACK_CACHE (1 << 16) + +/** + * Indicates support for zero-message opens. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the + * filesystem may return `ENOSYS` from the open() handler to indicate + * success. Further attempts to open files will be handled in the + * kernel. (If this flag is not set, returning ENOSYS will be treated + * as an error and signaled to the caller). + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users wish to have + * this behavior you must return `ENOSYS` from the open() handler on supporting + * kernels. + */ +#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17) + +/** + * Indicates support for parallel directory operations. If this flag + * is unset, the FUSE kernel module will ensure that lookup() and + * readdir() requests are never issued concurrently for the same + * directory. + */ +#define FUSE_CAP_PARALLEL_DIROPS (1 << 18) + +/** + * Indicates support for POSIX ACLs. + * + * If this feature is enabled, the kernel will cache and have + * responsibility for enforcing ACLs. ACL will be stored as xattrs and + * passed to userspace, which is responsible for updating the ACLs in + * the filesystem, keeping the file mode in sync with the ACL, and + * ensuring inheritance of default ACLs when new filesystem nodes are + * created. Note that this requires that the file system is able to + * parse and interpret the xattr representation of ACLs. + * + * Enabling this feature implicitly turns on the + * ``default_permissions`` mount option (even if it was not passed to + * mount(2)). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_POSIX_ACL (1 << 19) + +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bits when a file is written, truncated, or + * its owner is changed. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20) + +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bit and additionally cap (stored as xattr) when a + * file is written, truncated, or its owner is changed. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * KILLPRIV_V2 requires handling of + * - FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags) + * - FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid) + * - FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags) + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21) + +/** + * Indicates that the kernel supports caching symlinks in its page cache. + * + * When this feature is enabled, symlink targets are saved in the page cache. + * You can invalidate a cached link by calling: + * `fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);` + * + * This feature is disabled by default. + * If the kernel supports it (>= 4.20), you can enable this feature by + * setting this flag in the `want` field of the `fuse_conn_info` structure. + */ +#define FUSE_CAP_CACHE_SYMLINKS (1 << 23) + +/** + * Indicates support for zero-message opendirs. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the filesystem + * may return `ENOSYS` from the opendir() handler to indicate success. Further + * opendir and releasedir messages will be handled in the kernel. (If this + * flag is not set, returning ENOSYS will be treated as an error and signalled + * to the caller.) + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users with to have + * this behavior you must return `ENOSYS` from the opendir() handler on + * supporting kernels. + */ +#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24) + +/** + * Indicates support for invalidating cached pages only on explicit request. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` structure, + * then the FUSE kernel module supports invalidating cached pages only on + * explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() + * or fuse_invalidate_path(). + * + * By setting this flag in the `want` field of the `fuse_conn_info` structure, + * the filesystem is responsible for invalidating cached pages through explicit + * requests to the kernel. + * + * Note that setting this flag does not prevent the cached pages from being + * flushed by OS itself and/or through user actions. + * + * Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA + * are set in the `capable` field of the `fuse_conn_info` structure then + * FUSE_CAP_AUTO_INVAL_DATA takes precedence. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25) + +/** + * Indicates support that dentries can be expired. + * + * Expiring dentries, instead of invalidating them, makes a difference for + * overmounted dentries, where plain invalidation would detach all submounts + * before dropping the dentry from the cache. If only expiry is set on the + * dentry, then any overmounts are left alone and until ->d_revalidate() + * is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + */ +#define FUSE_CAP_EXPIRE_ONLY (1 << 26) + +/** + * Indicates that an extended 'struct fuse_setxattr' is used by the kernel + * side - extra_flags are passed, which are used (as of now by acl) processing. + * For example FUSE_SETXATTR_ACL_KILL_SGID might be set. + */ +#define FUSE_CAP_SETXATTR_EXT (1 << 27) + +/** + * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction + * is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). + * MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to + * ensure coherency between mount points (or network clients) and with kernel page + * cache as enforced by mmap that cannot be guaranteed anymore. + */ +#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1 << 28) + +/** + * Indicates support for passthrough mode access for read/write operations. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` + * structure, then the FUSE kernel module supports redirecting read/write + * operations to the backing file instead of letting them to be handled + * by the FUSE daemon. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_PASSTHROUGH (1 << 29) + +/** + * Indicates that the file system cannot handle NFS export + * + * If this flag is set NFS export and name_to_handle_at + * is not going to work at all and will fail with EOPNOTSUPP. + */ +#define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * FUSE_IOCTL_DIR: is a directory + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_DIR (1 << 4) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Connection information, passed to the ->init() method + * + * Some of the elements are read-write, these can be changed to + * indicate the value requested by the filesystem. The requested + * value must usually be smaller than the indicated value. + * + * Note: The `capable` and `want` fields are limited to 32 bits for + * ABI compatibility. For full 64-bit capability support, use the + * `capable_ext` and `want_ext` fields instead. + */ +struct fuse_conn_info { + /** + * Major version of the protocol (read-only) + */ + uint32_t proto_major; + + /** + * Minor version of the protocol (read-only) + */ + uint32_t proto_minor; + + /** + * Maximum size of the write buffer + */ + uint32_t max_write; + + /** + * Maximum size of read requests. A value of zero indicates no + * limit. However, even if the filesystem does not specify a + * limit, the maximum size of read requests will still be + * limited by the kernel. + * + * NOTE: For the time being, the maximum size of read requests + * must be set both here *and* passed to fuse_session_new() + * using the ``-o max_read=`` mount option. At some point + * in the future, specifying the mount option will no longer + * be necessary. + */ + uint32_t max_read; + + /** + * Maximum readahead + */ + uint32_t max_readahead; + + /** + * Capability flags that the kernel supports (read-only) + * + * Deprecated left over for ABI compatibility, use capable_ext + */ + uint32_t capable; + + /** + * Capability flags that the filesystem wants to enable. + * + * libfuse attempts to initialize this field with + * reasonable default values before calling the init() handler. + * + * Deprecated left over for ABI compatibility. + * Use want_ext with the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + */ + uint32_t want; + + /** + * Maximum number of pending "background" requests. A + * background request is any type of request for which the + * total number is not limited by other means. As of kernel + * 4.8, only two types of requests fall into this category: + * + * 1. Read-ahead requests + * 2. Asynchronous direct I/O requests + * + * Read-ahead requests are generated (if max_readahead is + * non-zero) by the kernel to preemptively fill its caches + * when it anticipates that userspace will soon read more + * data. + * + * Asynchronous direct I/O requests are generated if + * FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large + * direct I/O request. In this case the kernel will internally + * split it up into multiple smaller requests and submit them + * to the filesystem concurrently. + * + * Note that the following requests are *not* background + * requests: writeback requests (limited by the kernel's + * flusher algorithm), regular (i.e., synchronous and + * buffered) userspace read/write requests (limited to one per + * thread), asynchronous read requests (Linux's io_submit(2) + * call actually blocks, so these are also limited to one per + * thread). + */ + uint32_t max_background; + + /** + * Kernel congestion threshold parameter. If the number of pending + * background requests exceeds this number, the FUSE kernel module will + * mark the filesystem as "congested". This instructs the kernel to + * expect that queued requests will take some time to complete, and to + * adjust its algorithms accordingly (e.g. by putting a waiting thread + * to sleep instead of using a busy-loop). + */ + uint32_t congestion_threshold; + + /** + * When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible + * for updating mtime and ctime when write requests are received. The + * updated values are passed to the filesystem with setattr() requests. + * However, if the filesystem does not support the full resolution of + * the kernel timestamps (nanoseconds), the mtime and ctime values used + * by kernel and filesystem will differ (and result in an apparent + * change of times after a cache flush). + * + * To prevent this problem, this variable can be used to inform the + * kernel about the timestamp granularity supported by the file-system. + * The value should be power of 10. The default is 1, i.e. full + * nano-second resolution. Filesystems supporting only second resolution + * should set this to 1000000000. + */ + uint32_t time_gran; + + /** + * When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed + * stacking depth of the backing files. In current kernel, the maximum + * allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes + * the FUSE passthrough layer, so the maximum stacking depth for backing + * files is 1. + * + * The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the + * backing files cannot be on a stacked filesystem, but another stacked + * filesystem can be stacked over this FUSE passthrough filesystem. + * + * Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on + * a stacked filesystem, such as overlayfs or another FUSE passthrough. + * In this configuration, another stacked filesystem cannot be stacked + * over this FUSE passthrough filesystem. + */ +#define FUSE_BACKING_STACKED_UNDER (0) +#define FUSE_BACKING_STACKED_OVER (1) + uint32_t max_backing_stack_depth; + + /** + * Disable FUSE_INTERRUPT requests. + * + * Enable `no_interrupt` option to: + * 1) Avoid unnecessary locking operations and list operations, + * 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to + * inform the kernel not to send the FUSE_INTERRUPT request. + */ + uint32_t no_interrupt : 1; + + /* reserved bits for future use */ + uint32_t padding : 31; + + /** + * Extended capability flags that the kernel supports (read-only) + * This field provides full 64-bit capability support. + */ + uint64_t capable_ext; + + /** + * Extended capability flags that the filesystem wants to enable. + * This field provides full 64-bit capability support. + * + * Don't set this field directly, but use the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + * + */ + uint64_t want_ext; + + /** + * For future use. + */ + uint32_t reserved[16]; +}; +fuse_static_assert(sizeof(struct fuse_conn_info) == 128, + "Size of struct fuse_conn_info must be 128 bytes"); + +struct fuse_session; +struct fuse_pollhandle; +struct fuse_conn_info_opts; + +/** + * This function parses several command-line options that can be used + * to override elements of struct fuse_conn_info. The pointer returned + * by this function should be passed to the + * fuse_apply_conn_info_opts() method by the file system's init() + * handler. + * + * Before using this function, think twice if you really want these + * parameters to be adjustable from the command line. In most cases, + * they should be determined by the file system internally. + * + * The following options are recognized: + * + * -o max_write=N sets conn->max_write + * -o max_readahead=N sets conn->max_readahead + * -o max_background=N sets conn->max_background + * -o congestion_threshold=N sets conn->congestion_threshold + * -o async_read sets FUSE_CAP_ASYNC_READ in conn->want + * -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want + * -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want + * -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock + * -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want + * -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want + * -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want + * -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want + * -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want + * -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want + * -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want + * -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets + * FUSE_CAP_READDIRPLUS_AUTO in conn->want + * -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and + * FUSE_CAP_READDIRPLUS_AUTO in conn->want + * -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want + * -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want + * -o time_gran=N sets conn->time_gran + * + * Known options will be removed from *args*, unknown options will be + * passed through unchanged. + * + * @param args argument vector (input+output) + * @return parsed options + **/ +struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args); + +/** + * This function applies the (parsed) parameters in *opts* to the + * *conn* pointer. It may modify the following fields: wants, + * max_write, max_readahead, congestion_threshold, max_background, + * time_gran. A field is only set (or unset) if the corresponding + * option has been explicitly set. + */ +void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, + struct fuse_conn_info *conn); + +/** + * Go into the background + * + * @param foreground if true, stay in the foreground + * @return 0 on success, -1 on failure + */ +int fuse_daemonize(int foreground); + +/** + * Get the version of the library + * + * @return the version + */ +int fuse_version(void); + +/** + * Get the full package version string of the library + * + * @return the package version + */ +const char *fuse_pkgversion(void); + +/** + * Destroy poll handle + * + * @param ph the poll handle + */ +void fuse_pollhandle_destroy(struct fuse_pollhandle *ph); + +/* ----------------------------------------------------------- * + * Data buffer * + * ----------------------------------------------------------- */ + +/** + * Buffer flags + */ +enum fuse_buf_flags { + /** + * Buffer contains a file descriptor + * + * If this flag is set, the .fd field is valid, otherwise the + * .mem fields is valid. + */ + FUSE_BUF_IS_FD = (1 << 1), + + /** + * Seek on the file descriptor + * + * If this flag is set then the .pos field is valid and is + * used to seek to the given offset before performing + * operation on file descriptor. + */ + FUSE_BUF_FD_SEEK = (1 << 2), + + /** + * Retry operation on file descriptor + * + * If this flag is set then retry operation on file descriptor + * until .size bytes have been copied or an error or EOF is + * detected. + */ + FUSE_BUF_FD_RETRY = (1 << 3) +}; + +/** + * Buffer copy flags + */ +enum fuse_buf_copy_flags { + /** + * Don't use splice(2) + * + * Always fall back to using read and write instead of + * splice(2) to copy data from one file descriptor to another. + * + * If this flag is not set, then only fall back if splice is + * unavailable. + */ + FUSE_BUF_NO_SPLICE = (1 << 1), + + /** + * Force splice + * + * Always use splice(2) to copy data from one file descriptor + * to another. If splice is not available, return -EINVAL. + */ + FUSE_BUF_FORCE_SPLICE = (1 << 2), + + /** + * Try to move data with splice. + * + * If splice is used, try to move pages from the source to the + * destination instead of copying. See documentation of + * SPLICE_F_MOVE in splice(2) man page. + */ + FUSE_BUF_SPLICE_MOVE = (1 << 3), + + /** + * Don't block on the pipe when copying data with splice + * + * Makes the operations on the pipe non-blocking (if the pipe + * is full or empty). See SPLICE_F_NONBLOCK in the splice(2) + * man page. + */ + FUSE_BUF_SPLICE_NONBLOCK= (1 << 4) +}; + +/** + * Single data buffer + * + * Generic data buffer for I/O, extended attributes, etc... Data may + * be supplied as a memory pointer or as a file descriptor + */ +struct fuse_buf { + /** + * Size of data in bytes + */ + size_t size; + + /** + * Buffer flags + */ + enum fuse_buf_flags flags; + + /** + * Memory pointer + * + * Used unless FUSE_BUF_IS_FD flag is set. + */ + void *mem; + + /** + * File descriptor + * + * Used if FUSE_BUF_IS_FD flag is set. + */ + int fd; + + /** + * File position + * + * Used if FUSE_BUF_FD_SEEK flag is set. + */ + off_t pos; + + /** + * Size of memory pointer + * + * Used only if mem was internally allocated. + * Not used if mem was user-provided. + */ + size_t mem_size; +}; + +/** + * Data buffer vector + * + * An array of data buffers, each containing a memory pointer or a + * file descriptor. + * + * Allocate dynamically to add more than one buffer. + */ +struct fuse_bufvec { + /** + * Number of buffers in the array + */ + size_t count; + + /** + * Index of current buffer within the array + */ + size_t idx; + + /** + * Current offset within the current buffer + */ + size_t off; + + /** + * Array of buffers + */ + struct fuse_buf buf[1]; +}; + +/** + * libfuse version a file system was compiled with. Should be filled in from + * defines in 'libfuse_config.h' + */ +struct libfuse_version +{ + uint32_t major; + uint32_t minor; + uint32_t hotfix; + uint32_t padding; +}; + +/* Initialize bufvec with a single buffer of given size */ +#define FUSE_BUFVEC_INIT(size__) \ + ((struct fuse_bufvec) { \ + /* .count= */ 1, \ + /* .idx = */ 0, \ + /* .off = */ 0, \ + /* .buf = */ { /* [0] = */ { \ + /* .size = */ (size__), \ + /* .flags = */ (enum fuse_buf_flags) 0, \ + /* .mem = */ NULL, \ + /* .fd = */ -1, \ + /* .pos = */ 0, \ + /* .mem_size = */ 0, \ + } } \ + } ) + +/** + * Get total size of data in a fuse buffer vector + * + * @param bufv buffer vector + * @return size of data + */ +size_t fuse_buf_size(const struct fuse_bufvec *bufv); + +/** + * Copy data from one buffer vector to another + * + * @param dst destination buffer vector + * @param src source buffer vector + * @param flags flags controlling the copy + * @return actual number of bytes copied or -errno on error + */ +ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, + enum fuse_buf_copy_flags flags); + +/* ----------------------------------------------------------- * + * Signal handling * + * ----------------------------------------------------------- */ + +/** + * Exit session on HUP, TERM and INT signals and ignore PIPE signal + * + * Stores session in a global variable. May only be called once per + * process until fuse_remove_signal_handlers() is called. + * + * Once either of the POSIX signals arrives, the signal handler calls + * fuse_session_exit(). + * + * @param se the session to exit + * @return 0 on success, -1 on failure + * + * See also: + * fuse_remove_signal_handlers() + */ +int fuse_set_signal_handlers(struct fuse_session *se); + +/** + * Print a stack backtrace diagnostic on critical signals () + * + * Stores session in a global variable. May only be called once per + * process until fuse_remove_signal_handlers() is called. + * + * Once either of the POSIX signals arrives, the signal handler calls + * fuse_session_exit(). + * + * @param se the session to exit + * @return 0 on success, -1 on failure + * + * See also: + * fuse_remove_signal_handlers() + */ +int fuse_set_fail_signal_handlers(struct fuse_session *se); + +/** + * Restore default signal handlers + * + * Resets global session. After this fuse_set_signal_handlers() may + * be called again. + * + * @param se the same session as given in fuse_set_signal_handlers() + * + * See also: + * fuse_set_signal_handlers() + */ +void fuse_remove_signal_handlers(struct fuse_session *se); + +/** + * Config operations. + * These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). + * Using them in earlier versions will result in errors. + */ +#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12) +/** + * Create and set default config for fuse_session_loop_mt and fuse_loop_mt. + * + * @return anonymous config struct + */ +struct fuse_loop_config *fuse_loop_cfg_create(void); + +/** + * Free the config data structure + */ +void fuse_loop_cfg_destroy(struct fuse_loop_config *config); + +/** + * fuse_loop_config setter to set the number of max idle threads. + */ +void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config, + unsigned int value); + +/** + * fuse_loop_config setter to set the number of max threads. + */ +void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config, + unsigned int value); + +/** + * fuse_loop_config setter to enable the clone_fd feature + */ +void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config, + unsigned int value); + +/** + * Convert old config to more recernt fuse_loop_config2 + * + * @param config current config2 type + * @param v1_conf older config1 type (below FUSE API 312) + */ +void fuse_loop_cfg_convert(struct fuse_loop_config *config, + struct fuse_loop_config_v1 *v1_conf); +#endif + + +static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + if (conn->capable_ext & flag) { + conn->want_ext |= flag; + return true; + } + return false; +} + +static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + conn->want_ext &= ~flag; +} + +static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + return conn->capable_ext & flag ? true : false; +} + +/* ----------------------------------------------------------- * + * Compatibility stuff * + * ----------------------------------------------------------- */ + +#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30 +# error only API version 30 or greater is supported +#endif + +#ifdef __cplusplus +} +#endif + + +/* + * This interface uses 64 bit off_t. + * + * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags! + */ + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit"); +#else +struct _fuse_off_t_must_be_64bit_dummy_struct \ + { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); }; +#endif + +#endif /* FUSE_COMMON_H_ */ diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h new file mode 100644 index 0000000..d08b99d --- /dev/null +++ b/include/fuse_kernel.h @@ -0,0 +1,1189 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */ +/* + This file defines the kernel interface of FUSE + Copyright (C) 2001-2008 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + + This -- and only this -- header file may also be distributed under + the terms of the BSD Licence as follows: + + Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +/* + * This file defines the kernel interface of FUSE + * + * Protocol changelog: + * + * 7.1: + * - add the following messages: + * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK, + * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE, + * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR, + * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR, + * FUSE_RELEASEDIR + * - add padding to messages to accommodate 32-bit servers on 64-bit kernels + * + * 7.2: + * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags + * - add FUSE_FSYNCDIR message + * + * 7.3: + * - add FUSE_ACCESS message + * - add FUSE_CREATE message + * - add filehandle to fuse_setattr_in + * + * 7.4: + * - add frsize to fuse_kstatfs + * - clean up request size limit checking + * + * 7.5: + * - add flags and max_write to fuse_init_out + * + * 7.6: + * - add max_readahead to fuse_init_in and fuse_init_out + * + * 7.7: + * - add FUSE_INTERRUPT message + * - add POSIX file lock support + * + * 7.8: + * - add lock_owner and flags fields to fuse_release_in + * - add FUSE_BMAP message + * - add FUSE_DESTROY message + * + * 7.9: + * - new fuse_getattr_in input argument of GETATTR + * - add lk_flags in fuse_lk_in + * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in + * - add blksize field to fuse_attr + * - add file flags field to fuse_read_in and fuse_write_in + * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in + * + * 7.10 + * - add nonseekable open flag + * + * 7.11 + * - add IOCTL message + * - add unsolicited notification support + * - add POLL message and NOTIFY_POLL notification + * + * 7.12 + * - add umask flag to input argument of create, mknod and mkdir + * - add notification messages for invalidation of inodes and + * directory entries + * + * 7.13 + * - make max number of background requests and congestion threshold + * tunables + * + * 7.14 + * - add splice support to fuse device + * + * 7.15 + * - add store notify + * - add retrieve notify + * + * 7.16 + * - add BATCH_FORGET request + * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct + * fuse_ioctl_iovec' instead of ambiguous 'struct iovec' + * - add FUSE_IOCTL_32BIT flag + * + * 7.17 + * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK + * + * 7.18 + * - add FUSE_IOCTL_DIR flag + * - add FUSE_NOTIFY_DELETE + * + * 7.19 + * - add FUSE_FALLOCATE + * + * 7.20 + * - add FUSE_AUTO_INVAL_DATA + * + * 7.21 + * - add FUSE_READDIRPLUS + * - send the requested events in POLL request + * + * 7.22 + * - add FUSE_ASYNC_DIO + * + * 7.23 + * - add FUSE_WRITEBACK_CACHE + * - add time_gran to fuse_init_out + * - add reserved space to fuse_init_out + * - add FATTR_CTIME + * - add ctime and ctimensec to fuse_setattr_in + * - add FUSE_RENAME2 request + * - add FUSE_NO_OPEN_SUPPORT flag + * + * 7.24 + * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support + * + * 7.25 + * - add FUSE_PARALLEL_DIROPS + * + * 7.26 + * - add FUSE_HANDLE_KILLPRIV + * - add FUSE_POSIX_ACL + * + * 7.27 + * - add FUSE_ABORT_ERROR + * + * 7.28 + * - add FUSE_COPY_FILE_RANGE + * - add FOPEN_CACHE_DIR + * - add FUSE_MAX_PAGES, add max_pages to init_out + * - add FUSE_CACHE_SYMLINKS + * + * 7.29 + * - add FUSE_NO_OPENDIR_SUPPORT flag + * + * 7.30 + * - add FUSE_EXPLICIT_INVAL_DATA + * - add FUSE_IOCTL_COMPAT_X32 + * + * 7.31 + * - add FUSE_WRITE_KILL_PRIV flag + * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING + * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag + * + * 7.32 + * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS + * + * 7.33 + * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID + * - add FUSE_OPEN_KILL_SUIDGID + * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT + * - add FUSE_SETXATTR_ACL_KILL_SGID + * + * 7.34 + * - add FUSE_SYNCFS + * + * 7.35 + * - add FOPEN_NOFLUSH + * + * 7.36 + * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag + * - add flags2 to fuse_init_in and fuse_init_out + * - add FUSE_SECURITY_CTX init flag + * - add security context to create, mkdir, symlink, and mknod requests + * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX + * + * 7.37 + * - add FUSE_TMPFILE + * + * 7.38 + * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry + * - add FOPEN_PARALLEL_DIRECT_WRITES + * - add total_extlen to fuse_in_header + * - add FUSE_MAX_NR_SECCTX + * - add extension header + * - add FUSE_EXT_GROUPS + * - add FUSE_CREATE_SUPP_GROUP + * - add FUSE_HAS_EXPIRE_ONLY + * + * 7.39 + * - add FUSE_DIRECT_IO_ALLOW_MMAP + * - add FUSE_STATX and related structures + * + * 7.40 + * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag + * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag + * - add FUSE_NO_EXPORT_SUPPORT init flag + * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag + */ + +#ifndef _LINUX_FUSE_H +#define _LINUX_FUSE_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +/* + * Version negotiation: + * + * Both the kernel and userspace send the version they support in the + * INIT request and reply respectively. + * + * If the major versions match then both shall use the smallest + * of the two minor versions for communication. + * + * If the kernel supports a larger major version, then userspace shall + * reply with the major version it supports, ignore the rest of the + * INIT message and expect a new INIT message from the kernel with a + * matching major version. + * + * If the library supports a larger major version, then it shall fall + * back to the major protocol version sent by the kernel for + * communication and reply with that major version (and an arbitrary + * supported minor version). + */ + +/** Version number of this interface */ +#define FUSE_KERNEL_VERSION 7 + +/** Minor version number of this interface */ +#define FUSE_KERNEL_MINOR_VERSION 40 + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/* Make sure all structures are padded to 64bit boundary, so 32bit + userspace works under 64bit kernels */ + +struct fuse_attr { + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint32_t rdev; + uint32_t blksize; + uint32_t flags; +}; + +/* + * The following structures are bit-for-bit compatible with the statx(2) ABI in + * Linux. + */ +struct fuse_sx_time { + int64_t tv_sec; + uint32_t tv_nsec; + int32_t __reserved; +}; + +struct fuse_statx { + uint32_t mask; + uint32_t blksize; + uint64_t attributes; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint16_t mode; + uint16_t __spare0[1]; + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t attributes_mask; + struct fuse_sx_time atime; + struct fuse_sx_time btime; + struct fuse_sx_time ctime; + struct fuse_sx_time mtime; + uint32_t rdev_major; + uint32_t rdev_minor; + uint32_t dev_major; + uint32_t dev_minor; + uint64_t __spare2[14]; +}; + +struct fuse_kstatfs { + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; + uint64_t files; + uint64_t ffree; + uint32_t bsize; + uint32_t namelen; + uint32_t frsize; + uint32_t padding; + uint32_t spare[6]; +}; + +struct fuse_file_lock { + uint64_t start; + uint64_t end; + uint32_t type; + uint32_t pid; /* tgid */ +}; + +/** + * Bitmasks for fuse_setattr_in.valid + */ +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +#define FATTR_FH (1 << 6) +#define FATTR_ATIME_NOW (1 << 7) +#define FATTR_MTIME_NOW (1 << 8) +#define FATTR_LOCKOWNER (1 << 9) +#define FATTR_CTIME (1 << 10) +#define FATTR_KILL_SUIDGID (1 << 11) + +/** + * Flags returned by the OPEN request + * + * FOPEN_DIRECT_IO: bypass page cache for this open file + * FOPEN_KEEP_CACHE: don't invalidate the data cache on open + * FOPEN_NONSEEKABLE: the file is not seekable + * FOPEN_CACHE_DIR: allow caching this directory + * FOPEN_STREAM: the file is stream-like (no file position at all) + * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE) + * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode + * FOPEN_PASSTHROUGH: passthrough read/write io for this open file + */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) +#define FOPEN_NONSEEKABLE (1 << 2) +#define FOPEN_CACHE_DIR (1 << 3) +#define FOPEN_STREAM (1 << 4) +#define FOPEN_NOFLUSH (1 << 5) +#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6) +#define FOPEN_PASSTHROUGH (1 << 7) + +/** + * INIT request/reply flags + * + * FUSE_ASYNC_READ: asynchronous read requests + * FUSE_POSIX_LOCKS: remote locking for POSIX file locks + * FUSE_FILE_OPS: kernel sends file handle for fstat, etc... (not yet supported) + * FUSE_ATOMIC_O_TRUNC: handles the O_TRUNC open flag in the filesystem + * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." + * FUSE_BIG_WRITES: filesystem can handle write size larger than 4kB + * FUSE_DONT_MASK: don't apply umask to file mode on create operations + * FUSE_SPLICE_WRITE: kernel supports splice write on the device + * FUSE_SPLICE_MOVE: kernel supports splice move on the device + * FUSE_SPLICE_READ: kernel supports splice read on the device + * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks + * FUSE_HAS_IOCTL_DIR: kernel supports ioctl on directories + * FUSE_AUTO_INVAL_DATA: automatically invalidate cached pages + * FUSE_DO_READDIRPLUS: do READDIRPLUS (READDIR+LOOKUP in one) + * FUSE_READDIRPLUS_AUTO: adaptive readdirplus + * FUSE_ASYNC_DIO: asynchronous direct I/O submission + * FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes + * FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens + * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir + * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc + * FUSE_POSIX_ACL: filesystem supports posix acls + * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED + * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages + * FUSE_CACHE_SYMLINKS: cache READLINK responses + * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir + * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request + * FUSE_MAP_ALIGNMENT: init_out.map_alignment contains log2(byte alignment) for + * foffset and moffset fields in struct + * fuse_setupmapping_out and fuse_removemapping_one. + * FUSE_SUBMOUNTS: kernel supports auto-mounting directory submounts + * FUSE_HANDLE_KILLPRIV_V2: fs kills suid/sgid/cap on write/chown/trunc. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * FUSE_SETXATTR_EXT: Server supports extended struct fuse_setxattr_in + * FUSE_INIT_EXT: extended fuse_init_in request + * FUSE_INIT_RESERVED: reserved, do not use + * FUSE_SECURITY_CTX: add security context to create, mkdir, symlink, and + * mknod + * FUSE_HAS_INODE_DAX: use per inode DAX + * FUSE_CREATE_SUPP_GROUP: add supplementary group info to create, mkdir, + * symlink and mknod (single group that matches parent) + * FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation + * FUSE_DIRECT_IO_ALLOW_MMAP: allow shared mmap in FOPEN_DIRECT_IO mode. + * FUSE_NO_EXPORT_SUPPORT: explicitly disable export support + * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit + * of the request ID indicates resend requests + */ +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_SPLICE_WRITE (1 << 7) +#define FUSE_SPLICE_MOVE (1 << 8) +#define FUSE_SPLICE_READ (1 << 9) +#define FUSE_FLOCK_LOCKS (1 << 10) +#define FUSE_HAS_IOCTL_DIR (1 << 11) +#define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_DO_READDIRPLUS (1 << 13) +#define FUSE_READDIRPLUS_AUTO (1 << 14) +#define FUSE_ASYNC_DIO (1 << 15) +#define FUSE_WRITEBACK_CACHE (1 << 16) +#define FUSE_NO_OPEN_SUPPORT (1 << 17) +#define FUSE_PARALLEL_DIROPS (1 << 18) +#define FUSE_HANDLE_KILLPRIV (1 << 19) +#define FUSE_POSIX_ACL (1 << 20) +#define FUSE_ABORT_ERROR (1 << 21) +#define FUSE_MAX_PAGES (1 << 22) +#define FUSE_CACHE_SYMLINKS (1 << 23) +#define FUSE_NO_OPENDIR_SUPPORT (1 << 24) +#define FUSE_EXPLICIT_INVAL_DATA (1 << 25) +#define FUSE_MAP_ALIGNMENT (1 << 26) +#define FUSE_SUBMOUNTS (1 << 27) +#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28) +#define FUSE_SETXATTR_EXT (1 << 29) +#define FUSE_INIT_EXT (1 << 30) +#define FUSE_INIT_RESERVED (1 << 31) +/* bits 32..63 get shifted down 32 bits into the flags2 field */ +#define FUSE_SECURITY_CTX (1ULL << 32) +#define FUSE_HAS_INODE_DAX (1ULL << 33) +#define FUSE_CREATE_SUPP_GROUP (1ULL << 34) +#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35) +#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36) +#define FUSE_PASSTHROUGH (1ULL << 37) +#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38) +#define FUSE_HAS_RESEND (1ULL << 39) + +/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ +#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP + +/** + * CUSE INIT request/reply flags + * + * CUSE_UNRESTRICTED_IOCTL: use unrestricted ioctl + */ +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) + +/** + * Release flags + */ +#define FUSE_RELEASE_FLUSH (1 << 0) +#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1) + +/** + * Getattr flags + */ +#define FUSE_GETATTR_FH (1 << 0) + +/** + * Lock flags + */ +#define FUSE_LK_FLOCK (1 << 0) + +/** + * WRITE flags + * + * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed + * FUSE_WRITE_LOCKOWNER: lock_owner field is valid + * FUSE_WRITE_KILL_SUIDGID: kill suid and sgid bits + */ +#define FUSE_WRITE_CACHE (1 << 0) +#define FUSE_WRITE_LOCKOWNER (1 << 1) +#define FUSE_WRITE_KILL_SUIDGID (1 << 2) + +/* Obsolete alias; this flag implies killing suid/sgid only. */ +#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID + +/** + * Read flags + */ +#define FUSE_READ_LOCKOWNER (1 << 1) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * FUSE_IOCTL_32BIT: 32bit ioctl + * FUSE_IOCTL_DIR: is a directory + * FUSE_IOCTL_COMPAT_X32: x32 compat ioctl on 64bit machine (64bit time_t) + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_32BIT (1 << 3) +#define FUSE_IOCTL_DIR (1 << 4) +#define FUSE_IOCTL_COMPAT_X32 (1 << 5) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Poll flags + * + * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify + */ +#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) + +/** + * Fsync flags + * + * FUSE_FSYNC_FDATASYNC: Sync data only, not metadata + */ +#define FUSE_FSYNC_FDATASYNC (1 << 0) + +/** + * fuse_attr flags + * + * FUSE_ATTR_SUBMOUNT: Object is a submount root + * FUSE_ATTR_DAX: Enable DAX for this file in per inode DAX mode + */ +#define FUSE_ATTR_SUBMOUNT (1 << 0) +#define FUSE_ATTR_DAX (1 << 1) + +/** + * Open flags + * FUSE_OPEN_KILL_SUIDGID: Kill suid and sgid if executable + */ +#define FUSE_OPEN_KILL_SUIDGID (1 << 0) + +/** + * setxattr flags + * FUSE_SETXATTR_ACL_KILL_SGID: Clear SGID when system.posix_acl_access is set + */ +#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0) + +/** + * notify_inval_entry flags + * FUSE_EXPIRE_ONLY + */ +#define FUSE_EXPIRE_ONLY (1 << 0) + +/** + * extension type + * FUSE_MAX_NR_SECCTX: maximum value of &fuse_secctx_header.nr_secctx + * FUSE_EXT_GROUPS: &fuse_supp_groups extension + */ +enum fuse_ext_type { + /* Types 0..31 are reserved for fuse_secctx_header */ + FUSE_MAX_NR_SECCTX = 31, + FUSE_EXT_GROUPS = 32, +}; + +enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, /* no reply */ + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, + FUSE_POLL = 40, + FUSE_NOTIFY_REPLY = 41, + FUSE_BATCH_FORGET = 42, + FUSE_FALLOCATE = 43, + FUSE_READDIRPLUS = 44, + FUSE_RENAME2 = 45, + FUSE_LSEEK = 46, + FUSE_COPY_FILE_RANGE = 47, + FUSE_SETUPMAPPING = 48, + FUSE_REMOVEMAPPING = 49, + FUSE_SYNCFS = 50, + FUSE_TMPFILE = 51, + FUSE_STATX = 52, + + /* CUSE specific operations */ + CUSE_INIT = 4096, + + /* Reserved opcodes: helpful to detect structure endian-ness */ + CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */ + FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */ +}; + +enum fuse_notify_code { + FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, + FUSE_NOTIFY_RETRIEVE = 5, + FUSE_NOTIFY_DELETE = 6, + FUSE_NOTIFY_RESEND = 7, + FUSE_NOTIFY_CODE_MAX, +}; + +/* The read buffer is required to be at least 8k, but may be much larger */ +#define FUSE_MIN_READ_BUFFER 8192 + +#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 + +struct fuse_entry_out { + uint64_t nodeid; /* Inode ID */ + uint64_t generation; /* Inode generation: nodeid:gen must + be unique for the fs's lifetime */ + uint64_t entry_valid; /* Cache timeout for the name */ + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t entry_valid_nsec; + uint32_t attr_valid_nsec; + struct fuse_attr attr; +}; + +struct fuse_forget_in { + uint64_t nlookup; +}; + +struct fuse_forget_one { + uint64_t nodeid; + uint64_t nlookup; +}; + +struct fuse_batch_forget_in { + uint32_t count; + uint32_t dummy; +}; + +struct fuse_getattr_in { + uint32_t getattr_flags; + uint32_t dummy; + uint64_t fh; +}; + +#define FUSE_COMPAT_ATTR_OUT_SIZE 96 + +struct fuse_attr_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t dummy; + struct fuse_attr attr; +}; + +struct fuse_statx_in { + uint32_t getattr_flags; + uint32_t reserved; + uint64_t fh; + uint32_t sx_flags; + uint32_t sx_mask; +}; + +struct fuse_statx_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t flags; + uint64_t spare[2]; + struct fuse_statx stat; +}; + +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 + +struct fuse_mknod_in { + uint32_t mode; + uint32_t rdev; + uint32_t umask; + uint32_t padding; +}; + +struct fuse_mkdir_in { + uint32_t mode; + uint32_t umask; +}; + +struct fuse_rename_in { + uint64_t newdir; +}; + +struct fuse_rename2_in { + uint64_t newdir; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_link_in { + uint64_t oldnodeid; +}; + +struct fuse_setattr_in { + uint32_t valid; + uint32_t padding; + uint64_t fh; + uint64_t size; + uint64_t lock_owner; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t unused4; + uint32_t uid; + uint32_t gid; + uint32_t unused5; +}; + +struct fuse_open_in { + uint32_t flags; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_create_in { + uint32_t flags; + uint32_t mode; + uint32_t umask; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_open_out { + uint64_t fh; + uint32_t open_flags; + int32_t backing_id; +}; + +struct fuse_release_in { + uint64_t fh; + uint32_t flags; + uint32_t release_flags; + uint64_t lock_owner; +}; + +struct fuse_flush_in { + uint64_t fh; + uint32_t unused; + uint32_t padding; + uint64_t lock_owner; +}; + +struct fuse_read_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t read_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +#define FUSE_COMPAT_WRITE_IN_SIZE 24 + +struct fuse_write_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t write_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_write_out { + uint32_t size; + uint32_t padding; +}; + +#define FUSE_COMPAT_STATFS_SIZE 48 + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + +struct fuse_fsync_in { + uint64_t fh; + uint32_t fsync_flags; + uint32_t padding; +}; + +#define FUSE_COMPAT_SETXATTR_IN_SIZE 8 + +struct fuse_setxattr_in { + uint32_t size; + uint32_t flags; + uint32_t setxattr_flags; + uint32_t padding; +}; + +struct fuse_getxattr_in { + uint32_t size; + uint32_t padding; +}; + +struct fuse_getxattr_out { + uint32_t size; + uint32_t padding; +}; + +struct fuse_lk_in { + uint64_t fh; + uint64_t owner; + struct fuse_file_lock lk; + uint32_t lk_flags; + uint32_t padding; +}; + +struct fuse_lk_out { + struct fuse_file_lock lk; +}; + +struct fuse_access_in { + uint32_t mask; + uint32_t padding; +}; + +struct fuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint32_t flags2; + uint32_t unused[11]; +}; + +#define FUSE_COMPAT_INIT_OUT_SIZE 8 +#define FUSE_COMPAT_22_INIT_OUT_SIZE 24 + +struct fuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint16_t max_background; + uint16_t congestion_threshold; + uint32_t max_write; + uint32_t time_gran; + uint16_t max_pages; + uint16_t map_alignment; + uint32_t flags2; + uint32_t max_stack_depth; + uint32_t unused[6]; +}; + +#define CUSE_INIT_INFO_MAX 4096 + +struct cuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; +}; + +struct cuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; + uint32_t max_read; + uint32_t max_write; + uint32_t dev_major; /* chardev major */ + uint32_t dev_minor; /* chardev minor */ + uint32_t spare[10]; +}; + +struct fuse_interrupt_in { + uint64_t unique; +}; + +struct fuse_bmap_in { + uint64_t block; + uint32_t blocksize; + uint32_t padding; +}; + +struct fuse_bmap_out { + uint64_t block; +}; + +struct fuse_ioctl_in { + uint64_t fh; + uint32_t flags; + uint32_t cmd; + uint64_t arg; + uint32_t in_size; + uint32_t out_size; +}; + +struct fuse_ioctl_iovec { + uint64_t base; + uint64_t len; +}; + +struct fuse_ioctl_out { + int32_t result; + uint32_t flags; + uint32_t in_iovs; + uint32_t out_iovs; +}; + +struct fuse_poll_in { + uint64_t fh; + uint64_t kh; + uint32_t flags; + uint32_t events; +}; + +struct fuse_poll_out { + uint32_t revents; + uint32_t padding; +}; + +struct fuse_notify_poll_wakeup_out { + uint64_t kh; +}; + +struct fuse_fallocate_in { + uint64_t fh; + uint64_t offset; + uint64_t length; + uint32_t mode; + uint32_t padding; +}; + +/** + * FUSE request unique ID flag + * + * Indicates whether this is a resend request. The receiver should handle this + * request accordingly. + */ +#define FUSE_UNIQUE_RESEND (1ULL << 63) + +struct fuse_in_header { + uint32_t len; + uint32_t opcode; + uint64_t unique; + uint64_t nodeid; + uint32_t uid; + uint32_t gid; + uint32_t pid; + uint16_t total_extlen; /* length of extensions in 8byte units */ + uint16_t padding; +}; + +struct fuse_out_header { + uint32_t len; + int32_t error; + uint64_t unique; +}; + +struct fuse_dirent { + uint64_t ino; + uint64_t off; + uint32_t namelen; + uint32_t type; + char name[]; +}; + +/* Align variable length records to 64bit boundary */ +#define FUSE_REC_ALIGN(x) \ + (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) + +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) + +struct fuse_direntplus { + struct fuse_entry_out entry_out; + struct fuse_dirent dirent; +}; + +#define FUSE_NAME_OFFSET_DIRENTPLUS \ + offsetof(struct fuse_direntplus, dirent.name) +#define FUSE_DIRENTPLUS_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) + +struct fuse_notify_inval_inode_out { + uint64_t ino; + int64_t off; + int64_t len; +}; + +struct fuse_notify_inval_entry_out { + uint64_t parent; + uint32_t namelen; + uint32_t flags; +}; + +struct fuse_notify_delete_out { + uint64_t parent; + uint64_t child; + uint32_t namelen; + uint32_t padding; +}; + +struct fuse_notify_store_out { + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +struct fuse_notify_retrieve_out { + uint64_t notify_unique; + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +/* Matches the size of fuse_write_in */ +struct fuse_notify_retrieve_in { + uint64_t dummy1; + uint64_t offset; + uint32_t size; + uint32_t dummy2; + uint64_t dummy3; + uint64_t dummy4; +}; + +struct fuse_backing_map { + int32_t fd; + uint32_t flags; + uint64_t padding; +}; + +/* Device ioctls: */ +#define FUSE_DEV_IOC_MAGIC 229 +#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) +#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \ + struct fuse_backing_map) +#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t) + +struct fuse_lseek_in { + uint64_t fh; + uint64_t offset; + uint32_t whence; + uint32_t padding; +}; + +struct fuse_lseek_out { + uint64_t offset; +}; + +struct fuse_copy_file_range_in { + uint64_t fh_in; + uint64_t off_in; + uint64_t nodeid_out; + uint64_t fh_out; + uint64_t off_out; + uint64_t len; + uint64_t flags; +}; + +#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0) +#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1) +struct fuse_setupmapping_in { + /* An already open handle */ + uint64_t fh; + /* Offset into the file to start the mapping */ + uint64_t foffset; + /* Length of mapping required */ + uint64_t len; + /* Flags, FUSE_SETUPMAPPING_FLAG_* */ + uint64_t flags; + /* Offset in Memory Window */ + uint64_t moffset; +}; + +struct fuse_removemapping_in { + /* number of fuse_removemapping_one follows */ + uint32_t count; +}; + +struct fuse_removemapping_one { + /* Offset into the dax window start the unmapping */ + uint64_t moffset; + /* Length of mapping required */ + uint64_t len; +}; + +#define FUSE_REMOVEMAPPING_MAX_ENTRY \ + (PAGE_SIZE / sizeof(struct fuse_removemapping_one)) + +struct fuse_syncfs_in { + uint64_t padding; +}; + +/* + * For each security context, send fuse_secctx with size of security context + * fuse_secctx will be followed by security context name and this in turn + * will be followed by actual context label. + * fuse_secctx, name, context + */ +struct fuse_secctx { + uint32_t size; + uint32_t padding; +}; + +/* + * Contains the information about how many fuse_secctx structures are being + * sent and what's the total size of all security contexts (including + * size of fuse_secctx_header). + * + */ +struct fuse_secctx_header { + uint32_t size; + uint32_t nr_secctx; +}; + +/** + * struct fuse_ext_header - extension header + * @size: total size of this extension including this header + * @type: type of extension + * + * This is made compatible with fuse_secctx_header by using type values > + * FUSE_MAX_NR_SECCTX + */ +struct fuse_ext_header { + uint32_t size; + uint32_t type; +}; + +/** + * struct fuse_supp_groups - Supplementary group extension + * @nr_groups: number of supplementary groups + * @groups: flexible array of group IDs + */ +struct fuse_supp_groups { + uint32_t nr_groups; + uint32_t groups[]; +}; + +#endif /* _LINUX_FUSE_H */ diff --git a/include/fuse_log.h b/include/fuse_log.h new file mode 100644 index 0000000..c855957 --- /dev/null +++ b/include/fuse_log.h @@ -0,0 +1,94 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2019 Red Hat, Inc. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#ifndef FUSE_LOG_H_ +#define FUSE_LOG_H_ + +/** @file + * + * This file defines the logging interface of FUSE + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Log severity level + * + * These levels correspond to syslog(2) log levels since they are widely used. + */ +enum fuse_log_level { + FUSE_LOG_EMERG, + FUSE_LOG_ALERT, + FUSE_LOG_CRIT, + FUSE_LOG_ERR, + FUSE_LOG_WARNING, + FUSE_LOG_NOTICE, + FUSE_LOG_INFO, + FUSE_LOG_DEBUG +}; + +/** + * Log message handler function. + * + * This function must be thread-safe. It may be called from any libfuse + * function, including fuse_parse_cmdline() and other functions invoked before + * a FUSE filesystem is created. + * + * Install a custom log message handler function using fuse_set_log_func(). + * + * @param level log severity level + * @param fmt sprintf-style format string including newline + * @param ap format string arguments + */ +typedef void (*fuse_log_func_t)(enum fuse_log_level level, + const char *fmt, va_list ap); + +/** + * Install a custom log handler function. + * + * Log messages are emitted by libfuse functions to report errors and debug + * information. Messages are printed to stderr by default but this can be + * overridden by installing a custom log message handler function. + * + * The log message handler function is global and affects all FUSE filesystems + * created within this process. + * + * @param func a custom log message handler function or NULL to revert to + * the default + */ +void fuse_set_log_func(fuse_log_func_t func); + +/** + * Emit a log message + * + * @param level severity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc) + * @param fmt sprintf-style format string including newline + */ +void fuse_log(enum fuse_log_level level, const char *fmt, ...); + +/** + * Switch default log handler from stderr to syslog + * + * Passed options are according to 'man 3 openlog' + */ +void fuse_log_enable_syslog(const char *ident, int option, int facility); + +/** + * To be called at teardown to close syslog. +*/ +void fuse_log_close_syslog(void); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_LOG_H_ */ diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h new file mode 100644 index 0000000..c7b44d9 --- /dev/null +++ b/include/fuse_lowlevel.h @@ -0,0 +1,2322 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#ifndef FUSE_LOWLEVEL_H_ +#define FUSE_LOWLEVEL_H_ + +/** @file + * + * Low level API + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this + * header. To use the newest API define it to 35 (recommended for any + * new application). + */ + +#ifndef FUSE_USE_VERSION +#error FUSE_USE_VERSION not defined +#endif + +#include "fuse_common.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Miscellaneous definitions * + * ----------------------------------------------------------- */ + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/** Inode number type */ +typedef uint64_t fuse_ino_t; + +/** Request pointer type */ +typedef struct fuse_req *fuse_req_t; + +/** + * Session + * + * This provides hooks for processing requests, and exiting + */ +struct fuse_session; + +/** Directory entry parameters supplied to fuse_reply_entry() */ +struct fuse_entry_param { + /** Unique inode number + * + * In lookup, zero means negative entry (from version 2.5) + * Returning ENOENT also means negative entry, but by setting zero + * ino the kernel may cache negative entries for entry_timeout + * seconds. + */ + fuse_ino_t ino; + + /** Generation number for this entry. + * + * If the file system will be exported over NFS, the + * ino/generation pairs need to be unique over the file + * system's lifetime (rather than just the mount time). So if + * the file system reuses an inode after it has been deleted, + * it must assign a new, previously unused generation number + * to the inode at the same time. + * + */ + uint64_t generation; + + /** Inode attributes. + * + * Even if attr_timeout == 0, attr must be correct. For example, + * for open(), FUSE uses attr.st_size from lookup() to determine + * how many bytes to request. If this value is not correct, + * incorrect data will be returned. + */ + struct stat attr; + + /** Validity timeout (in seconds) for inode attributes. If + attributes only change as a result of requests that come + through the kernel, this should be set to a very large + value. */ + double attr_timeout; + + /** Validity timeout (in seconds) for the name. If directory + entries are changed/deleted only as a result of requests + that come through the kernel, this should be set to a very + large value. */ + double entry_timeout; +}; + +/** + * Additional context associated with requests. + * + * Note that the reported client uid, gid and pid may be zero in some + * situations. For example, if the FUSE file system is running in a + * PID or user namespace but then accessed from outside the namespace, + * there is no valid uid/pid/gid that could be reported. + */ +struct fuse_ctx { + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** Thread ID of the calling process */ + pid_t pid; + + /** Umask of the calling process */ + mode_t umask; +}; + +struct fuse_forget_data { + fuse_ino_t ino; + uint64_t nlookup; +}; + +struct fuse_custom_io { + ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata); + ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata); + ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata); + ssize_t (*splice_send)(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata); + int (*clone_fd)(int master_fd); +}; + +/** + * Flags for fuse_lowlevel_notify_entry() + * 0 = invalidate entry + * FUSE_LL_EXPIRE_ONLY = expire entry +*/ +enum fuse_notify_entry_flags { + FUSE_LL_INVALIDATE = 0, + FUSE_LL_EXPIRE_ONLY = (1 << 0), +}; + +/* 'to_set' flags in setattr */ +#define FUSE_SET_ATTR_MODE (1 << 0) +#define FUSE_SET_ATTR_UID (1 << 1) +#define FUSE_SET_ATTR_GID (1 << 2) +#define FUSE_SET_ATTR_SIZE (1 << 3) +#define FUSE_SET_ATTR_ATIME (1 << 4) +#define FUSE_SET_ATTR_MTIME (1 << 5) +#define FUSE_SET_ATTR_ATIME_NOW (1 << 7) +#define FUSE_SET_ATTR_MTIME_NOW (1 << 8) +#define FUSE_SET_ATTR_FORCE (1 << 9) +#define FUSE_SET_ATTR_CTIME (1 << 10) +#define FUSE_SET_ATTR_KILL_SUID (1 << 11) +#define FUSE_SET_ATTR_KILL_SGID (1 << 12) +#define FUSE_SET_ATTR_FILE (1 << 13) +#define FUSE_SET_ATTR_KILL_PRIV (1 << 14) +#define FUSE_SET_ATTR_OPEN (1 << 15) +#define FUSE_SET_ATTR_TIMES_SET (1 << 16) +#define FUSE_SET_ATTR_TOUCH (1 << 17) + +/* ----------------------------------------------------------- * + * Request methods and replies * + * ----------------------------------------------------------- */ + +/** + * Low level filesystem operations + * + * Most of the methods (with the exception of init and destroy) + * receive a request handle (fuse_req_t) as their first argument. + * This handle must be passed to one of the specified reply functions. + * + * This may be done inside the method invocation, or after the call + * has returned. The request handle is valid until one of the reply + * functions is called. + * + * Other pointer arguments (name, fuse_file_info, etc) are not valid + * after the call has returned, so if they are needed later, their + * contents have to be copied. + * + * In general, all methods are expected to perform any necessary + * permission checking. However, a filesystem may delegate this task + * to the kernel by passing the `default_permissions` mount option to + * `fuse_session_new()`. In this case, methods will only be called if + * the kernel's permission check has succeeded. + * + * The filesystem sometimes needs to handle a return value of -ENOENT + * from the reply function, which means, that the request was + * interrupted, and the reply discarded. For example if + * fuse_reply_open() return -ENOENT means, that the release method for + * this file will not be called. + * + * This data structure is ABI sensitive, on adding new functions these need to + * be appended at the end of the struct + */ +struct fuse_lowlevel_ops { + /** + * Initialize filesystem + * + * This function is called when libfuse establishes + * communication with the FUSE kernel module. The file system + * should use this module to inspect and/or modify the + * connection parameters provided in the `conn` structure. + * + * Note that some parameters may be overwritten by options + * passed to fuse_session_new() which take precedence over the + * values set in this handler. + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_session_new() + */ + void (*init) (void *userdata, struct fuse_conn_info *conn); + + /** + * Clean up filesystem. + * + * Called on filesystem exit. When this method is called, the + * connection to the kernel may be gone already, so that eg. calls + * to fuse_lowlevel_notify_* will fail. + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_session_new() + */ + void (*destroy) (void *userdata); + + /** + * Look up a directory entry by name and get its attributes. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name the name to look up + */ + void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Forget about an inode + * + * This function is called when the kernel removes an inode + * from its internal caches. + * + * The inode's lookup count increases by one for every call to + * fuse_reply_entry and fuse_reply_create. The nlookup parameter + * indicates by how much the lookup count should be decreased. + * + * Inodes with a non-zero lookup count may receive request from + * the kernel even after calls to unlink, rmdir or (when + * overwriting an existing file) rename. Filesystems must handle + * such requests properly and it is recommended to defer removal + * of the inode until the lookup count reaches zero. Calls to + * unlink, rmdir or rename will be followed closely by forget + * unless the file or directory is open, in which case the + * kernel issues forget only after the release or releasedir + * calls. + * + * Note that if a file system will be exported over NFS the + * inodes lifetime must extend even beyond forget. See the + * generation field in struct fuse_entry_param above. + * + * On unmount the lookup count for all inodes implicitly drops + * to zero. It is not guaranteed that the file system will + * receive corresponding forget messages for the affected + * inodes. + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + * @param ino the inode number + * @param nlookup the number of lookups to forget + */ + void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup); + + /** + * Get file attributes. + * + * If writeback caching is enabled, the kernel may have a + * better idea of a file's length than the FUSE file system + * (eg if there has been a write that extended the file size, + * but that has not yet been passed to the filesystem. + * + * In this case, the st_size value provided by the file system + * will be ignored. + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information, or NULL + */ + void (*getattr) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Set file attributes + * + * In the 'attr' argument only members indicated by the 'to_set' + * bitmask contain valid values. Other members contain undefined + * values. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits if the file + * size or owner is being changed. + * + * This method will not be called to update st_atime or st_ctime implicitly + * (eg. after a read() request), and only be called to implicitly update st_mtime + * if writeback caching is active. It is the filesystem's responsibility to update + * these timestamps when needed, and (if desired) to implement mount options like + * `noatime` or `relatime`. + * + * If the setattr was invoked from the ftruncate() system call + * under Linux kernel versions 2.6.15 or later, the fi->fh will + * contain the value set by the open method or will be undefined + * if the open method didn't set any value. Otherwise (not + * ftruncate call, or kernel version earlier than 2.6.15) the fi + * parameter will be NULL. + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param attr the attributes + * @param to_set bit mask of attributes which should be set + * @param fi file information, or NULL + */ + void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi); + + /** + * Read symbolic link + * + * Valid replies: + * fuse_reply_readlink + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + */ + void (*readlink) (fuse_req_t req, fuse_ino_t ino); + + /** + * Create file node + * + * Create a regular file, character device, block device, fifo or + * socket node. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param rdev the device number (only valid if created file is a device) + */ + void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev); + + /** + * Create a directory + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode with which to create the new file + */ + void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode); + + /** + * Remove a file + * + * If the file's inode's lookup count is non-zero, the file + * system is expected to postpone any removal of the inode + * until the lookup count reaches zero (see description of the + * forget function). + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Remove a directory + * + * If the directory's inode's lookup count is non-zero, the + * file system is expected to postpone any removal of the + * inode until the lookup count reaches zero (see description + * of the forget function). + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Create a symbolic link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param link the contents of the symbolic link + * @param parent inode number of the parent directory + * @param name to create + */ + void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name); + + /** Rename a file + * + * If the target exists it should be atomically replaced. If + * the target's inode's lookup count is non-zero, the file + * system is expected to postpone any removal of the inode + * until the lookup count reaches zero (see description of the + * forget function). + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EINVAL, i.e. all + * future bmap requests will fail with EINVAL without being + * send to the filesystem process. + * + * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If + * RENAME_NOREPLACE is specified, the filesystem must not + * overwrite *newname* if it exists and return an error + * instead. If `RENAME_EXCHANGE` is specified, the filesystem + * must atomically exchange the two files, i.e. both must + * exist and neither may be deleted. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the old parent directory + * @param name old name + * @param newparent inode number of the new parent directory + * @param newname new name + */ + void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags); + + /** + * Create a hard link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param ino the old inode number + * @param newparent inode number of the new parent directory + * @param newname new name to create + */ + void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname); + + /** + * Open a file + * + * Open flags are available in fi->flags. The following rules + * apply. + * + * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be + * filtered out / handled by the kernel. + * + * - Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used + * by the filesystem to check if the operation is + * permitted. If the ``-o default_permissions`` mount + * option is given, this check is already done by the + * kernel before calling open() and may thus be omitted by + * the filesystem. + * + * - When writeback caching is enabled, the kernel may send + * read requests even for files opened with O_WRONLY. The + * filesystem should be prepared to handle this. + * + * - When writeback caching is disabled, the filesystem is + * expected to properly handle the O_APPEND flag and ensure + * that each write is appending to the end of the file. + * + * - When writeback caching is enabled, the kernel will + * handle O_APPEND. However, unless all changes to the file + * come through the kernel this will not work reliably. The + * filesystem should thus either ignore the O_APPEND flag + * (and let the kernel handle it), or return an error + * (indicating that reliably O_APPEND is not available). + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in for more details. + * + * If this request is answered with an error code of ENOSYS + * and FUSE_CAP_NO_OPEN_SUPPORT is set in + * `fuse_conn_info.capable`, this is treated as success and + * future calls to open and release will also succeed without being + * sent to the filesystem process. + * + * To get this behavior without providing an opendir handler, you may + * set FUSE_CAP_NO_OPEN_SUPPORT in `fuse_conn_info.want` on supported + * kernels to automatically get the zero message open(). + * + * If this callback is not provided and FUSE_CAP_NO_OPEN_SUPPORT is not + * set in `fuse_conn_info.want` then an empty reply will be sent. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*open) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read data + * + * Read should send exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the file + * has been opened in 'direct_io' mode, in which case the return + * value of the read system call will reflect the return value of + * this operation. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_iov + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size number of bytes to read + * @param off offset to read from + * @param fi file information + */ + void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Write data + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the file has + * been opened in 'direct_io' mode, in which case the return value + * of the write system call will reflect the return value of this + * operation. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param buf data to write + * @param size number of bytes to write + * @param off offset to write to + * @param fi file information + */ + void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi); + + /** + * Flush method + * + * This is called on each close() of the opened file. + * + * Since file descriptors can be duplicated (dup, dup2, fork), for + * one open call there may be many flush calls. + * + * Filesystems shouldn't assume that flush will always be called + * after some writes, or that if will be called at all. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * NOTE: the name of the method is misleading, since (unlike + * fsync) the filesystem is not forced to flush pending writes. + * One reason to flush data is if the filesystem wants to return + * write errors during close. However, such use is non-portable + * because POSIX does not require [close] to wait for delayed I/O to + * complete. + * + * If the filesystem supports file locking operations (setlk, + * getlk) it should remove all locks belonging to 'fi->owner'. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to flush() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * + * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html + */ + void (*flush) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open call there will be exactly one release call (unless + * the filesystem is force-unmounted). + * + * The filesystem may reply with an error, but error values are + * not returned to close() or munmap() which triggered the + * release. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * fi->flags will contain the same flags as for open. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*release) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to fsync() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Open a directory + * + * Filesystem may store an arbitrary file handle (pointer, index, + * etc) in fi->fh, and use this in other all other directory + * stream operations (readdir, releasedir, fsyncdir). + * + * If this request is answered with an error code of ENOSYS and + * FUSE_CAP_NO_OPENDIR_SUPPORT is set in `fuse_conn_info.capable`, + * this is treated as success and future calls to opendir and + * releasedir will also succeed without being sent to the filesystem + * process. In addition, the kernel will cache readdir results + * as if opendir returned FOPEN_KEEP_CACHE | FOPEN_CACHE_DIR. + * + * To get this behavior without providing an opendir handler, you may + * set FUSE_CAP_NO_OPENDIR_SUPPORT in `fuse_conn_info.want` on supported + * kernels to automatically get the zero message opendir(). + * + * If this callback is not provided and FUSE_CAP_NO_OPENDIR_SUPPORT is + * not set in `fuse_conn_info.want` then an empty reply will be sent. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*opendir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read directory + * + * Send a buffer filled using fuse_add_direntry(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Returning a directory entry from readdir() does not affect + * its lookup count. + * + * If off_t is non-zero, then it will correspond to one of the off_t + * values that was previously returned by readdir() for the same + * directory handle. In this case, readdir() should skip over entries + * coming before the position defined by the off_t value. If entries + * are added or removed while the directory handle is open, the filesystem + * may still include the entries that have been removed, and may not + * report the entries that have been created. However, addition or + * removal of entries must never cause readdir() to skip over unrelated + * entries or to report them more than once. This means + * that off_t can not be a simple index that enumerates the entries + * that have been returned but must contain sufficient information to + * uniquely determine the next directory entry to return even when the + * set of entries is changing. + * + * The function does not have to report the '.' and '..' + * entries, but is allowed to do so. Note that, if readdir does + * not return '.' or '..', they will not be implicitly returned, + * and this behavior is observable by the caller. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Release an open directory + * + * For every opendir call there will be exactly one releasedir + * call (unless the filesystem is force-unmounted). + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*releasedir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize directory contents + * + * If the datasync parameter is non-zero, then only the directory + * contents should be flushed, not the meta data. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to fsyncdir() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Get file system statistics + * + * Valid replies: + * fuse_reply_statfs + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number, zero means "undefined" + */ + void (*statfs) (fuse_req_t req, fuse_ino_t ino); + + /** + * Set an extended attribute + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future setxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + */ + void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags); + + /** + * Get an extended attribute + * + * If size is zero, the size of the value should be sent with + * fuse_reply_xattr. + * + * If the size is non-zero, and the value fits in the buffer, the + * value should be sent with fuse_reply_buf. + * + * If the size is too small for the value, the ERANGE error should + * be sent. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future getxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + * @param size maximum size of the value to send + */ + void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size); + + /** + * List extended attribute names + * + * If size is zero, the total size of the attribute list should be + * sent with fuse_reply_xattr. + * + * If the size is non-zero, and the null character separated + * attribute list fits in the buffer, the list should be sent with + * fuse_reply_buf. + * + * If the size is too small for the list, the ERANGE error should + * be sent. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future listxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum size of the list to send + */ + void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); + + /** + * Remove an extended attribute + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future removexattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + */ + void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); + + /** + * Check file access permissions + * + * This will be called for the access() and chdir() system + * calls. If the 'default_permissions' mount option is given, + * this method is not called. + * + * This method is not called under Linux kernel versions 2.4.x + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent success, i.e. this and all future access() + * requests will succeed without being send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param mask requested access mode + */ + void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * See the description of the open handler for more + * information. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + * + * If this request is answered with an error code of ENOSYS, the handler + * is treated as not implemented (i.e., for this and future requests the + * mknod() and open() handlers will be called instead). + * + * Valid replies: + * fuse_reply_create + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param fi file information + */ + void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi); + + /** + * Test for a POSIX file lock + * + * Valid replies: + * fuse_reply_lock + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to test + */ + void (*getlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock); + + /** + * Acquire, modify or release a POSIX file lock + * + * For POSIX threads (NPTL) there's a 1-1 relation between pid and + * owner, but otherwise this is not always the case. For checking + * lock ownership, 'fi->owner' must be used. The l_pid field in + * 'struct flock' should only be used to fill in this field in + * getlk(). + * + * Note: if the locking methods are not implemented, the kernel + * will still allow file locking to work locally. Hence these are + * only interesting for network filesystems and similar. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to set + * @param sleep locking operation may sleep + */ + void (*setlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, + struct flock *lock, int sleep); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure, i.e. all future bmap() requests will + * fail with the same error code without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_bmap + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param blocksize unit of block index + * @param idx block index within file + */ + void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize, + uint64_t idx); + +#if FUSE_USE_VERSION < 35 + void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd, + void *arg, struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); +#else + /** + * Ioctl + * + * Note: For unrestricted ioctls (not allowed for FUSE + * servers), data in and out areas can be discovered by giving + * iovs and setting FUSE_IOCTL_RETRY in *flags*. For + * restricted ioctls, kernel prepares in/out data area + * according to the information encoded in cmd. + * + * Valid replies: + * fuse_reply_ioctl_retry + * fuse_reply_ioctl + * fuse_reply_ioctl_iov + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param cmd ioctl command + * @param arg ioctl argument + * @param fi file information + * @param flags for FUSE_IOCTL_* flags + * @param in_buf data fetched from the caller + * @param in_bufsz number of fetched bytes + * @param out_bufsz maximum size of output data + * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits. + */ + void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); +#endif + + /** + * Poll for IO readiness + * + * The client should immediately respond with fuse_reply_poll(), + * setting revents appropriately according to which events are ready. + * + * Additionally, if ph is non-NULL, the client must retain it and + * notify when all future IO readiness events occur by calling + * fuse_lowlevel_notify_poll() with the specified ph. + * + * Regardless of the number of times poll with a non-NULL ph is + * received, a single notify_poll is enough to service all. (Notifying + * more times incurs overhead but doesn't harm correctness.) Any + * additional received handles can be immediately destroyed. + * + * The callee is responsible for destroying ph with + * fuse_pollhandle_destroy() when no longer in use. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as success (with a kernel-defined default poll-mask) and + * future calls to poll() will succeed the same way without being send + * to the filesystem process. + * + * Valid replies: + * fuse_reply_poll + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param ph poll handle to be used for notification + */ + void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); + + /** + * Write data made available in a buffer + * + * This is a more generic version of the ->write() method. If + * FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the + * kernel supports splicing from the fuse device, then the + * data will be made available in pipe for supporting zero + * copy data transfer. + * + * buf->count is guaranteed to be one (and thus buf->idx is + * always zero). The write_buf handler must ensure that + * bufv->off is correctly updated (reflecting the number of + * bytes read from bufv->buf[0]). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param bufv buffer containing the data + * @param off offset to write to + * @param fi file information + */ + void (*write_buf) (fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *bufv, off_t off, + struct fuse_file_info *fi); + + /** + * Callback function for the retrieve request + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + * @param cookie user data supplied to fuse_lowlevel_notify_retrieve() + * @param ino the inode number supplied to fuse_lowlevel_notify_retrieve() + * @param offset the offset supplied to fuse_lowlevel_notify_retrieve() + * @param bufv the buffer containing the returned data + */ + void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv); + + /** + * Forget about multiple inodes + * + * See description of the forget function for more + * information. + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + */ + void (*forget_multi) (fuse_req_t req, size_t count, + struct fuse_forget_data *forgets); + + /** + * Acquire, modify or release a BSD file lock + * + * Note: if the locking methods are not implemented, the kernel + * will still allow file locking to work locally. Hence these are + * only interesting for network filesystems and similar. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param op the locking operation, see flock(2) + */ + void (*flock) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, int op); + + /** + * Allocate requested space. If this function returns success then + * subsequent writes to the specified range shall not fail due to the lack + * of free space on the file system storage media. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future fallocate() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param offset starting point for allocated region + * @param length size of allocated region + * @param mode determines the operation to be performed on the given range, + * see fallocate(2) + */ + void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi); + + /** + * Read directory with attributes + * + * Send a buffer filled using fuse_add_direntry_plus(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * In contrast to readdir() (which does not affect the lookup counts), + * the lookup count of every entry returned by readdirplus(), except "." + * and "..", is incremented by one. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Copy a range of data from one file to another + * + * Performs an optimized copy between two file descriptors without the + * additional cost of transferring data through the FUSE kernel module + * to user space (glibc) and then back into the FUSE filesystem again. + * + * In case this method is not implemented, glibc falls back to reading + * data from the source and writing to the destination. Effectively + * doing an inefficient copy of the data. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future copy_file_range() requests will fail with EOPNOTSUPP without + * being send to the filesystem process. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino_in the inode number or the source file + * @param off_in starting point from were the data should be read + * @param fi_in file information of the source file + * @param ino_out the inode number or the destination file + * @param off_out starting point where the data should be written + * @param fi_out file information of the destination file + * @param len maximum size of the data to copy + * @param flags passed along with the copy_file_range() syscall + */ + void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in, + off_t off_in, struct fuse_file_info *fi_in, + fuse_ino_t ino_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags); + + /** + * Find next data or hole after the specified offset + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure, i.e. all future lseek() requests will + * fail with the same error code without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_lseek + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param off offset to start search from + * @param whence either SEEK_DATA or SEEK_HOLE + * @param fi file information + */ + void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi); + + + /** + * Create a tempfile + * + * Tempfile means an anonymous file. It can be made into a normal file later + * by using linkat or such. + * + * If this is answered with an error ENOSYS this is treated by the kernel as + * a permanent failure and it will disable the feature and not ask again. + * + * Valid replies: + * fuse_reply_create + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param mode file type and mode with which to create the new file + * @param fi file information + */ + void (*tmpfile) (fuse_req_t req, fuse_ino_t parent, + mode_t mode, struct fuse_file_info *fi); + +}; + +/** + * Reply with an error code or success. + * + * Possible requests: + * all except forget, forget_multi, retrieve_reply + * + * Wherever possible, error codes should be chosen from the list of + * documented error conditions in the corresponding system calls + * manpage. + * + * An error code of ENOSYS is sometimes treated specially. This is + * indicated in the documentation of the affected handler functions. + * + * The following requests may be answered with a zero error code: + * unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, + * removexattr, setlk. + * + * @param req request handle + * @param err the positive error value, or zero for success + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_err(fuse_req_t req, int err); + +/** + * Don't send reply + * + * Possible requests: + * forget + * forget_multi + * retrieve_reply + * + * @param req request handle + */ +void fuse_reply_none(fuse_req_t req); + +/** + * Reply with a directory entry + * + * Possible requests: + * lookup, mknod, mkdir, symlink, link + * + * Side effects: + * increments the lookup count on success + * + * @param req request handle + * @param e the entry parameters + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e); + +/** + * Reply with a directory entry and open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes + * + * Possible requests: + * create + * + * Side effects: + * increments the lookup count on success + * + * @param req request handle + * @param e the entry parameters + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *fi); + +/** + * Reply with attributes + * + * Possible requests: + * getattr, setattr + * + * @param req request handle + * @param attr the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout); + +/** + * Reply with the contents of a symbolic link + * + * Possible requests: + * readlink + * + * @param req request handle + * @param link symbolic link contents + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_readlink(fuse_req_t req, const char *link); + +/** + * Setup passthrough backing file for open reply + * + * Currently there should be only one backing id per node / backing file. + * + * Possible requests: + * open, opendir, create + * + * @param req request handle + * @param fd backing file descriptor + * @return positive backing id for success, 0 for failure + */ +int fuse_passthrough_open(fuse_req_t req, int fd); +int fuse_passthrough_close(fuse_req_t req, int backing_id); + +/** + * Reply with open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes, + * + * Possible requests: + * open, opendir + * + * @param req request handle + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi); + +/** + * Reply with number of bytes written + * + * Possible requests: + * write + * + * @param req request handle + * @param count the number of bytes written + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_write(fuse_req_t req, size_t count); + +/** + * Reply with data + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param buf buffer containing data + * @param size the size of data in bytes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size); + +/** + * Reply with data copied/moved from buffer(s) + * + * Zero copy data transfer ("splicing") will be used under + * the following circumstances: + * + * 1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and + * 2. the kernel supports splicing from the fuse device + * (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and + * 3. *flags* does not contain FUSE_BUF_NO_SPLICE + * 4. The amount of data that is provided in file-descriptor backed + * buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) + * is at least twice the page size. + * + * In order for SPLICE_F_MOVE to be used, the following additional + * conditions have to be fulfilled: + * + * 1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and + * 2. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in + fuse_conn_info.capable), and + * 3. *flags* contains FUSE_BUF_SPLICE_MOVE + * + * Note that, if splice is used, the data is actually spliced twice: + * once into a temporary pipe (to prepend header data), and then again + * into the kernel. If some of the provided buffers are memory-backed, + * the data in them is copied in step one and spliced in step two. + * + * The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags + * are silently ignored. + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * Side effects: + * when used to return data from a readdirplus() (but not readdir()) + * call, increments the lookup count of each returned entry by one + * on success. + * + * @param req request handle + * @param bufv buffer vector + * @param flags flags controlling the copy + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); + +/** + * Reply with data vector + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param iov the vector containing the data + * @param count the size of vector + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count); + +/** + * Reply with filesystem statistics + * + * Possible requests: + * statfs + * + * @param req request handle + * @param stbuf filesystem statistics + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf); + +/** + * Reply with needed buffer size + * + * Possible requests: + * getxattr, listxattr + * + * @param req request handle + * @param count the buffer size needed in bytes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_xattr(fuse_req_t req, size_t count); + +/** + * Reply with file lock information + * + * Possible requests: + * getlk + * + * @param req request handle + * @param lock the lock information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_lock(fuse_req_t req, const struct flock *lock); + +/** + * Reply with block index + * + * Possible requests: + * bmap + * + * @param req request handle + * @param idx block index within device + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_bmap(fuse_req_t req, uint64_t idx); + +/* ----------------------------------------------------------- * + * Filling a buffer in readdir * + * ----------------------------------------------------------- */ + +/** + * Add a directory entry to the buffer + * + * Buffer needs to be large enough to hold the entry. If it's not, + * then the entry is not filled in but the size of the entry is still + * returned. The caller can check this by comparing the bufsize + * parameter with the returned entry size. If the entry size is + * larger than the buffer size, the operation failed. + * + * From the 'stbuf' argument the st_ino field and bits 12-15 of the + * st_mode field are used. The other fields are ignored. + * + * *off* should be any non-zero value that the filesystem can use to + * identify the current point in the directory stream. It does not + * need to be the actual physical position. A value of zero is + * reserved to mean "from the beginning", and should therefore never + * be used (the first call to fuse_add_direntry should be passed the + * offset of the second directory entry). + * + * @param req request handle + * @param buf the point where the new entry will be added to the buffer + * @param bufsize remaining size of the buffer + * @param name the name of the entry + * @param stbuf the file attributes + * @param off the offset of the next entry + * @return the space needed for the entry + */ +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, + off_t off); + +/** + * Add a directory entry to the buffer with the attributes + * + * See documentation of `fuse_add_direntry()` for more details. + * + * @param req request handle + * @param buf the point where the new entry will be added to the buffer + * @param bufsize remaining size of the buffer + * @param name the name of the entry + * @param e the directory entry + * @param off the offset of the next entry + * @return the space needed for the entry + */ +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off); + +/** + * Reply to ask for data fetch and output buffer preparation. ioctl + * will be retried with the specified input data fetched and output + * buffer prepared. + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param in_iov iovec specifying data to fetch from the caller + * @param in_count number of entries in in_iov + * @param out_iov iovec specifying addresses to write output to + * @param out_count number of entries in out_iov + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_ioctl_retry(fuse_req_t req, + const struct iovec *in_iov, size_t in_count, + const struct iovec *out_iov, size_t out_count); + +/** + * Reply to finish ioctl + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param result result to be passed to the caller + * @param buf buffer containing output data + * @param size length of output data + */ +int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size); + +/** + * Reply to finish ioctl with iov buffer + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param result result to be passed to the caller + * @param iov the vector containing the data + * @param count the size of vector + */ +int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, + int count); + +/** + * Reply with poll result event mask + * + * @param req request handle + * @param revents poll result event mask + */ +int fuse_reply_poll(fuse_req_t req, unsigned revents); + +/** + * Reply with offset + * + * Possible requests: + * lseek + * + * @param req request handle + * @param off offset of next data or hole + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_lseek(fuse_req_t req, off_t off); + +/* ----------------------------------------------------------- * + * Notification * + * ----------------------------------------------------------- */ + +/** + * Notify IO readiness event + * + * For more information, please read comment for poll operation. + * + * @param ph poll handle to notify IO readiness event for + */ +int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph); + +/** + * Notify to invalidate cache for an inode. + * + * Added in FUSE protocol version 7.12. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * If the filesystem has writeback caching enabled, invalidating an + * inode will first trigger a writeback of all dirty pages. The call + * will block until all writeback requests have completed and the + * inode has been invalidated. It will, however, not wait for + * completion of pending writeback requests that have been issued + * before. + * + * If there are no dirty pages, this function will never block. + * + * @param se the session object + * @param ino the inode number + * @param off the offset in the inode where to start invalidating + * or negative to invalidate attributes only + * @param len the amount of cache to invalidate or 0 for all + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, + off_t off, off_t len); + +/** + * Notify to invalidate parent attributes and the dentry matching parent/name + * + * To avoid a deadlock this function must not be called in the + * execution path of a related filesystem operation or within any code + * that could hold a lock that could be needed to execute such an + * operation. As of kernel 4.18, a "related operation" is a lookup(), + * symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() + * request for the parent, and a setattr(), unlink(), rmdir(), + * rename(), setxattr(), removexattr(), readdir() or readdirplus() + * request for the inode itself. + * + * When called correctly, this function will never block. + * + * Added in FUSE protocol version 7.12. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen); + +/** + * Notify to expire parent attributes and the dentry matching parent/name + * + * Same restrictions apply as for fuse_lowlevel_notify_inval_entry() + * + * Compared to invalidating an entry, expiring the entry results not in a + * forceful removal of that entry from kernel cache but instead the next access + * to it forces a lookup from the filesystem. + * + * This makes a difference for overmounted dentries, where plain invalidation + * would detach all submounts before dropping the dentry from the cache. + * If only expiry is set on the dentry, then any overmounts are left alone and + * until ->d_revalidate() is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + * + * Added in FUSE protocol version 7.38. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do nothing. + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure, -enosys if no kernel support +*/ +int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen); + +/** + * This function behaves like fuse_lowlevel_notify_inval_entry() with + * the following additional effect (at least as of Linux kernel 4.8): + * + * If the provided *child* inode matches the inode that is currently + * associated with the cached dentry, and if there are any inotify + * watches registered for the dentry, then the watchers are informed + * that the dentry has been deleted. + * + * To avoid a deadlock this function must not be called while + * executing a related filesystem operation or while holding a lock + * that could be needed to execute such an operation (see the + * description of fuse_lowlevel_notify_inval_entry() for more + * details). + * + * When called correctly, this function will never block. + * + * Added in FUSE protocol version 7.18. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param parent inode number + * @param child inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_delete(struct fuse_session *se, + fuse_ino_t parent, fuse_ino_t child, + const char *name, size_t namelen); + +/** + * Store data to the kernel buffers + * + * Synchronously store data in the kernel buffers belonging to the + * given inode. The stored data is marked up-to-date (no read will be + * performed against it, unless it's invalidated or evicted from the + * cache). + * + * If the stored data overflows the current file size, then the size + * is extended, similarly to a write(2) on the filesystem. + * + * If this function returns an error, then the store wasn't fully + * completed, but it may have been partially completed. + * + * Added in FUSE protocol version 7.15. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param ino the inode number + * @param offset the starting offset into the file to store to + * @param bufv buffer vector + * @param flags flags controlling the copy + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); +/** + * Retrieve data from the kernel buffers + * + * Retrieve data in the kernel buffers belonging to the given inode. + * If successful then the retrieve_reply() method will be called with + * the returned data. + * + * Only present pages are returned in the retrieve reply. Retrieving + * stops when it finds a non-present page and only data prior to that + * is returned. + * + * If this function returns an error, then the retrieve will not be + * completed and no reply will be sent. + * + * This function doesn't change the dirty state of pages in the kernel + * buffer. For dirty pages the write() method will be called + * regardless of having been retrieved previously. + * + * Added in FUSE protocol version 7.15. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param ino the inode number + * @param size the number of bytes to retrieve + * @param offset the starting offset into the file to retrieve from + * @param cookie user data to supply to the reply callback + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, + size_t size, off_t offset, void *cookie); + + +/* ----------------------------------------------------------- * + * Utility functions * + * ----------------------------------------------------------- */ + +/** + * Get the userdata from the request + * + * @param req request handle + * @return the user data passed to fuse_session_new() + */ +void *fuse_req_userdata(fuse_req_t req); + +/** + * Get the context from the request + * + * The pointer returned by this function will only be valid for the + * request's lifetime + * + * @param req request handle + * @return the context structure + */ +const struct fuse_ctx *fuse_req_ctx(fuse_req_t req); + +/** + * Get the current supplementary group IDs for the specified request + * + * Similar to the getgroups(2) system call, except the return value is + * always the total number of group IDs, even if it is larger than the + * specified size. + * + * The current fuse kernel module in linux (as of 2.6.30) doesn't pass + * the group list to userspace, hence this function needs to parse + * "/proc/$TID/task/$TID/status" to get the group IDs. + * + * This feature may not be supported on all operating systems. In + * such a case this function will return -ENOSYS. + * + * @param req request handle + * @param size size of given array + * @param list array of group IDs to be filled in + * @return the total number of supplementary group IDs or -errno on failure + */ +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]); + +/** + * Callback function for an interrupt + * + * @param req interrupted request + * @param data user data + */ +typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data); + +/** + * Register/unregister callback for an interrupt + * + * If an interrupt has already happened, then the callback function is + * called from within this function, hence it's not possible for + * interrupts to be lost. + * + * @param req request handle + * @param func the callback function or NULL for unregister + * @param data user data passed to the callback function + */ +void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, + void *data); + +/** + * Check if a request has already been interrupted + * + * @param req request handle + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_req_interrupted(fuse_req_t req); + + +/* ----------------------------------------------------------- * + * Inquiry functions * + * ----------------------------------------------------------- */ + +/** + * Print low-level version information to stdout. + */ +void fuse_lowlevel_version(void); + +/** + * Print available low-level options to stdout. This is not an + * exhaustive list, but includes only those options that may be of + * interest to an end-user of a file system. + */ +void fuse_lowlevel_help(void); + +/** + * Print available options for `fuse_parse_cmdline()`. + */ +void fuse_cmdline_help(void); + +/* ----------------------------------------------------------- * + * Filesystem setup & teardown * + * ----------------------------------------------------------- */ + +/** + * Note: Any addition to this struct needs to create a compatibility symbol + * for fuse_parse_cmdline(). For ABI compatibility reasons it is also + * not possible to remove struct members. + */ +struct fuse_cmdline_opts { + int singlethread; + int foreground; + int debug; + int nodefault_subtype; + char *mountpoint; + int show_version; + int show_help; + int clone_fd; + unsigned int max_idle_threads; /* discouraged, due to thread + * destruct overhead */ + + /* Added in libfuse-3.12 */ + unsigned int max_threads; +}; + +/** + * Utility function to parse common options for simple file systems + * using the low-level API. A help text that describes the available + * options can be printed with `fuse_cmdline_help`. A single + * non-option argument is treated as the mountpoint. Multiple + * non-option arguments will result in an error. + * + * If neither -o subtype= or -o fsname= options are given, a new + * subtype option will be added and set to the basename of the program + * (the fsname will remain unset, and then defaults to "fuse"). + * + * Known options will be removed from *args*, unknown options will + * remain. + * + * @param args argument vector (input+output) + * @param opts output argument for parsed options + * @return 0 on success, -1 on failure + */ +#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#else +#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts) +#else +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts) +#endif +#endif + +/* Do not call this directly, use fuse_session_new() instead */ +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata); + +/** + * Create a low level session. + * + * Returns a session structure suitable for passing to + * fuse_session_mount() and fuse_session_loop(). + * + * This function accepts most file-system independent mount options + * (like context, nodev, ro - see mount(8)), as well as the general + * fuse mount options listed in mount.fuse(8) (e.g. -o allow_root and + * -o default_permissions, but not ``-o use_ino``). Instead of `-o + * debug`, debugging may also enabled with `-d` or `--debug`. + * + * If not all options are known, an error message is written to stderr + * and the function returns NULL. + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. To prevent accidentally passing an option in + * argv[0], this element must always be present (even if no options + * are specified). It may be set to the empty string ('\0') if no + * reasonable value can be provided. + * + * @param args argument vector + * @param op the (low-level) filesystem operations + * @param op_size sizeof(struct fuse_lowlevel_ops) + * @param version the libfuse version a file system server was compiled against + * @param userdata user data + * @return the fuse session on success, NULL on failure + **/ +static inline struct fuse_session * +fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); +} +#define fuse_session_new(args, op, op_size, userdata) \ + fuse_session_new_fn(args, op, op_size, userdata) + +/* + * This should mostly not be called directly, but instead the + * fuse_session_custom_io() should be used. + */ +int fuse_session_custom_io_317(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd); + +/** + * Set a file descriptor for the session. + * + * This function can be used if you want to have a custom communication + * interface instead of using a mountpoint. In practice, this means that instead + * of calling fuse_session_mount() and fuse_session_unmount(), one could call + * fuse_session_custom_io() where fuse_session_mount() would have otherwise been + * called. + * + * In `io`, implementations for read and writev MUST be provided. Otherwise -1 + * will be returned and `fd` will not be used. Implementations for `splice_send` + * and `splice_receive` are optional. If they are not provided splice will not + * be used for send or receive respectively. + * + * The provided file descriptor `fd` will be closed when fuse_session_destroy() + * is called. + * + * @param se session object + * @param io Custom io to use when retrieving/sending requests/responses + * @param fd file descriptor for the session + * + * @return 0 on success + * @return -EINVAL if `io`, `io->read` or `ìo->writev` are NULL + * @return -EBADF if `fd` was smaller than 0 + * @return -errno if failed to allocate memory to store `io` + * + **/ +#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION +static inline int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd) +{ + return fuse_session_custom_io_317(se, io, op_size, fd); +} +#else +static inline int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) +{ + return fuse_session_custom_io_317(se, io, + offsetof(struct fuse_custom_io, clone_fd), fd); +} +#endif + +/** + * Mount a FUSE file system. + * + * @param mountpoint the mount point path + * @param se session object + * + * @return 0 on success, -1 on failure. + **/ +int fuse_session_mount(struct fuse_session *se, const char *mountpoint); + +/** + * Enter a single threaded, blocking event loop. + * + * When the event loop terminates because the connection to the FUSE + * kernel module has been closed, this function returns zero. This + * happens when the filesystem is unmounted regularly (by the + * filesystem owner or root running the umount(8) or fusermount(1) + * command), or if connection is explicitly severed by writing ``1`` + * to the``abort`` file in ``/sys/fs/fuse/connections/NNN``. The only + * way to distinguish between these two conditions is to check if the + * filesystem is still mounted after the session loop returns. + * + * When some error occurs during request processing, the function + * returns a negated errno(3) value. + * + * If the loop has been terminated because of a signal handler + * installed by fuse_set_signal_handlers(), this function returns the + * (positive) signal value that triggered the exit. + * + * @param se the session + * @return 0, -errno, or a signal value + */ +int fuse_session_loop(struct fuse_session *se); + +#if FUSE_USE_VERSION < 32 + int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd); + #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd) +#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) + int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config); + #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config) +#else + #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) + /** + * Enter a multi-threaded event loop. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * @param se the session + * @param config session loop configuration + * @return see fuse_session_loop() + */ + int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config); + #else + int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); + #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config) + #endif +#endif + +/** + * Flag a session as terminated. + * + * This will cause any running event loops to terminate on the next opportunity. If this function is + * called by a thread that is not a FUSE worker thread, the next + * opportunity will be when FUSE a request is received (which may be far in the future if the + * filesystem is not currently being used by any clients). One way to avoid this delay is to + * afterwards sent a signal to the main thread (if fuse_set_signal_handlers() is used, SIGPIPE + * will cause the main thread to wake-up but otherwise be ignored). + * + * @param se the session + */ +void fuse_session_exit(struct fuse_session *se); + +/** + * Reset the terminated flag of a session + * + * @param se the session + */ +void fuse_session_reset(struct fuse_session *se); + +/** + * Query the terminated flag of a session + * + * @param se the session + * @return 1 if exited, 0 if not exited + */ +int fuse_session_exited(struct fuse_session *se); + +/** + * Ensure that file system is unmounted. + * + * In regular operation, the file system is typically unmounted by the + * user calling umount(8) or fusermount(1), which then terminates the + * FUSE session loop. However, the session loop may also terminate as + * a result of an explicit call to fuse_session_exit() (e.g. by a + * signal handler installed by fuse_set_signal_handler()). In this + * case the filesystem remains mounted, but any attempt to access it + * will block (while the filesystem process is still running) or give + * an ESHUTDOWN error (after the filesystem process has terminated). + * + * If the communication channel with the FUSE kernel module is still + * open (i.e., if the session loop was terminated by an explicit call + * to fuse_session_exit()), this function will close it and unmount + * the filesystem. If the communication channel has been closed by the + * kernel, this method will do (almost) nothing. + * + * NOTE: The above semantics mean that if the connection to the kernel + * is terminated via the ``/sys/fs/fuse/connections/NNN/abort`` file, + * this method will *not* unmount the filesystem. + * + * @param se the session + */ +void fuse_session_unmount(struct fuse_session *se); + +/** + * Destroy a session + * + * @param se the session + */ +void fuse_session_destroy(struct fuse_session *se); + +/* ----------------------------------------------------------- * + * Custom event loop support * + * ----------------------------------------------------------- */ + +/** + * Return file descriptor for communication with kernel. + * + * The file selector can be used to integrate FUSE with a custom event + * loop. Whenever data is available for reading on the provided fd, + * the event loop should call `fuse_session_receive_buf` followed by + * `fuse_session_process_buf` to process the request. + * + * The returned file descriptor is valid until `fuse_session_unmount` + * is called. + * + * @param se the session + * @return a file descriptor + */ +int fuse_session_fd(struct fuse_session *se); + +/** + * Process a raw request supplied in a generic buffer + * + * The fuse_buf may contain a memory buffer or a pipe file descriptor. + * + * @param se the session + * @param buf the fuse_buf containing the request + */ +void fuse_session_process_buf(struct fuse_session *se, + const struct fuse_buf *buf); + +/** + * Read a raw request from the kernel into the supplied buffer. + * + * Depending on file system options, system capabilities, and request + * size the request is either read into a memory buffer or spliced + * into a temporary pipe. + * + * @param se the session + * @param buf the fuse_buf to store the request in + * @return the actual size of the raw request, or -errno on error + */ +int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_LOWLEVEL_H_ */ diff --git a/include/fuse_mount_compat.h b/include/fuse_mount_compat.h new file mode 100644 index 0000000..be8d576 --- /dev/null +++ b/include/fuse_mount_compat.h @@ -0,0 +1,49 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2023 Giulio Benetti + + Logging API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LICENSE +*/ + +#ifndef FUSE_MOUNT_COMPAT_H_ +#define FUSE_MOUNT_COMPAT_H_ + +#include + +/* Some libc don't define MS_*, so define them manually + * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on) + */ +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +#ifndef MS_NOSYMFOLLOW +#define MS_NOSYMFOLLOW 256 +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#ifndef MS_PRIVATE +#define MS_PRIVATE (1<<18) +#endif + +#ifndef MS_LAZYTIME +#define MS_LAZYTIME (1<<25) +#endif + +#ifndef UMOUNT_DETACH +#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */ +#endif +#ifndef UMOUNT_NOFOLLOW +#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */ +#endif +#ifndef UMOUNT_UNUSED +#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */ +#endif + +#endif /* FUSE_MOUNT_COMPAT_H_ */ diff --git a/include/fuse_opt.h b/include/fuse_opt.h new file mode 100644 index 0000000..d8573e7 --- /dev/null +++ b/include/fuse_opt.h @@ -0,0 +1,271 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#ifndef FUSE_OPT_H_ +#define FUSE_OPT_H_ + +/** @file + * + * This file defines the option parsing interface of FUSE + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Option description + * + * This structure describes a single option, and action associated + * with it, in case it matches. + * + * More than one such match may occur, in which case the action for + * each match is executed. + * + * There are three possible actions in case of a match: + * + * i) An integer (int or unsigned) variable determined by 'offset' is + * set to 'value' + * + * ii) The processing function is called, with 'value' as the key + * + * iii) An integer (any) or string (char *) variable determined by + * 'offset' is set to the value of an option parameter + * + * 'offset' should normally be either set to + * + * - 'offsetof(struct foo, member)' actions i) and iii) + * + * - -1 action ii) + * + * The 'offsetof()' macro is defined in the header. + * + * The template determines which options match, and also have an + * effect on the action. Normally the action is either i) or ii), but + * if a format is present in the template, then action iii) is + * performed. + * + * The types of templates are: + * + * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only + * themselves. Invalid values are "--" and anything beginning + * with "-o" + * + * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or + * the relevant option in a comma separated option list + * + * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) + * which have a parameter + * + * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform + * action iii). + * + * 5) "-x ", etc. Matches either "-xparam" or "-x param" as + * two separate arguments + * + * 6) "-x %s", etc. Combination of 4) and 5) + * + * If the format is "%s", memory is allocated for the string unlike with + * scanf(). The previous value (if non-NULL) stored at the this location is + * freed. + */ +struct fuse_opt { + /** Matching template and optional parameter formatting */ + const char *templ; + + /** + * Offset of variable within 'data' parameter of fuse_opt_parse() + * or -1 + */ + unsigned long offset; + + /** + * Value to set the variable to, or to be passed as 'key' to the + * processing function. Ignored if template has a format + */ + int value; +}; + +/** + * Key option. In case of a match, the processing function will be + * called with the specified key. + */ +#define FUSE_OPT_KEY(templ, key) { templ, -1U, key } + +/** + * Last option. An array of 'struct fuse_opt' must end with a NULL + * template value + */ +#define FUSE_OPT_END { NULL, 0, 0 } + +/** + * Argument list + */ +struct fuse_args { + /** Argument count */ + int argc; + + /** Argument vector. NULL terminated */ + char **argv; + + /** Is 'argv' allocated? */ + int allocated; +}; + +/** + * Initializer for 'struct fuse_args' + */ +#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } + +/** + * Key value passed to the processing function if an option did not + * match any template + */ +#define FUSE_OPT_KEY_OPT -1 + +/** + * Key value passed to the processing function for all non-options + * + * Non-options are the arguments beginning with a character other than + * '-' or all arguments after the special '--' option + */ +#define FUSE_OPT_KEY_NONOPT -2 + +/** + * Special key value for options to keep + * + * Argument is not passed to processing function, but behave as if the + * processing function returned 1 + */ +#define FUSE_OPT_KEY_KEEP -3 + +/** + * Special key value for options to discard + * + * Argument is not passed to processing function, but behave as if the + * processing function returned zero + */ +#define FUSE_OPT_KEY_DISCARD -4 + +/** + * Processing function + * + * This function is called if + * - option did not match any 'struct fuse_opt' + * - argument is a non-option + * - option did match and offset was set to -1 + * + * The 'arg' parameter will always contain the whole argument or + * option including the parameter if exists. A two-argument option + * ("-x foo") is always converted to single argument option of the + * form "-xfoo" before this function is called. + * + * Options of the form '-ofoo' are passed to this function without the + * '-o' prefix. + * + * The return value of this function determines whether this argument + * is to be inserted into the output argument vector, or discarded. + * + * @param data is the user data passed to the fuse_opt_parse() function + * @param arg is the whole argument or option + * @param key determines why the processing function was called + * @param outargs the current output argument list + * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept + */ +typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, + struct fuse_args *outargs); + +/** + * Option parsing function + * + * If 'args' was returned from a previous call to fuse_opt_parse() or + * it was constructed from + * + * A NULL 'args' is equivalent to an empty argument vector + * + * A NULL 'opts' is equivalent to an 'opts' array containing a single + * end marker + * + * A NULL 'proc' is equivalent to a processing function always + * returning '1' + * + * @param args is the input and output argument list + * @param data is the user data + * @param opts is the option description array + * @param proc is the processing function + * @return -1 on error, 0 on success + */ +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc); + +/** + * Add an option to a comma separated option list + * + * @param opts is a pointer to an option list, may point to a NULL value + * @param opt is the option to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_opt(char **opts, const char *opt); + +/** + * Add an option, escaping commas, to a comma separated option list + * + * @param opts is a pointer to an option list, may point to a NULL value + * @param opt is the option to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_opt_escaped(char **opts, const char *opt); + +/** + * Add an argument to a NULL terminated argument vector + * + * @param args is the structure containing the current argument list + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_arg(struct fuse_args *args, const char *arg); + +/** + * Add an argument at the specified position in a NULL terminated + * argument vector + * + * Adds the argument to the N-th position. This is useful for adding + * options at the beginning of the array which must not come after the + * special '--' option. + * + * @param args is the structure containing the current argument list + * @param pos is the position at which to add the argument + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); + +/** + * Free the contents of argument list + * + * The structure itself is not freed + * + * @param args is the structure containing the argument list + */ +void fuse_opt_free_args(struct fuse_args *args); + + +/** + * Check if an option matches + * + * @param opts is the option description array + * @param opt is the option to match + * @return 1 if a match is found, 0 if not + */ +int fuse_opt_match(const struct fuse_opt opts[], const char *opt); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_OPT_H_ */ diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..bf67197 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,4 @@ +libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h', + 'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ] + +install_headers(libfuse_headers, subdir: 'fuse3') diff --git a/lib/buffer.c b/lib/buffer.c new file mode 100644 index 0000000..6375433 --- /dev/null +++ b/lib/buffer.c @@ -0,0 +1,324 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2010 Miklos Szeredi + + Functions for dealing with `struct fuse_buf` and `struct + fuse_bufvec`. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include +#include +#include +#include + +size_t fuse_buf_size(const struct fuse_bufvec *bufv) +{ + size_t i; + size_t size = 0; + + for (i = 0; i < bufv->count; i++) { + if (bufv->buf[i].size >= SIZE_MAX - size) + return SIZE_MAX; + + size += bufv->buf[i].size; + } + + return size; +} + +static size_t min_size(size_t s1, size_t s2) +{ + return s1 < s2 ? s1 : s2; +} + +static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (dst->flags & FUSE_BUF_FD_SEEK) { + res = pwrite(dst->fd, (char *)src->mem + src_off, len, + dst->pos + dst_off); + } else { + res = write(dst->fd, (char *)src->mem + src_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(dst->flags & FUSE_BUF_FD_RETRY)) + break; + + src_off += res; + dst_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (src->flags & FUSE_BUF_FD_SEEK) { + res = pread(src->fd, (char *)dst->mem + dst_off, len, + src->pos + src_off); + } else { + res = read(src->fd, (char *)dst->mem + dst_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY)) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + char buf[4096]; + struct fuse_buf tmp = { + .size = sizeof(buf), + .flags = 0, + }; + ssize_t res; + size_t copied = 0; + + tmp.mem = buf; + + while (len) { + size_t this_len = min_size(tmp.size, len); + size_t read_len; + + res = fuse_buf_read(&tmp, 0, src, src_off, this_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + read_len = res; + res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + copied += res; + + if (res < this_len) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +#ifdef HAVE_SPLICE +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int splice_flags = 0; + off_t *srcpos = NULL; + off_t *dstpos = NULL; + off_t srcpos_val; + off_t dstpos_val; + ssize_t res; + size_t copied = 0; + + if (flags & FUSE_BUF_SPLICE_MOVE) + splice_flags |= SPLICE_F_MOVE; + if (flags & FUSE_BUF_SPLICE_NONBLOCK) + splice_flags |= SPLICE_F_NONBLOCK; + + if (src->flags & FUSE_BUF_FD_SEEK) { + srcpos_val = src->pos + src_off; + srcpos = &srcpos_val; + } + if (dst->flags & FUSE_BUF_FD_SEEK) { + dstpos_val = dst->pos + dst_off; + dstpos = &dstpos_val; + } + + while (len) { + res = splice(src->fd, srcpos, dst->fd, dstpos, len, + splice_flags); + if (res == -1) { + if (copied) + break; + + if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE)) + return -errno; + + /* Maybe splice is not supported for this combination */ + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, + len); + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY) && + !(dst->flags & FUSE_BUF_FD_RETRY)) { + break; + } + + len -= res; + } + + return copied; +} +#else +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + (void) flags; + + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); +} +#endif + + +static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int src_is_fd = src->flags & FUSE_BUF_IS_FD; + int dst_is_fd = dst->flags & FUSE_BUF_IS_FD; + + if (!src_is_fd && !dst_is_fd) { + char *dstmem = (char *)dst->mem + dst_off; + char *srcmem = (char *)src->mem + src_off; + + if (dstmem != srcmem) { + if (dstmem + len <= srcmem || srcmem + len <= dstmem) + memcpy(dstmem, srcmem, len); + else + memmove(dstmem, srcmem, len); + } + + return len; + } else if (!src_is_fd) { + return fuse_buf_write(dst, dst_off, src, src_off, len); + } else if (!dst_is_fd) { + return fuse_buf_read(dst, dst_off, src, src_off, len); + } else if (flags & FUSE_BUF_NO_SPLICE) { + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); + } else { + return fuse_buf_splice(dst, dst_off, src, src_off, len, flags); + } +} + +static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv) +{ + if (bufv->idx < bufv->count) + return &bufv->buf[bufv->idx]; + else + return NULL; +} + +static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len) +{ + const struct fuse_buf *buf = fuse_bufvec_current(bufv); + + if (!buf) + return 0; + + bufv->off += len; + assert(bufv->off <= buf->size); + if (bufv->off == buf->size) { + assert(bufv->idx < bufv->count); + bufv->idx++; + if (bufv->idx == bufv->count) + return 0; + bufv->off = 0; + } + return 1; +} + +ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv, + enum fuse_buf_copy_flags flags) +{ + size_t copied = 0; + + if (dstv == srcv) + return fuse_buf_size(dstv); + + for (;;) { + const struct fuse_buf *src = fuse_bufvec_current(srcv); + const struct fuse_buf *dst = fuse_bufvec_current(dstv); + size_t src_len; + size_t dst_len; + size_t len; + ssize_t res; + + if (src == NULL || dst == NULL) + break; + + src_len = src->size - srcv->off; + dst_len = dst->size - dstv->off; + len = min_size(src_len, dst_len); + + res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags); + if (res < 0) { + if (!copied) + return res; + break; + } + copied += res; + + if (!fuse_bufvec_advance(srcv, res) || + !fuse_bufvec_advance(dstv, res)) + break; + + if (res < len) + break; + } + + return copied; +} diff --git a/lib/compat.c b/lib/compat.c new file mode 100644 index 0000000..b98ca4b --- /dev/null +++ b/lib/compat.c @@ -0,0 +1,86 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Helper functions to create (simple) standalone programs. With the + aid of these functions it should be possible to create full FUSE + file system by implementing nothing but the request handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +/* Description: + This file has compatibility symbols for platforms that do not + support version symboling +*/ + +#include "libfuse_config.h" + +#include +#include + +struct fuse_args; +struct fuse_cmdline_opts; +struct fuse_cmdline_opts; +struct fuse_session; +struct fuse_custom_io; +struct fuse_operations; +struct fuse_lowlevel_ops; + +/** + * Compatibility ABI symbol for systems that do not support version symboling + */ +#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +/* With current libfuse fuse_parse_cmdline is a macro pointing to the + * versioned function. Here in this file we need to provide the ABI symbol + * and the redirecting macro is conflicting. + */ +#ifdef fuse_parse_cmdline +#undef fuse_parse_cmdline +#endif +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts) +{ + return fuse_parse_cmdline_30(args, opts); +} + +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) + +{ + return fuse_session_custom_io_30(se, io, fd); +} + +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ + +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + return fuse_main_real_30(argc, argv, op, op_size, user_data); +} + +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + return fuse_session_new_30(args, op, op_size, userdata); +} diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c new file mode 100644 index 0000000..5387f84 --- /dev/null +++ b/lib/cuse_lowlevel.c @@ -0,0 +1,368 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_config.h" +#include "cuse_lowlevel.h" +#include "fuse_kernel.h" +#include "fuse_i.h" +#include "fuse_opt.h" + +#include +#include +#include +#include +#include +#include + +struct cuse_data { + struct cuse_lowlevel_ops clop; + unsigned max_read; + unsigned dev_major; + unsigned dev_minor; + unsigned flags; + unsigned dev_info_len; + char dev_info[]; +}; + +static struct cuse_lowlevel_ops *req_clop(fuse_req_t req) +{ + return &req->se->cuse_data->clop; +} + +static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->open(req, fi); +} + +static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->read(req, size, off, fi); +} + +static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->write(req, buf, size, off, fi); +} + +static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->flush(req, fi); +} + +static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->release(req, fi); +} + +static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->fsync(req, datasync, fi); +} + +static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + (void)ino; + req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz, + out_bufsz); +} + +static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + (void)ino; + req_clop(req)->poll(req, fi, ph); +} + +static size_t cuse_pack_info(int argc, const char **argv, char *buf) +{ + size_t size = 0; + int i; + + for (i = 0; i < argc; i++) { + size_t len; + + len = strlen(argv[i]) + 1; + size += len; + if (buf) { + memcpy(buf, argv[i], len); + buf += len; + } + } + + return size; +} + +static struct cuse_data *cuse_prep_data(const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop) +{ + struct cuse_data *cd; + size_t dev_info_len; + + dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, + NULL); + + if (dev_info_len > CUSE_INIT_INFO_MAX) { + fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n", + dev_info_len, CUSE_INIT_INFO_MAX); + return NULL; + } + + cd = calloc(1, sizeof(*cd) + dev_info_len); + if (!cd) { + fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n"); + return NULL; + } + + memcpy(&cd->clop, clop, sizeof(cd->clop)); + cd->max_read = 131072; + cd->dev_major = ci->dev_major; + cd->dev_minor = ci->dev_minor; + cd->dev_info_len = dev_info_len; + cd->flags = ci->flags; + cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info); + + return cd; +} + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata) +{ + struct fuse_lowlevel_ops lop; + struct cuse_data *cd; + struct fuse_session *se; + + cd = cuse_prep_data(ci, clop); + if (!cd) + return NULL; + + memset(&lop, 0, sizeof(lop)); + lop.init = clop->init; + lop.destroy = clop->destroy; + lop.open = clop->open ? cuse_fll_open : NULL; + lop.read = clop->read ? cuse_fll_read : NULL; + lop.write = clop->write ? cuse_fll_write : NULL; + lop.flush = clop->flush ? cuse_fll_flush : NULL; + lop.release = clop->release ? cuse_fll_release : NULL; + lop.fsync = clop->fsync ? cuse_fll_fsync : NULL; + lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL; + lop.poll = clop->poll ? cuse_fll_poll : NULL; + + se = fuse_session_new(args, &lop, sizeof(lop), userdata); + if (!se) { + free(cd); + return NULL; + } + se->cuse_data = cd; + + return se; +} + +static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg, + char *dev_info, unsigned dev_info_len) +{ + struct iovec iov[3]; + + iov[1].iov_base = arg; + iov[1].iov_len = sizeof(struct cuse_init_out); + iov[2].iov_base = dev_info; + iov[2].iov_len = dev_info_len; + + return fuse_send_reply_iov_nofree(req, 0, iov, 3); +} + +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_init_in *arg = (struct fuse_init_in *) inarg; + struct cuse_init_out outarg; + struct fuse_session *se = req->se; + struct cuse_data *cd = se->cuse_data; + size_t bufsize = se->bufsize; + struct cuse_lowlevel_ops *clop = req_clop(req); + + (void) nodeid; + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor); + fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags); + } + se->conn.proto_major = arg->major; + se->conn.proto_minor = arg->minor; + + /* XXX This is not right.*/ + se->conn.capable_ext = 0; + se->conn.want_ext = 0; + + if (arg->major < 7) { + fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + bufsize -= 4096; + if (bufsize < se->conn.max_write) + se->conn.max_write = bufsize; + + se->got_init = 1; + if (se->op.init) + se->op.init(se->userdata, &se->conn); + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + outarg.flags = cd->flags; + outarg.max_read = cd->max_read; + outarg.max_write = se->conn.max_write; + outarg.dev_major = cd->dev_major; + outarg.dev_minor = cd->dev_minor; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n", + outarg.major, outarg.minor); + fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags); + fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read); + fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write); + fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major); + fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor); + fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len, + cd->dev_info); + } + + cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len); + + if (clop->init_done) + clop->init_done(se->userdata); + + fuse_free_req(req); +} + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata) +{ + const char *devname = "/dev/cuse"; + static const struct fuse_opt kill_subtype_opts[] = { + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_DISCARD), + FUSE_OPT_END + }; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + int fd; + int res; + + if (fuse_parse_cmdline(&args, &opts) == -1) + return NULL; + *multithreaded = !opts.singlethread; + + /* Remove subtype= option */ + res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL); + if (res == -1) + goto out1; + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + se = cuse_lowlevel_new(&args, ci, clop, userdata); + if (se == NULL) + goto out1; + + fd = open(devname, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n"); + else + fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n", + devname, strerror(errno)); + goto err_se; + } + se->fd = fd; + + res = fuse_set_signal_handlers(se); + if (res == -1) + goto err_se; + + res = fuse_daemonize(opts.foreground); + if (res == -1) + goto err_sig; + + fuse_opt_free_args(&args); + return se; + +err_sig: + fuse_remove_signal_handlers(se); +err_se: + fuse_session_destroy(se); +out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + return NULL; +} + +void cuse_lowlevel_teardown(struct fuse_session *se) +{ + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); +} + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata) +{ + struct fuse_session *se; + int multithreaded; + int res; + + se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded, + userdata); + if (se == NULL) + return 1; + + if (multithreaded) { + struct fuse_loop_config *config = fuse_loop_cfg_create(); + res = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + } + else + res = fuse_session_loop(se); + + cuse_lowlevel_teardown(se); + if (res == -1) + return 1; + + return 0; +} diff --git a/lib/fuse.c b/lib/fuse.c new file mode 100644 index 0000000..49f5711 --- /dev/null +++ b/lib/fuse.c @@ -0,0 +1,5239 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the high-level FUSE API on top of the low-level + API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#define _GNU_SOURCE +#include "fuse.h" +#include + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "fuse_kernel.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_NODE_SLAB 1 + +#ifndef MAP_ANONYMOUS +#undef FUSE_NODE_SLAB +#endif + +#ifndef RENAME_EXCHANGE +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ +#endif + +#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1 + +#define FUSE_UNKNOWN_INO 0xffffffff +#define OFFSET_MAX 0x7fffffffffffffffLL + +#define NODE_TABLE_MIN_SIZE 8192 + +struct fuse_fs { + struct fuse_operations op; + void *user_data; + int debug; +}; + +struct fusemod_so { + void *handle; + int ctr; +}; + +struct lock_queue_element { + struct lock_queue_element *next; + pthread_cond_t cond; + fuse_ino_t nodeid1; + const char *name1; + char **path1; + struct node **wnode1; + fuse_ino_t nodeid2; + const char *name2; + char **path2; + struct node **wnode2; + int err; + bool done : 1; +}; + +struct node_table { + struct node **array; + size_t use; + size_t size; + size_t split; +}; + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +struct node_slab { + struct list_head list; /* must be the first member */ + struct list_head freelist; + int used; +}; + +struct fuse { + struct fuse_session *se; + struct node_table name_table; + struct node_table id_table; + struct list_head lru_table; + fuse_ino_t ctr; + unsigned int generation; + unsigned int hidectr; + pthread_mutex_t lock; + struct fuse_config conf; + int intr_installed; + struct fuse_fs *fs; + struct lock_queue_element *lockq; + int pagesize; + struct list_head partial_slabs; + struct list_head full_slabs; + pthread_t prune_thread; +}; + +struct lock { + int type; + off_t start; + off_t end; + pid_t pid; + uint64_t owner; + struct lock *next; +}; + +struct node { + struct node *name_next; + struct node *id_next; + fuse_ino_t nodeid; + unsigned int generation; + int refctr; + struct node *parent; + char *name; + uint64_t nlookup; + int open_count; + struct timespec stat_updated; + struct timespec mtime; + off_t size; + struct lock *locks; + unsigned int is_hidden : 1; + unsigned int cache_valid : 1; + int treelock; + char inline_name[32]; +}; + +#define TREELOCK_WRITE -1 +#define TREELOCK_WAIT_OFFSET INT_MIN + +struct node_lru { + struct node node; + struct list_head lru; + struct timespec forget_time; +}; + +struct fuse_direntry { + struct stat stat; + enum fuse_fill_dir_flags flags; + char *name; + struct fuse_direntry *next; +}; + +struct fuse_dh { + pthread_mutex_t lock; + struct fuse *fuse; + fuse_req_t req; + char *contents; + struct fuse_direntry *first; + struct fuse_direntry **last; + unsigned len; + unsigned size; + unsigned needlen; + int filled; + uint64_t fh; + int error; + fuse_ino_t nodeid; +}; + +struct fuse_context_i { + struct fuse_context ctx; + fuse_req_t req; +}; + +/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */ +extern fuse_module_factory_t fuse_module_subdir_factory; +#ifdef HAVE_ICONV +extern fuse_module_factory_t fuse_module_iconv_factory; +#endif + +static pthread_key_t fuse_context_key; +static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER; +static int fuse_context_ref; +static struct fuse_module *fuse_modules = NULL; + +static int fuse_register_module(const char *name, + fuse_module_factory_t factory, + struct fusemod_so *so) +{ + struct fuse_module *mod; + + mod = calloc(1, sizeof(struct fuse_module)); + if (!mod) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n"); + return -1; + } + mod->name = strdup(name); + if (!mod->name) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n"); + free(mod); + return -1; + } + mod->factory = factory; + mod->ctr = 0; + mod->so = so; + if (mod->so) + mod->so->ctr++; + mod->next = fuse_modules; + fuse_modules = mod; + + return 0; +} + +static void fuse_unregister_module(struct fuse_module *m) +{ + struct fuse_module **mp; + for (mp = &fuse_modules; *mp; mp = &(*mp)->next) { + if (*mp == m) { + *mp = (*mp)->next; + break; + } + } + free(m->name); + free(m); +} + +static int fuse_load_so_module(const char *module) +{ + int ret = -1; + char *tmp; + struct fusemod_so *so; + fuse_module_factory_t *factory; + + tmp = malloc(strlen(module) + 64); + if (!tmp) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + sprintf(tmp, "libfusemod_%s.so", module); + so = calloc(1, sizeof(struct fusemod_so)); + if (!so) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n"); + goto out; + } + + so->handle = dlopen(tmp, RTLD_NOW); + if (so->handle == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n", + tmp, dlerror()); + goto out_free_so; + } + + sprintf(tmp, "fuse_module_%s_factory", module); + factory = (fuse_module_factory_t*)dlsym(so->handle, tmp); + if (factory == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n", + tmp, dlerror()); + goto out_dlclose; + } + ret = fuse_register_module(module, *factory, so); + if (ret) + goto out_dlclose; + +out: + free(tmp); + return ret; + +out_dlclose: + dlclose(so->handle); +out_free_so: + free(so); + goto out; +} + +static struct fuse_module *fuse_find_module(const char *module) +{ + struct fuse_module *m; + for (m = fuse_modules; m; m = m->next) { + if (strcmp(module, m->name) == 0) { + m->ctr++; + break; + } + } + return m; +} + +static struct fuse_module *fuse_get_module(const char *module) +{ + struct fuse_module *m; + + pthread_mutex_lock(&fuse_context_lock); + m = fuse_find_module(module); + if (!m) { + int err = fuse_load_so_module(module); + if (!err) + m = fuse_find_module(module); + } + pthread_mutex_unlock(&fuse_context_lock); + return m; +} + +static void fuse_put_module(struct fuse_module *m) +{ + pthread_mutex_lock(&fuse_context_lock); + if (m->so) + assert(m->ctr > 0); + /* Builtin modules may already have m->ctr == 0 */ + if (m->ctr > 0) + m->ctr--; + if (!m->ctr && m->so) { + struct fusemod_so *so = m->so; + assert(so->ctr > 0); + so->ctr--; + if (!so->ctr) { + struct fuse_module **mp; + for (mp = &fuse_modules; *mp;) { + if ((*mp)->so == so) + fuse_unregister_module(*mp); + else + mp = &(*mp)->next; + } + dlclose(so->handle); + free(so); + } + } else if (!m->ctr) { + fuse_unregister_module(m); + } + pthread_mutex_unlock(&fuse_context_lock); +} + +static void init_list_head(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static void list_add(struct list_head *new, struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add_head(struct list_head *new, struct list_head *head) +{ + list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + list_add(new, head->prev, head); +} + +static inline void list_del(struct list_head *entry) +{ + struct list_head *prev = entry->prev; + struct list_head *next = entry->next; + + next->prev = prev; + prev->next = next; +} + +static inline int lru_enabled(struct fuse *f) +{ + return f->conf.remember > 0; +} + +static struct node_lru *node_lru(struct node *node) +{ + return (struct node_lru *) node; +} + +static size_t get_node_size(struct fuse *f) +{ + if (lru_enabled(f)) + return sizeof(struct node_lru); + else + return sizeof(struct node); +} + +#ifdef FUSE_NODE_SLAB +static struct node_slab *list_to_slab(struct list_head *head) +{ + return (struct node_slab *) head; +} + +static struct node_slab *node_to_slab(struct fuse *f, struct node *node) +{ + return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1)); +} + +static int alloc_slab(struct fuse *f) +{ + void *mem; + struct node_slab *slab; + char *start; + size_t num; + size_t i; + size_t node_size = get_node_size(f); + + mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (mem == MAP_FAILED) + return -1; + + slab = mem; + init_list_head(&slab->freelist); + slab->used = 0; + num = (f->pagesize - sizeof(struct node_slab)) / node_size; + + start = (char *) mem + f->pagesize - num * node_size; + for (i = 0; i < num; i++) { + struct list_head *n; + + n = (struct list_head *) (start + i * node_size); + list_add_tail(n, &slab->freelist); + } + list_add_tail(&slab->list, &f->partial_slabs); + + return 0; +} + +static struct node *alloc_node(struct fuse *f) +{ + struct node_slab *slab; + struct list_head *node; + + if (list_empty(&f->partial_slabs)) { + int res = alloc_slab(f); + if (res != 0) + return NULL; + } + slab = list_to_slab(f->partial_slabs.next); + slab->used++; + node = slab->freelist.next; + list_del(node); + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->full_slabs); + } + memset(node, 0, sizeof(struct node)); + + return (struct node *) node; +} + +static void free_slab(struct fuse *f, struct node_slab *slab) +{ + int res; + + list_del(&slab->list); + res = munmap(slab, f->pagesize); + if (res == -1) + fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n", + slab); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + struct node_slab *slab = node_to_slab(f, node); + struct list_head *n = (struct list_head *) node; + + slab->used--; + if (slab->used) { + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->partial_slabs); + } + list_add_head(n, &slab->freelist); + } else { + free_slab(f, slab); + } +} +#else +static struct node *alloc_node(struct fuse *f) +{ + return (struct node *) calloc(1, get_node_size(f)); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + (void) f; + free(node); +} +#endif + +static size_t id_hash(struct fuse *f, fuse_ino_t ino) +{ + uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size; + uint64_t oldhash = hash % (f->id_table.size / 2); + + if (oldhash >= f->id_table.split) + return oldhash; + else + return hash; +} + +static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid) +{ + size_t hash = id_hash(f, nodeid); + struct node *node; + + for (node = f->id_table.array[hash]; node != NULL; node = node->id_next) + if (node->nodeid == nodeid) + return node; + + return NULL; +} + +static struct node *get_node(struct fuse *f, fuse_ino_t nodeid) +{ + struct node *node = get_node_nocheck(f, nodeid); + if (!node) { + fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n", + (unsigned long long) nodeid); + abort(); + } + return node; +} + +static void curr_time(struct timespec *now); +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2); + +static void remove_node_lru(struct node *node) +{ + struct node_lru *lnode = node_lru(node); + list_del(&lnode->lru); + init_list_head(&lnode->lru); +} + +static void set_forget_time(struct fuse *f, struct node *node) +{ + struct node_lru *lnode = node_lru(node); + + list_del(&lnode->lru); + list_add_tail(&lnode->lru, &f->lru_table); + curr_time(&lnode->forget_time); +} + +static void free_node(struct fuse *f, struct node *node) +{ + if (node->name != node->inline_name) + free(node->name); + free_node_mem(f, node); +} + +static void node_table_reduce(struct node_table *t) +{ + size_t newsize = t->size / 2; + void *newarray; + + if (newsize < NODE_TABLE_MIN_SIZE) + return; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray != NULL) + t->array = newarray; + + t->size = newsize; + t->split = t->size / 2; +} + +static void remerge_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->id_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_id(struct fuse *f, struct node *node) +{ + struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)]; + + for (; *nodep != NULL; nodep = &(*nodep)->id_next) + if (*nodep == node) { + *nodep = node->id_next; + f->id_table.use--; + + if(f->id_table.use < f->id_table.size / 4) + remerge_id(f); + return; + } +} + +static int node_table_resize(struct node_table *t) +{ + size_t newsize = t->size * 2; + void *newarray; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray == NULL) + return -1; + + t->array = newarray; + memset(t->array + t->size, 0, t->size * sizeof(struct node *)); + t->size = newsize; + t->split = 0; + + return 0; +} + +static void rehash_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = id_hash(f, node->nodeid); + + if (newhash != hash) { + next = nodep; + *nodep = node->id_next; + node->id_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->id_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static void hash_id(struct fuse *f, struct node *node) +{ + size_t hash = id_hash(f, node->nodeid); + node->id_next = f->id_table.array[hash]; + f->id_table.array[hash] = node; + f->id_table.use++; + + if (f->id_table.use >= f->id_table.size / 2) + rehash_id(f); +} + +static size_t name_hash(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + uint64_t hash = parent; + uint64_t oldhash; + + for (; *name; name++) + hash = hash * 31 + (unsigned char) *name; + + hash %= f->name_table.size; + oldhash = hash % (f->name_table.size / 2); + if (oldhash >= f->name_table.split) + return oldhash; + else + return hash; +} + +static void unref_node(struct fuse *f, struct node *node); + +static void remerge_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->name_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_name(struct fuse *f, struct node *node) +{ + if (node->name) { + size_t hash = name_hash(f, node->parent->nodeid, node->name); + struct node **nodep = &f->name_table.array[hash]; + + for (; *nodep != NULL; nodep = &(*nodep)->name_next) + if (*nodep == node) { + *nodep = node->name_next; + node->name_next = NULL; + unref_node(f, node->parent); + if (node->name != node->inline_name) + free(node->name); + node->name = NULL; + node->parent = NULL; + f->name_table.use--; + + if (f->name_table.use < f->name_table.size / 4) + remerge_name(f); + return; + } + fuse_log(FUSE_LOG_ERR, + "fuse internal error: unable to unhash node: %llu\n", + (unsigned long long) node->nodeid); + abort(); + } +} + +static void rehash_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = name_hash(f, node->parent->nodeid, node->name); + + if (newhash != hash) { + next = nodep; + *nodep = node->name_next; + node->name_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->name_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid, + const char *name) +{ + size_t hash = name_hash(f, parentid, name); + struct node *parent = get_node(f, parentid); + if (strlen(name) < sizeof(node->inline_name)) { + strcpy(node->inline_name, name); + node->name = node->inline_name; + } else { + node->name = strdup(name); + if (node->name == NULL) + return -1; + } + + parent->refctr ++; + node->parent = parent; + node->name_next = f->name_table.array[hash]; + f->name_table.array[hash] = node; + f->name_table.use++; + + if (f->name_table.use >= f->name_table.size / 2) + rehash_name(f); + + return 0; +} + +static void delete_node(struct fuse *f, struct node *node) +{ + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n", + (unsigned long long) node->nodeid); + + assert(node->treelock == 0); + unhash_name(f, node); + if (lru_enabled(f)) + remove_node_lru(node); + unhash_id(f, node); + free_node(f, node); +} + +static void unref_node(struct fuse *f, struct node *node) +{ + assert(node->refctr > 0); + node->refctr --; + if (!node->refctr) + delete_node(f, node); +} + +static fuse_ino_t next_id(struct fuse *f) +{ + do { + f->ctr = (f->ctr + 1) & 0xffffffff; + if (!f->ctr) + f->generation ++; + } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO || + get_node_nocheck(f, f->ctr) != NULL); + return f->ctr; +} + +static struct node *lookup_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + size_t hash = name_hash(f, parent, name); + struct node *node; + + for (node = f->name_table.array[hash]; node != NULL; node = node->name_next) + if (node->parent->nodeid == parent && + strcmp(node->name, name) == 0) + return node; + + return NULL; +} + +static void inc_nlookup(struct node *node) +{ + if (!node->nlookup) + node->refctr++; + node->nlookup++; +} + +static struct node *find_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + if (!name) + node = get_node(f, parent); + else + node = lookup_node(f, parent, name); + if (node == NULL) { + node = alloc_node(f); + if (node == NULL) + goto out_err; + + node->nodeid = next_id(f); + node->generation = f->generation; + if (f->conf.remember) + inc_nlookup(node); + + if (hash_name(f, node, parent, name) == -1) { + free_node(f, node); + node = NULL; + goto out_err; + } + hash_id(f, node); + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(node); + init_list_head(&lnode->lru); + } + } else if (lru_enabled(f) && node->nlookup == 1) { + remove_node_lru(node); + } + inc_nlookup(node); +out_err: + pthread_mutex_unlock(&f->lock); + return node; +} + +static int lookup_path_in_cache(struct fuse *f, + const char *path, fuse_ino_t *inop) +{ + char *tmp = strdup(path); + if (!tmp) + return -ENOMEM; + + pthread_mutex_lock(&f->lock); + fuse_ino_t ino = FUSE_ROOT_ID; + + int err = 0; + char *save_ptr; + char *path_element = strtok_r(tmp, "/", &save_ptr); + while (path_element != NULL) { + struct node *node = lookup_node(f, ino, path_element); + if (node == NULL) { + err = -ENOENT; + break; + } + ino = node->nodeid; + path_element = strtok_r(NULL, "/", &save_ptr); + } + pthread_mutex_unlock(&f->lock); + free(tmp); + + if (!err) + *inop = ino; + return err; +} + +static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name) +{ + size_t len = strlen(name); + + if (s - len <= *buf) { + unsigned pathlen = *bufsize - (s - *buf); + unsigned newbufsize = *bufsize; + char *newbuf; + + while (newbufsize < pathlen + len + 1) { + if (newbufsize >= 0x80000000) + newbufsize = 0xffffffff; + else + newbufsize *= 2; + } + + newbuf = realloc(*buf, newbufsize); + if (newbuf == NULL) + return NULL; + + *buf = newbuf; + s = newbuf + newbufsize - pathlen; + memmove(s, newbuf + *bufsize - pathlen, pathlen); + *bufsize = newbufsize; + } + s -= len; + memcpy(s, name, len); + s--; + *s = '/'; + + return s; +} + +static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode, + struct node *end) +{ + struct node *node; + + if (wnode) { + assert(wnode->treelock == TREELOCK_WRITE); + wnode->treelock = 0; + } + + for (node = get_node(f, nodeid); + node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) { + assert(node->treelock != 0); + assert(node->treelock != TREELOCK_WAIT_OFFSET); + assert(node->treelock != TREELOCK_WRITE); + node->treelock--; + if (node->treelock == TREELOCK_WAIT_OFFSET) + node->treelock = 0; + } +} + +static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnodep, bool need_lock) +{ + unsigned bufsize = 256; + char *buf; + char *s; + struct node *node; + struct node *wnode = NULL; + int err; + + *path = NULL; + + err = -ENOMEM; + buf = malloc(bufsize); + if (buf == NULL) + goto out_err; + + s = buf + bufsize - 1; + *s = '\0'; + + if (name != NULL) { + s = add_name(&buf, &bufsize, s, name); + err = -ENOMEM; + if (s == NULL) + goto out_free; + } + + if (wnodep) { + assert(need_lock); + wnode = lookup_node(f, nodeid, name); + if (wnode) { + if (wnode->treelock != 0) { + if (wnode->treelock > 0) + wnode->treelock += TREELOCK_WAIT_OFFSET; + err = -EAGAIN; + goto out_free; + } + wnode->treelock = TREELOCK_WRITE; + } + } + + for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + err = -ESTALE; + if (node->name == NULL || node->parent == NULL) + goto out_unlock; + + err = -ENOMEM; + s = add_name(&buf, &bufsize, s, node->name); + if (s == NULL) + goto out_unlock; + + if (need_lock) { + err = -EAGAIN; + if (node->treelock < 0) + goto out_unlock; + + node->treelock++; + } + } + + if (s[0]) + memmove(buf, s, bufsize - (s - buf)); + else + strcpy(buf, "/"); + + *path = buf; + if (wnodep) + *wnodep = wnode; + + return 0; + + out_unlock: + if (need_lock) + unlock_path(f, nodeid, wnode, node); + out_free: + free(buf); + + out_err: + return err; +} + +static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + /* FIXME: locking two paths needs deadlock checking */ + err = try_get_path(f, nodeid1, name1, path1, wnode1, true); + if (!err) { + err = try_get_path(f, nodeid2, name2, path2, wnode2, true); + if (err) { + struct node *wn1 = wnode1 ? *wnode1 : NULL; + + unlock_path(f, nodeid1, wn1, NULL); + free(*path1); + } + } + return err; +} + +static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe) +{ + int err; + + if (!qe->path1) { + /* Just waiting for it to be unlocked */ + if (get_node(f, qe->nodeid1)->treelock == 0) + pthread_cond_signal(&qe->cond); + + return; + } + + if (qe->done) + return; // Don't try to double-lock the element + + if (!qe->path2) { + err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1, + qe->wnode1, true); + } else { + err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2, + qe->name2, qe->path1, qe->path2, qe->wnode1, + qe->wnode2); + } + + if (err == -EAGAIN) + return; /* keep trying */ + + qe->err = err; + qe->done = true; + pthread_cond_signal(&qe->cond); +} + +static void wake_up_queued(struct fuse *f) +{ + struct lock_queue_element *qe; + + for (qe = f->lockq; qe != NULL; qe = qe->next) + queue_element_wakeup(f, qe); +} + +static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid, + const char *name, bool wr) +{ + if (f->conf.debug) { + struct node *wnode = NULL; + + if (wr) + wnode = lookup_node(f, nodeid, name); + + if (wnode) { + fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n", + msg, (unsigned long long) wnode->nodeid); + } else { + fuse_log(FUSE_LOG_DEBUG, "%s %llu\n", + msg, (unsigned long long) nodeid); + } + } +} + +static void queue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + qe->done = false; + pthread_cond_init(&qe->cond, NULL); + qe->next = NULL; + for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next); + *qp = qe; +} + +static void dequeue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + pthread_cond_destroy(&qe->cond); + for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next); + *qp = qe->next; +} + +static int wait_path(struct fuse *f, struct lock_queue_element *qe) +{ + queue_path(f, qe); + + do { + pthread_cond_wait(&qe->cond, &f->lock); + } while (!qe->done); + + dequeue_path(f, qe); + + return qe->err; +} + +static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + int err; + + pthread_mutex_lock(&f->lock); + err = try_get_path(f, nodeid, name, path, wnode, true); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + .name1 = name, + .path1 = path, + .wnode1 = wnode, + }; + debug_path(f, "QUEUE PATH", nodeid, name, !!wnode); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode); + } + pthread_mutex_unlock(&f->lock); + + return err; +} + +static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + return get_path_common(f, nodeid, NULL, path, NULL); +} + +static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + int err = 0; + + if (f->conf.nullpath_ok) { + *path = NULL; + } else { + err = get_path_common(f, nodeid, NULL, path, NULL); + if (err == -ESTALE) + err = 0; + } + + return err; +} + +static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path) +{ + return get_path_common(f, nodeid, name, path, NULL); +} + +static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + return get_path_common(f, nodeid, name, path, wnode); +} + +#if defined(__FreeBSD__) +#define CHECK_DIR_LOOP +#endif + +#if defined(CHECK_DIR_LOOP) +static int check_dir_loop(struct fuse *f, + fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2) +{ + struct node *node, *node1, *node2; + fuse_ino_t id1, id2; + + node1 = lookup_node(f, nodeid1, name1); + id1 = node1 ? node1->nodeid : nodeid1; + + node2 = lookup_node(f, nodeid2, name2); + id2 = node2 ? node2->nodeid : nodeid2; + + for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id2 && node->nodeid == id1) + return -EINVAL; + } + + if (node2) + { + for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id1 && node->nodeid == id2) + return -ENOTEMPTY; + } + } + + return 0; +} +#endif + +static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + pthread_mutex_lock(&f->lock); + +#if defined(CHECK_DIR_LOOP) + if (name1) + { + // called during rename; perform dir loop check + err = check_dir_loop(f, nodeid1, name1, nodeid2, name2); + if (err) + goto out_unlock; + } +#endif + + err = try_get_path2(f, nodeid1, name1, nodeid2, name2, + path1, path2, wnode1, wnode2); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid1, + .name1 = name1, + .path1 = path1, + .wnode1 = wnode1, + .nodeid2 = nodeid2, + .name2 = name2, + .path2 = path2, + .wnode2 = wnode2, + }; + + debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + } + +#if defined(CHECK_DIR_LOOP) +out_unlock: +#endif + pthread_mutex_unlock(&f->lock); + + return err; +} + +static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid, + struct node *wnode, char *path) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid, wnode, NULL); + if (f->lockq) + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path); +} + +static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path) +{ + if (path) + free_path_wrlock(f, nodeid, NULL, path); +} + +static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2, + struct node *wnode1, struct node *wnode2, + char *path1, char *path2) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid1, wnode1, NULL); + unlock_path(f, nodeid2, wnode2, NULL); + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path1); + free(path2); +} + +static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup) +{ + struct node *node; + if (nodeid == FUSE_ROOT_ID) + return; + pthread_mutex_lock(&f->lock); + node = get_node(f, nodeid); + + /* + * Node may still be locked due to interrupt idiocy in open, + * create and opendir + */ + while (node->nlookup == nlookup && node->treelock) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + }; + + debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false); + queue_path(f, &qe); + + do { + pthread_cond_wait(&qe.cond, &f->lock); + } while (node->nlookup == nlookup && node->treelock); + + dequeue_path(f, &qe); + debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false); + } + + assert(node->nlookup >= nlookup); + node->nlookup -= nlookup; + if (!node->nlookup) { + unref_node(f, node); + } else if (lru_enabled(f) && node->nlookup == 1) { + set_forget_time(f, node); + } + pthread_mutex_unlock(&f->lock); +} + +static void unlink_node(struct fuse *f, struct node *node) +{ + if (f->conf.remember) { + assert(node->nlookup > 1); + node->nlookup--; + } + unhash_name(f, node); +} + +static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node != NULL) + unlink_node(f, node); + pthread_mutex_unlock(&f->lock); +} + +static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, + fuse_ino_t newdir, const char *newname, int hide) +{ + struct node *node; + struct node *newnode; + int err = 0; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, olddir, oldname); + newnode = lookup_node(f, newdir, newname); + if (node == NULL) + goto out; + + if (newnode != NULL) { + if (hide) { + fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n"); + err = -EBUSY; + goto out; + } + unlink_node(f, newnode); + } + + unhash_name(f, node); + if (hash_name(f, node, newdir, newname) == -1) { + err = -ENOMEM; + goto out; + } + + if (hide) + node->is_hidden = 1; + +out: + pthread_mutex_unlock(&f->lock); + return err; +} + +static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, + fuse_ino_t newdir, const char *newname) +{ + struct node *oldnode; + struct node *newnode; + int err; + + pthread_mutex_lock(&f->lock); + oldnode = lookup_node(f, olddir, oldname); + newnode = lookup_node(f, newdir, newname); + + if (oldnode) + unhash_name(f, oldnode); + if (newnode) + unhash_name(f, newnode); + + err = -ENOMEM; + if (oldnode) { + if (hash_name(f, oldnode, newdir, newname) == -1) + goto out; + } + if (newnode) { + if (hash_name(f, newnode, olddir, oldname) == -1) + goto out; + } + err = 0; +out: + pthread_mutex_unlock(&f->lock); + return err; +} + +static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf) +{ + if (!f->conf.use_ino) + stbuf->st_ino = nodeid; + if (f->conf.set_mode) { + if (f->conf.dmask && S_ISDIR(stbuf->st_mode)) + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.dmask); + else if (f->conf.fmask) + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.fmask); + else + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.umask); + } + if (f->conf.set_uid) + stbuf->st_uid = f->conf.uid; + if (f->conf.set_gid) + stbuf->st_gid = f->conf.gid; +} + +static struct fuse *req_fuse(fuse_req_t req) +{ + return (struct fuse *) fuse_req_userdata(req); +} + +static void fuse_intr_sighandler(int sig) +{ + (void) sig; + /* Nothing to do */ +} + +struct fuse_intr_data { + pthread_t id; + pthread_cond_t cond; + int finished; +}; + +static void fuse_interrupt(fuse_req_t req, void *d_) +{ + struct fuse_intr_data *d = d_; + struct fuse *f = req_fuse(req); + + if (d->id == pthread_self()) + return; + + pthread_mutex_lock(&f->lock); + while (!d->finished) { + struct timeval now; + struct timespec timeout; + + pthread_kill(d->id, f->conf.intr_signal); + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait(&d->cond, &f->lock, &timeout); + } + pthread_mutex_unlock(&f->lock); +} + +static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + pthread_mutex_lock(&f->lock); + d->finished = 1; + pthread_cond_broadcast(&d->cond); + pthread_mutex_unlock(&f->lock); + fuse_req_interrupt_func(req, NULL, NULL); + pthread_cond_destroy(&d->cond); +} + +static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d) +{ + d->id = pthread_self(); + pthread_cond_init(&d->cond, NULL); + d->finished = 0; + fuse_req_interrupt_func(req, fuse_interrupt, d); +} + +static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_finish_interrupt(f, req, d); +} + +static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_prepare_interrupt(req, d); +} + +static const char* file_info_string(struct fuse_file_info *fi, + char* buf, size_t len) +{ + if(fi == NULL) + return "NULL"; + snprintf(buf, len, "%llu", (unsigned long long) fi->fh); + return buf; +} + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.getattr) { + if (fs->debug) { + char buf[10]; + fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n", + file_info_string(fi, buf, sizeof(buf)), + path); + } + return fs->op.getattr(path, buf, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath, unsigned int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.rename) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath, + flags); + + return fs->op.rename(oldpath, newpath, flags); + } else { + return -ENOSYS; + } +} + +int fuse_fs_unlink(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.unlink) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path); + + return fs->op.unlink(path); + } else { + return -ENOSYS; + } +} + +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.rmdir) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path); + + return fs->op.rmdir(path); + } else { + return -ENOSYS; + } +} + +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.symlink) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path); + + return fs->op.symlink(linkname, path); + } else { + return -ENOSYS; + } +} + +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.link) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath); + + return fs->op.link(oldpath, newpath); + } else { + return -ENOSYS; + } +} + +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.release) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n", + fi->flush ? "+flush" : "", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.release(path, fi); + } else { + return 0; + } +} + +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.opendir) { + int err; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags, + path); + + err = fs->op.opendir(path, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; + } else { + return 0; + } +} + +int fuse_fs_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.open) { + int err; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags, + path); + + err = fs->op.open(path, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; + } else { + return 0; + } +} + +static void fuse_free_buf(struct fuse_bufvec *buf) +{ + if (buf != NULL) { + size_t i; + + for (i = 0; i < buf->count; i++) + if (!(buf->buf[i].flags & FUSE_BUF_IS_FD)) + free(buf->buf[i].mem); + free(buf); + } +} + +int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec **bufp, size_t size, off_t off, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.read || fs->op.read_buf) { + int res; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); + + if (fs->op.read_buf) { + res = fs->op.read_buf(path, bufp, size, off, fi); + } else { + struct fuse_bufvec *buf; + void *mem; + + buf = malloc(sizeof(struct fuse_bufvec)); + if (buf == NULL) + return -ENOMEM; + + mem = malloc(size); + if (mem == NULL) { + free(buf); + return -ENOMEM; + } + *buf = FUSE_BUFVEC_INIT(size); + buf->buf[0].mem = mem; + *bufp = buf; + + res = fs->op.read(path, mem, size, off, fi); + if (res >= 0) + buf->buf[0].size = res; + } + + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n", + (unsigned long long) fi->fh, + fuse_buf_size(*bufp), + (unsigned long long) off); + if (res >= 0 && fuse_buf_size(*bufp) > size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + + if (res < 0) + return res; + + return 0; + } else { + return -ENOSYS; + } +} + +int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size, + off_t off, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.read || fs->op.read_buf) { + int res; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); + + if (fs->op.read_buf) { + struct fuse_bufvec *buf = NULL; + + res = fs->op.read_buf(path, &buf, size, off, fi); + if (res == 0) { + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size); + + dst.buf[0].mem = mem; + res = fuse_buf_copy(&dst, buf, 0); + } + fuse_free_buf(buf); + } else { + res = fs->op.read(path, mem, size, off, fi); + } + + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n", + (unsigned long long) fi->fh, + res, + (unsigned long long) off); + if (res >= 0 && res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + + return res; + } else { + return -ENOSYS; + } +} + +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.write_buf || fs->op.write) { + int res; + size_t size = fuse_buf_size(buf); + + assert(buf->idx == 0 && buf->off == 0); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "write%s[%llu] %zu bytes to %llu flags: 0x%x\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, + size, + (unsigned long long) off, + fi->flags); + + if (fs->op.write_buf) { + res = fs->op.write_buf(path, buf, off, fi); + } else { + void *mem = NULL; + struct fuse_buf *flatbuf; + struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size); + + if (buf->count == 1 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + flatbuf = &buf->buf[0]; + } else { + res = -ENOMEM; + mem = malloc(size); + if (mem == NULL) + goto out; + + tmp.buf[0].mem = mem; + res = fuse_buf_copy(&tmp, buf, 0); + if (res <= 0) + goto out_free; + + tmp.buf[0].size = res; + flatbuf = &tmp.buf[0]; + } + + res = fs->op.write(path, flatbuf->mem, flatbuf->size, + off, fi); +out_free: + free(mem); + } +out: + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, res, + (unsigned long long) off); + if (res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n"); + + return res; + } else { + return -ENOSYS; + } +} + +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem, + size_t size, off_t off, struct fuse_file_info *fi) +{ + struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size); + + bufv.buf[0].mem = (void *) mem; + + return fuse_fs_write_buf(fs, path, &bufv, off, fi); +} + +int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.fsync) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsync(path, datasync, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.fsyncdir) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsyncdir(path, datasync, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_flush(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.flush) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n", + (unsigned long long) fi->fh); + + return fs->op.flush(path, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statfs) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path); + + return fs->op.statfs(path, buf); + } else { + buf->f_namemax = 255; + buf->f_bsize = 512; + return 0; + } +} + +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.releasedir) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.releasedir(path, fi); + } else { + return 0; + } +} + +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, off_t off, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.readdir) { + if (fs->debug) { + fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n", + (flags & FUSE_READDIR_PLUS) ? "plus" : "", + (unsigned long long) fi->fh, + (unsigned long long) off); + } + + return fs->op.readdir(path, buf, filler, off, fi, flags); + } else { + return -ENOSYS; + } +} + +int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.create) { + int err; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "create flags: 0x%x %s 0%o umask=0%03o\n", + fi->flags, path, mode, + fuse_get_context()->umask); + + err = fs->op.create(path, mode, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; + } else { + return -ENOSYS; + } +} + +int fuse_fs_lock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int cmd, struct flock *lock) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.lock) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n", + (unsigned long long) fi->fh, + (cmd == F_GETLK ? "F_GETLK" : + (cmd == F_SETLK ? "F_SETLK" : + (cmd == F_SETLKW ? "F_SETLKW" : "???"))), + (lock->l_type == F_RDLCK ? "F_RDLCK" : + (lock->l_type == F_WRLCK ? "F_WRLCK" : + (lock->l_type == F_UNLCK ? "F_UNLCK" : + "???"))), + (unsigned long long) lock->l_start, + (unsigned long long) lock->l_len, + (unsigned long long) lock->l_pid); + + return fs->op.lock(path, fi, cmd, lock); + } else { + return -ENOSYS; + } +} + +int fuse_fs_flock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int op) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.flock) { + if (fs->debug) { + int xop = op & ~LOCK_NB; + + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n", + (unsigned long long) fi->fh, + xop == LOCK_SH ? "LOCK_SH" : + (xop == LOCK_EX ? "LOCK_EX" : + (xop == LOCK_UN ? "LOCK_UN" : "???")), + (op & LOCK_NB) ? "|LOCK_NB" : ""); + } + return fs->op.flock(path, fi, op); + } else { + return -ENOSYS; + } +} + +int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, + gid_t gid, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.chown) { + if (fs->debug) { + char buf[10]; + fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long) uid, (unsigned long) gid); + } + return fs->op.chown(path, uid, gid, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.truncate) { + if (fs->debug) { + char buf[10]; + fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) size); + } + return fs->op.truncate(path, size, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2], struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.utimens) { + if (fs->debug) { + char buf[10]; + fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n", + file_info_string(fi, buf, sizeof(buf)), + path, tv[0].tv_sec, tv[0].tv_nsec, + tv[1].tv_sec, tv[1].tv_nsec); + } + return fs->op.utimens(path, tv, fi); + } else { + return -ENOSYS; + } +} + +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.access) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask); + + return fs->op.access(path, mask); + } else { + return -ENOSYS; + } +} + +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.readlink) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path, + (unsigned long) len); + + return fs->op.readlink(path, buf, len); + } else { + return -ENOSYS; + } +} + +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.mknod) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n", + path, mode, (unsigned long long) rdev, + fuse_get_context()->umask); + + return fs->op.mknod(path, mode, rdev); + } else { + return -ENOSYS; + } +} + +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.mkdir) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n", + path, mode, fuse_get_context()->umask); + + return fs->op.mkdir(path, mode); + } else { + return -ENOSYS; + } +} + +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.setxattr) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n", + path, name, (unsigned long) size, flags); + + return fs->op.setxattr(path, name, value, size, flags); + } else { + return -ENOSYS; + } +} + +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.getxattr) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n", + path, name, (unsigned long) size); + + return fs->op.getxattr(path, name, value, size); + } else { + return -ENOSYS; + } +} + +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.listxattr) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n", + path, (unsigned long) size); + + return fs->op.listxattr(path, list, size); + } else { + return -ENOSYS; + } +} + +int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, + uint64_t *idx) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.bmap) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n", + path, (unsigned long) blocksize, + (unsigned long long) *idx); + + return fs->op.bmap(path, blocksize, idx); + } else { + return -ENOSYS; + } +} + +int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.removexattr) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name); + + return fs->op.removexattr(path, name); + } else { + return -ENOSYS; + } +} + +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.ioctl) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n", + (unsigned long long) fi->fh, cmd, flags); + + return fs->op.ioctl(path, cmd, arg, fi, flags, data); + } else + return -ENOSYS; +} + +int fuse_fs_poll(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, struct fuse_pollhandle *ph, + unsigned *reventsp) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.poll) { + int res; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n", + (unsigned long long) fi->fh, ph, + fi->poll_events); + + res = fs->op.poll(path, fi, ph, reventsp); + + if (fs->debug && !res) + fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n", + (unsigned long long) fi->fh, *reventsp); + + return res; + } else + return -ENOSYS; +} + +int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.fallocate) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n", + path, + mode, + (unsigned long long) offset, + (unsigned long long) length); + + return fs->op.fallocate(path, mode, offset, length, fi); + } else + return -ENOSYS; +} + +ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, + struct fuse_file_info *fi_in, off_t off_in, + const char *path_out, + struct fuse_file_info *fi_out, off_t off_out, + size_t len, int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.copy_file_range) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to " + "%s:%llu, length: %llu\n", + path_in, + (unsigned long long) off_in, + path_out, + (unsigned long long) off_out, + (unsigned long long) len); + + return fs->op.copy_file_range(path_in, fi_in, off_in, path_out, + fi_out, off_out, len, flags); + } else + return -ENOSYS; +} + +off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.lseek) { + if (fs->debug) { + char buf[10]; + fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) off, whence); + } + return fs->op.lseek(path, off, whence, fi); + } else { + return -ENOSYS; + } +} + +static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + int isopen = 0; + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node && node->open_count > 0) + isopen = 1; + pthread_mutex_unlock(&f->lock); + return isopen; +} + +static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname, + char *newname, size_t bufsize) +{ + struct stat buf; + struct node *node; + struct node *newnode; + char *newpath; + int res; + int failctr = 10; + + do { + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, oldname); + if (node == NULL) { + pthread_mutex_unlock(&f->lock); + return NULL; + } + do { + f->hidectr ++; + snprintf(newname, bufsize, ".fuse_hidden%08x%08x", + (unsigned int) node->nodeid, f->hidectr); + newnode = lookup_node(f, dir, newname); + } while(newnode); + + res = try_get_path(f, dir, newname, &newpath, NULL, false); + pthread_mutex_unlock(&f->lock); + if (res) + break; + + memset(&buf, 0, sizeof(buf)); + res = fuse_fs_getattr(f->fs, newpath, &buf, NULL); + if (res == -ENOENT) + break; + free(newpath); + newpath = NULL; + } while(res == 0 && --failctr); + + return newpath; +} + +static int hide_node(struct fuse *f, const char *oldpath, + fuse_ino_t dir, const char *oldname) +{ + char newname[64]; + char *newpath; + int err = -EBUSY; + + newpath = hidden_name(f, dir, oldname, newname, sizeof(newname)); + if (newpath) { + err = fuse_fs_rename(f->fs, oldpath, newpath, 0); + if (!err) + err = rename_node(f, dir, oldname, dir, newname, 1); + free(newpath); + } + return err; +} + +static int mtime_eq(const struct stat *stbuf, const struct timespec *ts) +{ + return stbuf->st_mtime == ts->tv_sec && + ST_MTIM_NSEC(stbuf) == ts->tv_nsec; +} + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + +static void curr_time(struct timespec *now) +{ + static clockid_t clockid = CLOCK_MONOTONIC; + int res = clock_gettime(clockid, now); + if (res == -1 && errno == EINVAL) { + clockid = CLOCK_REALTIME; + res = clock_gettime(clockid, now); + } + if (res == -1) { + perror("fuse: clock_gettime"); + abort(); + } +} + +static void update_stat(struct node *node, const struct stat *stbuf) +{ + if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) || + stbuf->st_size != node->size)) + node->cache_valid = 0; + node->mtime.tv_sec = stbuf->st_mtime; + node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf); + node->size = stbuf->st_size; + curr_time(&node->stat_updated); +} + +static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name, + struct fuse_entry_param *e) +{ + struct node *node; + + node = find_node(f, nodeid, name); + if (node == NULL) + return -ENOMEM; + + e->ino = node->nodeid; + e->generation = node->generation; + e->entry_timeout = f->conf.entry_timeout; + e->attr_timeout = f->conf.attr_timeout; + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(node, &e->attr); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, e->ino, &e->attr); + return 0; +} + +static int lookup_path(struct fuse *f, fuse_ino_t nodeid, + const char *name, const char *path, + struct fuse_entry_param *e, struct fuse_file_info *fi) +{ + int res; + + memset(e, 0, sizeof(struct fuse_entry_param)); + res = fuse_fs_getattr(f->fs, path, &e->attr, fi); + if (res == 0) { + res = do_lookup(f, nodeid, name, e); + if (res == 0 && f->conf.debug) { + fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n", + (unsigned long long) e->ino); + } + } + return res; +} + +static struct fuse_context_i *fuse_get_context_internal(void) +{ + return (struct fuse_context_i *) pthread_getspecific(fuse_context_key); +} + +static struct fuse_context_i *fuse_create_context(struct fuse *f) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + if (c == NULL) { + c = (struct fuse_context_i *) + calloc(1, sizeof(struct fuse_context_i)); + if (c == NULL) { + /* This is hard to deal with properly, so just + abort. If memory is so low that the + context cannot be allocated, there's not + much hope for the filesystem anyway */ + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n"); + abort(); + } + pthread_setspecific(fuse_context_key, c); + } else { + memset(c, 0, sizeof(*c)); + } + c->ctx.fuse = f; + + return c; +} + +static void fuse_freecontext(void *data) +{ + free(data); +} + +static int fuse_create_context_key(void) +{ + int err = 0; + pthread_mutex_lock(&fuse_context_lock); + if (!fuse_context_ref) { + err = pthread_key_create(&fuse_context_key, fuse_freecontext); + if (err) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + pthread_mutex_unlock(&fuse_context_lock); + return -1; + } + } + fuse_context_ref++; + pthread_mutex_unlock(&fuse_context_lock); + return 0; +} + +static void fuse_delete_context_key(void) +{ + pthread_mutex_lock(&fuse_context_lock); + fuse_context_ref--; + if (!fuse_context_ref) { + free(pthread_getspecific(fuse_context_key)); + pthread_key_delete(fuse_context_key); + } + pthread_mutex_unlock(&fuse_context_lock); +} + +static struct fuse *req_fuse_prepare(fuse_req_t req) +{ + struct fuse_context_i *c = fuse_create_context(req_fuse(req)); + const struct fuse_ctx *ctx = fuse_req_ctx(req); + c->req = req; + c->ctx.uid = ctx->uid; + c->ctx.gid = ctx->gid; + c->ctx.pid = ctx->pid; + c->ctx.umask = ctx->umask; + return c->ctx.fuse; +} + +static inline void reply_err(fuse_req_t req, int err) +{ + /* fuse_reply_err() uses non-negated errno values */ + fuse_reply_err(req, -err); +} + +static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e, + int err) +{ + if (!err) { + struct fuse *f = req_fuse(req); + if (fuse_reply_entry(req, e) == -ENOENT) { + /* Skip forget for negative result */ + if (e->ino != 0) + forget_node(f, e->ino, 1); + } + } else + reply_err(req, err); +} + +void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.write_buf) + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); + if (!fs->op.lock) + fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS); + if (!fs->op.flock) + fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (fs->op.init) { + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + conn->want = want_default; + fs->user_data = fs->op.init(conn, cfg); + + rc = convert_to_conn_want_ext(conn, want_ext_default, + want_default); + + if (rc != 0) { + /* + * This is a grave developer error, but + * we cannot return an error here, as the function + * signature does not allow it. + */ + fuse_log( + FUSE_LOG_ERR, + "fuse: Aborting due to invalid conn want flags.\n"); + _exit(EXIT_FAILURE); + } + } +} + +static int fuse_init_intr_signal(int signum, int *installed); + +static void fuse_lib_init(void *data, struct fuse_conn_info *conn) +{ + struct fuse *f = (struct fuse *) data; + + fuse_create_context(f); + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + fuse_fs_init(f->fs, conn, &f->conf); + + if (f->conf.intr) { + if (fuse_init_intr_signal(f->conf.intr_signal, + &f->intr_installed) == -1) + fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n"); + } else { + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + } +} + +void fuse_fs_destroy(struct fuse_fs *fs) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.destroy) + fs->op.destroy(fs->user_data); +} + +static void fuse_lib_destroy(void *data) +{ + struct fuse *f = (struct fuse *) data; + + fuse_create_context(f); + fuse_fs_destroy(f->fs); +} + +static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + struct node *dot = NULL; + + if (name[0] == '.') { + int len = strlen(name); + + if (len == 1 || (name[1] == '.' && len == 2)) { + pthread_mutex_lock(&f->lock); + if (len == 1) { + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n"); + dot = get_node_nocheck(f, parent); + if (dot == NULL) { + pthread_mutex_unlock(&f->lock); + reply_entry(req, &e, -ESTALE); + return; + } + dot->refctr++; + } else { + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n"); + parent = get_node(f, parent)->parent->nodeid; + } + pthread_mutex_unlock(&f->lock); + name = NULL; + } + } + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path); + fuse_prepare_interrupt(f, req, &d); + err = lookup_path(f, parent, name, path, &e, NULL); + if (err == -ENOENT && f->conf.negative_timeout != 0.0) { + e.ino = 0; + e.entry_timeout = f->conf.negative_timeout; + err = 0; + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + if (dot) { + pthread_mutex_lock(&f->lock); + unref_node(f, dot); + pthread_mutex_unlock(&f->lock); + } + reply_entry(req, &e, err); +} + +static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup) +{ + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino, + (unsigned long long) nlookup); + forget_node(f, ino, nlookup); +} + +static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + do_forget(req_fuse(req), ino, nlookup); + fuse_reply_none(req); +} + +static void fuse_lib_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + struct fuse *f = req_fuse(req); + size_t i; + + for (i = 0; i < count; i++) + do_forget(f, forgets[i].ino, forgets[i].nlookup); + + fuse_reply_none(req); +} + + +static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_getattr(f->fs, path, &buf, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && buf.st_nlink > 0) + buf.st_nlink--; + if (f->conf.auto_cache) + update_stat(node, &buf); + pthread_mutex_unlock(&f->lock); + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.chmod) { + if (fs->debug) { + char buf[10]; + fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long long) mode); + } + return fs->op.chmod(path, mode, fi); + } + else + return -ENOSYS; +} + +static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = 0; + if (!err && (valid & FUSE_SET_ATTR_MODE)) + err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi); + if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : (uid_t) -1; + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : (gid_t) -1; + err = fuse_fs_chown(f->fs, path, uid, gid, fi); + } + if (!err && (valid & FUSE_SET_ATTR_SIZE)) { + err = fuse_fs_truncate(f->fs, path, + attr->st_size, fi); + } +#ifdef HAVE_UTIMENSAT + if (!err && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + err = fuse_fs_utimens(f->fs, path, tv, fi); + } else +#endif + if (!err && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) == + (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + tv[0].tv_sec = attr->st_atime; + tv[0].tv_nsec = ST_ATIM_NSEC(attr); + tv[1].tv_sec = attr->st_mtime; + tv[1].tv_nsec = ST_MTIM_NSEC(attr); + err = fuse_fs_utimens(f->fs, path, tv, fi); + } + if (!err) { + err = fuse_fs_getattr(f->fs, path, &buf, fi); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(get_node(f, ino), &buf); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_access(f->fs, path, mask); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + char linkname[PATH_MAX + 1]; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname)); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + linkname[PATH_MAX] = '\0'; + fuse_reply_readlink(req, linkname); + } else + reply_err(req, err); +} + +static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = -ENOSYS; + if (S_ISREG(mode)) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = O_CREAT | O_EXCL | O_WRONLY; + err = fuse_fs_create(f->fs, path, mode, &fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, + &fi); + fuse_fs_release(f->fs, path, &fi); + } + } + if (err == -ENOSYS) { + err = fuse_fs_mknod(f->fs, path, mode, rdev); + if (!err) + err = lookup_path(f, parent, name, path, &e, + NULL); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_mkdir(f->fs, path, mode); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && is_open(f, parent, name)) { + err = hide_node(f, path, parent, name); + if (!err) { + /* we have hidden the node so now check again under a lock in case it is not used any more */ + if (!is_open(f, parent, wnode->name)) { + char *unlinkpath; + + /* get the hidden file path, to unlink it */ + if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) { + err = fuse_fs_unlink(f->fs, unlinkpath); + if (!err) + remove_node(f, parent, wnode->name); + free(unlinkpath); + } + } + } + } else { + err = fuse_fs_unlink(f->fs, path); + if (!err) + remove_node(f, parent, name); + } + fuse_finish_interrupt(f, req, &d); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_rmdir(f->fs, path); + fuse_finish_interrupt(f, req, &d); + if (!err) + remove_node(f, parent, name); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_symlink(fuse_req_t req, const char *linkname, + fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_symlink(f->fs, linkname, path); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir, + const char *oldname, fuse_ino_t newdir, + const char *newname, unsigned int flags) +{ + struct fuse *f = req_fuse_prepare(req); + char *oldpath; + char *newpath; + struct node *wnode1; + struct node *wnode2; + int err; + + err = get_path2(f, olddir, oldname, newdir, newname, + &oldpath, &newpath, &wnode1, &wnode2); + if (!err) { + struct fuse_intr_data d; + err = 0; + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) && + is_open(f, newdir, newname)) + err = hide_node(f, newpath, newdir, newname); + if (!err) { + err = fuse_fs_rename(f->fs, oldpath, newpath, flags); + if (!err) { + if (flags & RENAME_EXCHANGE) { + err = exchange_node(f, olddir, oldname, + newdir, newname); + } else { + err = rename_node(f, olddir, oldname, + newdir, newname, 0); + } + } + } + fuse_finish_interrupt(f, req, &d); + free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath); + } + reply_err(req, err); +} + +static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *oldpath; + char *newpath; + int err; + + err = get_path2(f, ino, NULL, newparent, newname, + &oldpath, &newpath, NULL, NULL); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_link(f->fs, oldpath, newpath); + if (!err) + err = lookup_path(f, newparent, newname, newpath, + &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath); + } + reply_entry(req, &e, err); +} + +static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + int unlink_hidden = 0; + + fuse_fs_release(f->fs, path, fi); + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + assert(node->open_count > 0); + --node->open_count; + if (node->is_hidden && !node->open_count) { + unlink_hidden = 1; + node->is_hidden = 0; + } + pthread_mutex_unlock(&f->lock); + + if(unlink_hidden) { + if (path) { + fuse_fs_unlink(f->fs, path); + } else if (f->conf.nullpath_ok) { + char *unlinkpath; + + if (get_path(f, ino, &unlinkpath) == 0) + fuse_fs_unlink(f->fs, unlinkpath); + + free_path(f, ino, unlinkpath); + } + } +} + +static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_create(f->fs, path, mode, fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, fi); + if (err) + fuse_fs_release(f->fs, path, fi); + else if (!S_ISREG(e.attr.st_mode)) { + err = -EIO; + fuse_fs_release(f->fs, path, fi); + forget_node(f, e.ino, 1); + } else { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + if (fi->direct_io && + f->conf.parallel_direct_writes) + fi->parallel_direct_writes = 1; + } + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, e.ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_create(req, &e, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, e.ino, path, fi); + forget_node(f, e.ino, 1); + } + } else { + reply_err(req, err); + } + + free_path(f, parent, path); +} + +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2) +{ + return (t1->tv_sec - t2->tv_sec) + + ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0; +} + +static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->cache_valid) { + struct timespec now; + + curr_time(&now); + if (diff_timespec(&now, &node->stat_updated) > + f->conf.ac_attr_timeout) { + struct stat stbuf; + int err; + pthread_mutex_unlock(&f->lock); + err = fuse_fs_getattr(f->fs, path, &stbuf, fi); + pthread_mutex_lock(&f->lock); + if (!err) + update_stat(node, &stbuf); + else + node->cache_valid = 0; + } + } + if (node->cache_valid) + fi->keep_cache = 1; + + node->cache_valid = 1; + pthread_mutex_unlock(&f->lock); +} + +static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_open(f->fs, path, fi); + if (!err) { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + + if (f->conf.auto_cache) + open_auto_cache(f, ino, path, fi); + + if (f->conf.no_rofd_flush && + (fi->flags & O_ACCMODE) == O_RDONLY) + fi->noflush = 1; + + if (fi->direct_io && f->conf.parallel_direct_writes) + fi->parallel_direct_writes = 1; + + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_open(req, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, ino, path, fi); + } + } else + reply_err(req, err); + + free_path(f, ino, path); +} + +static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_bufvec *buf = NULL; + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res == 0) + fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE); + else + reply_err(req, res); + + fuse_free_buf(buf); +} + +static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_write_buf(f->fs, path, buf, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res >= 0) + fuse_reply_write(req, res); + else + reply_err(req, res); +} + +static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsync(f->fs, path, datasync, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi, + struct fuse_file_info *fi) +{ + struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh; + memset(fi, 0, sizeof(struct fuse_file_info)); + fi->fh = dh->fh; + return dh; +} + +static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_dh *dh; + struct fuse_file_info fi; + char *path; + int err; + + dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh)); + if (dh == NULL) { + reply_err(req, -ENOMEM); + return; + } + memset(dh, 0, sizeof(struct fuse_dh)); + dh->fuse = f; + dh->contents = NULL; + dh->first = NULL; + dh->len = 0; + dh->filled = 0; + dh->nodeid = ino; + pthread_mutex_init(&dh->lock, NULL); + + llfi->fh = (uintptr_t) dh; + + memset(&fi, 0, sizeof(fi)); + fi.flags = llfi->flags; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_opendir(f->fs, path, &fi); + fuse_finish_interrupt(f, req, &d); + dh->fh = fi.fh; + llfi->cache_readdir = fi.cache_readdir; + llfi->keep_cache = fi.keep_cache; + } + if (!err) { + if (fuse_reply_open(req, llfi) == -ENOENT) { + /* The opendir syscall was interrupted, so it + must be cancelled */ + fuse_fs_releasedir(f->fs, path, &fi); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + } else { + reply_err(req, err); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + free_path(f, ino, path); +} + +static int extend_contents(struct fuse_dh *dh, unsigned minsize) +{ + if (minsize > dh->size) { + char *newptr; + unsigned newsize = dh->size; + if (!newsize) + newsize = 1024; + while (newsize < minsize) { + if (newsize >= 0x80000000) + newsize = 0xffffffff; + else + newsize *= 2; + } + + newptr = (char *) realloc(dh->contents, newsize); + if (!newptr) { + dh->error = -ENOMEM; + return -1; + } + dh->contents = newptr; + dh->size = newsize; + } + return 0; +} + +static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name, + struct stat *st, enum fuse_fill_dir_flags flags) +{ + struct fuse_direntry *de; + + de = malloc(sizeof(struct fuse_direntry)); + if (!de) { + dh->error = -ENOMEM; + return -1; + } + de->name = strdup(name); + if (!de->name) { + dh->error = -ENOMEM; + free(de); + return -1; + } + de->flags = flags; + de->stat = *st; + de->next = NULL; + + *dh->last = de; + dh->last = &de->next; + + return 0; +} + +static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + struct node *node; + fuse_ino_t res = FUSE_UNKNOWN_INO; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, parent, name); + if (node) + res = node->nodeid; + pthread_mutex_unlock(&f->lock); + + return res; +} + +static int fill_dir(void *dh_, const char *name, const struct stat *statp, + off_t off, enum fuse_fill_dir_flags flags) +{ + struct fuse_dh *dh = (struct fuse_dh *) dh_; + struct stat stbuf; + + if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) { + dh->error = -EIO; + return 1; + } + + if (statp) + stbuf = *statp; + else { + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = FUSE_UNKNOWN_INO; + } + + if (!dh->fuse->conf.use_ino) { + stbuf.st_ino = FUSE_UNKNOWN_INO; + if (dh->fuse->conf.readdir_ino) { + stbuf.st_ino = (ino_t) + lookup_nodeid(dh->fuse, dh->nodeid, name); + } + } + + if (off) { + size_t newlen; + + if (dh->filled) { + dh->error = -EIO; + return 1; + } + + if (dh->first) { + dh->error = -EIO; + return 1; + } + + if (extend_contents(dh, dh->needlen) == -1) + return 1; + + newlen = dh->len + + fuse_add_direntry(dh->req, dh->contents + dh->len, + dh->needlen - dh->len, name, + &stbuf, off); + if (newlen > dh->needlen) + return 1; + + dh->len = newlen; + } else { + dh->filled = 1; + + if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1) + return 1; + } + return 0; +} + +static int is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); +} + +static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp, + off_t off, enum fuse_fill_dir_flags flags) +{ + struct fuse_dh *dh = (struct fuse_dh *) dh_; + struct fuse_entry_param e = { + /* ino=0 tells the kernel to ignore readdirplus stat info */ + .ino = 0, + }; + struct fuse *f = dh->fuse; + int res; + + if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) { + dh->error = -EIO; + return 1; + } + + if (statp && (flags & FUSE_FILL_DIR_PLUS)) { + e.attr = *statp; + } else { + e.attr.st_ino = FUSE_UNKNOWN_INO; + if (statp) { + e.attr.st_mode = statp->st_mode; + if (f->conf.use_ino) + e.attr.st_ino = statp->st_ino; + } + if (!f->conf.use_ino && f->conf.readdir_ino) { + e.attr.st_ino = (ino_t) + lookup_nodeid(f, dh->nodeid, name); + } + } + + if (off) { + size_t newlen; + + if (dh->filled) { + dh->error = -EIO; + return 1; + } + + if (dh->first) { + dh->error = -EIO; + return 1; + } + if (extend_contents(dh, dh->needlen) == -1) + return 1; + + if (statp && (flags & FUSE_FILL_DIR_PLUS)) { + if (!is_dot_or_dotdot(name)) { + res = do_lookup(f, dh->nodeid, name, &e); + if (res) { + dh->error = res; + return 1; + } + } + } + + newlen = dh->len + + fuse_add_direntry_plus(dh->req, dh->contents + dh->len, + dh->needlen - dh->len, name, + &e, off); + if (newlen > dh->needlen) + return 1; + dh->len = newlen; + } else { + dh->filled = 1; + + if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1) + return 1; + } + + return 0; +} + +static void free_direntries(struct fuse_direntry *de) +{ + while (de) { + struct fuse_direntry *next = de->next; + free(de->name); + free(de); + de = next; + } +} + +static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + size_t size, off_t off, struct fuse_dh *dh, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + char *path; + int err; + + if (f->fs->op.readdir) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_fill_dir_t filler = fill_dir; + + if (flags & FUSE_READDIR_PLUS) + filler = fill_dir_plus; + + free_direntries(dh->first); + dh->first = NULL; + dh->last = &dh->first; + dh->len = 0; + dh->error = 0; + dh->needlen = size; + dh->filled = 0; + dh->req = req; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags); + fuse_finish_interrupt(f, req, &d); + dh->req = NULL; + if (!err) + err = dh->error; + if (err) + dh->filled = 0; + free_path(f, ino, path); + } + return err; +} + +static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh, + off_t off, enum fuse_readdir_flags flags) +{ + off_t pos; + struct fuse_direntry *de = dh->first; + int res; + + dh->len = 0; + + if (extend_contents(dh, dh->needlen) == -1) + return dh->error; + + for (pos = 0; pos < off; pos++) { + if (!de) + break; + + de = de->next; + } + while (de) { + char *p = dh->contents + dh->len; + unsigned rem = dh->needlen - dh->len; + unsigned thislen; + unsigned newlen; + pos++; + + if (flags & FUSE_READDIR_PLUS) { + struct fuse_entry_param e = { + .ino = 0, + .attr = de->stat, + }; + + if (de->flags & FUSE_FILL_DIR_PLUS && + !is_dot_or_dotdot(de->name)) { + res = do_lookup(dh->fuse, dh->nodeid, + de->name, &e); + if (res) { + dh->error = res; + return 1; + } + } + + thislen = fuse_add_direntry_plus(req, p, rem, + de->name, &e, pos); + } else { + thislen = fuse_add_direntry(req, p, rem, + de->name, &de->stat, pos); + } + newlen = dh->len + thislen; + if (newlen > dh->needlen) + break; + dh->len = newlen; + de = de->next; + } + return 0; +} + +static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi, + enum fuse_readdir_flags flags) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + int err; + + pthread_mutex_lock(&dh->lock); + /* According to SUS, directory contents need to be refreshed on + rewinddir() */ + if (!off) + dh->filled = 0; + + if (!dh->filled) { + err = readdir_fill(f, req, ino, size, off, dh, &fi, flags); + if (err) { + reply_err(req, err); + goto out; + } + } + if (dh->filled) { + dh->needlen = size; + err = readdir_fill_from_list(req, dh, off, flags); + if (err) { + reply_err(req, err); + goto out; + } + } + fuse_reply_buf(req, dh->contents, dh->len); +out: + pthread_mutex_unlock(&dh->lock); +} + +static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi) +{ + fuse_readdir_common(req, ino, size, off, llfi, 0); +} + +static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi) +{ + fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS); +} + +static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + char *path; + + get_path_nullok(f, ino, &path); + + fuse_prepare_interrupt(f, req, &d); + fuse_fs_releasedir(f->fs, path, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + pthread_mutex_lock(&dh->lock); + pthread_mutex_unlock(&dh->lock); + pthread_mutex_destroy(&dh->lock); + free_direntries(dh->first); + free(dh->contents); + free(dh); + reply_err(req, 0); +} + +static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + char *path; + int err; + + get_dirhandle(llfi, &fi); + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + struct statvfs buf; + char *path = NULL; + int err = 0; + + memset(&buf, 0, sizeof(buf)); + if (ino) + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statfs(f->fs, path ? path : "/", &buf); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (!err) + fuse_reply_statfs(req, &buf); + else + reply_err(req, err); +} + +static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_setxattr(f->fs, path, name, value, size, flags); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *name, char *value, size_t size) +{ + int err; + char *path; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_getxattr(f->fs, path, name, value, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *value = (char *) malloc(size); + if (value == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_getxattr(f, req, ino, name, value, size); + if (res > 0) + fuse_reply_buf(req, value, res); + else + reply_err(req, res); + free(value); + } else { + res = common_getxattr(f, req, ino, name, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + char *list, size_t size) +{ + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_listxattr(f->fs, path, list, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *list = (char *) malloc(size); + if (list == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_listxattr(f, req, ino, list, size); + if (res > 0) + fuse_reply_buf(req, list, res); + else + reply_err(req, res); + free(list); + } else { + res = common_listxattr(f, req, ino, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_removexattr(f->fs, path, name); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct lock *locks_conflict(struct node *node, const struct lock *lock) +{ + struct lock *l; + + for (l = node->locks; l; l = l->next) + if (l->owner != lock->owner && + lock->start <= l->end && l->start <= lock->end && + (l->type == F_WRLCK || lock->type == F_WRLCK)) + break; + + return l; +} + +static void delete_lock(struct lock **lockp) +{ + struct lock *l = *lockp; + *lockp = l->next; + free(l); +} + +static void insert_lock(struct lock **pos, struct lock *lock) +{ + lock->next = *pos; + *pos = lock; +} + +static int locks_insert(struct node *node, struct lock *lock) +{ + struct lock **lp; + struct lock *newl1 = NULL; + struct lock *newl2 = NULL; + + if (lock->type != F_UNLCK || lock->start != 0 || + lock->end != OFFSET_MAX) { + newl1 = malloc(sizeof(struct lock)); + newl2 = malloc(sizeof(struct lock)); + + if (!newl1 || !newl2) { + free(newl1); + free(newl2); + return -ENOLCK; + } + } + + for (lp = &node->locks; *lp;) { + struct lock *l = *lp; + if (l->owner != lock->owner) + goto skip; + + if (lock->type == l->type) { + if (l->end < lock->start - 1) + goto skip; + if (lock->end < l->start - 1) + break; + if (l->start <= lock->start && lock->end <= l->end) + goto out; + if (l->start < lock->start) + lock->start = l->start; + if (lock->end < l->end) + lock->end = l->end; + goto delete; + } else { + if (l->end < lock->start) + goto skip; + if (lock->end < l->start) + break; + if (lock->start <= l->start && l->end <= lock->end) + goto delete; + if (l->end <= lock->end) { + l->end = lock->start - 1; + goto skip; + } + if (lock->start <= l->start) { + l->start = lock->end + 1; + break; + } + *newl2 = *l; + newl2->start = lock->end + 1; + l->end = lock->start - 1; + insert_lock(&l->next, newl2); + newl2 = NULL; + } + skip: + lp = &l->next; + continue; + + delete: + delete_lock(lp); + } + if (lock->type != F_UNLCK) { + *newl1 = *lock; + insert_lock(lp, newl1); + newl1 = NULL; + } +out: + free(newl1); + free(newl2); + return 0; +} + +static void flock_to_lock(struct flock *flock, struct lock *lock) +{ + memset(lock, 0, sizeof(struct lock)); + lock->type = flock->l_type; + lock->start = flock->l_start; + lock->end = + flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX; + lock->pid = flock->l_pid; +} + +static void lock_to_flock(struct lock *lock, struct flock *flock) +{ + flock->l_type = lock->type; + flock->l_start = lock->start; + flock->l_len = + (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1; + flock->l_pid = lock->pid; +} + +static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *path, struct fuse_file_info *fi) +{ + struct fuse_intr_data d; + struct flock lock; + struct lock l; + int err; + int errlock; + + fuse_prepare_interrupt(f, req, &d); + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + err = fuse_fs_flush(f->fs, path, fi); + errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock); + fuse_finish_interrupt(f, req, &d); + + if (errlock != -ENOSYS) { + flock_to_lock(&lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + + /* if op.lock() is defined FLUSH is needed regardless + of op.flush() */ + if (err == -ENOSYS) + err = 0; + } + return err; +} + +static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err = 0; + + get_path_nullok(f, ino, &path); + if (fi->flush) { + err = fuse_flush_common(f, req, ino, path, fi); + if (err == -ENOSYS) + err = 0; + } + + fuse_prepare_interrupt(f, req, &d); + fuse_do_release(f, ino, path, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + reply_err(req, err); +} + +static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + get_path_nullok(f, ino, &path); + err = fuse_flush_common(f, req, ino, path, fi); + free_path(f, ino, path); + + reply_err(req, err); +} + +static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int cmd) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_lock(f->fs, path, fi, cmd, lock); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock) +{ + int err; + struct lock l; + struct lock *conflict; + struct fuse *f = req_fuse(req); + + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + conflict = locks_conflict(get_node(f, ino), &l); + if (conflict) + lock_to_flock(conflict, lock); + pthread_mutex_unlock(&f->lock); + if (!conflict) + err = fuse_lock_common(req, ino, fi, lock, F_GETLK); + else + err = 0; + + if (!err) + fuse_reply_lock(req, lock); + else + reply_err(req, err); +} + +static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int sleep) +{ + int err = fuse_lock_common(req, ino, fi, lock, + sleep ? F_SETLKW : F_SETLK); + if (!err) { + struct fuse *f = req_fuse(req); + struct lock l; + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + } + reply_err(req, err); +} + +static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, int op) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (err == 0) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_flock(f->fs, path, fi, op); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, + uint64_t idx) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_bmap(f->fs, path, blocksize, &idx); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) + fuse_reply_bmap(req, idx); + else + reply_err(req, err); +} + +static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, + void *arg, struct fuse_file_info *llfi, + unsigned int flags, const void *in_buf, + size_t in_bufsz, size_t out_bufsz) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + char *path, *out_buf = NULL; + int err; + + err = -EPERM; + if (flags & FUSE_IOCTL_UNRESTRICTED) + goto err; + + if (flags & FUSE_IOCTL_DIR) + get_dirhandle(llfi, &fi); + else + fi = *llfi; + + if (out_bufsz) { + err = -ENOMEM; + out_buf = malloc(out_bufsz); + if (!out_buf) + goto err; + } + + assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz); + if (out_buf && in_bufsz) + memcpy(out_buf, in_buf, in_bufsz); + + err = get_path_nullok(f, ino, &path); + if (err) + goto err; + + fuse_prepare_interrupt(f, req, &d); + + err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags, + out_buf ? out_buf : (void *)in_buf); + + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + if (err < 0) + goto err; + fuse_reply_ioctl(req, err, out_buf, out_bufsz); + goto out; +err: + reply_err(req, err); +out: + free(out_buf); +} + +static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + unsigned revents = 0; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_poll(f->fs, path, fi, ph, &revents); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) + fuse_reply_poll(req, revents); + else + reply_err(req, err); +} + +static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, + off_t off_in, struct fuse_file_info *fi_in, + fuse_ino_t nodeid_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path_in, *path_out; + int err; + ssize_t res; + + err = get_path_nullok(f, nodeid_in, &path_in); + if (err) { + reply_err(req, err); + return; + } + + err = get_path_nullok(f, nodeid_out, &path_out); + if (err) { + free_path(f, nodeid_in, path_in); + reply_err(req, err); + return; + } + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out, + fi_out, off_out, len, flags); + fuse_finish_interrupt(f, req, &d); + + if (res >= 0) + fuse_reply_write(req, res); + else + reply_err(req, res); + + free_path(f, nodeid_in, path_in); + free_path(f, nodeid_out, path_out); +} + +static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + off_t res; + + err = get_path(f, ino, &path); + if (err) { + reply_err(req, err); + return; + } + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_lseek(f->fs, path, off, whence, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + if (res >= 0) + fuse_reply_lseek(req, res); + else + reply_err(req, res); +} + +static int clean_delay(struct fuse *f) +{ + /* + * This is calculating the delay between clean runs. To + * reduce the number of cleans we are doing them 10 times + * within the remember window. + */ + int min_sleep = 60; + int max_sleep = 3600; + int sleep_time = f->conf.remember / 10; + + if (sleep_time > max_sleep) + return max_sleep; + if (sleep_time < min_sleep) + return min_sleep; + return sleep_time; +} + +int fuse_clean_cache(struct fuse *f) +{ + struct node_lru *lnode; + struct list_head *curr, *next; + struct node *node; + struct timespec now; + + pthread_mutex_lock(&f->lock); + + curr_time(&now); + + for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) { + double age; + + next = curr->next; + lnode = list_entry(curr, struct node_lru, lru); + node = &lnode->node; + + age = diff_timespec(&now, &lnode->forget_time); + if (age <= f->conf.remember) + break; + + assert(node->nlookup == 1); + + /* Don't forget active directories */ + if (node->refctr > 1) + continue; + + node->nlookup = 0; + unhash_name(f, node); + unref_node(f, node); + } + pthread_mutex_unlock(&f->lock); + + return clean_delay(f); +} + +static struct fuse_lowlevel_ops fuse_path_ops = { + .init = fuse_lib_init, + .destroy = fuse_lib_destroy, + .lookup = fuse_lib_lookup, + .forget = fuse_lib_forget, + .forget_multi = fuse_lib_forget_multi, + .getattr = fuse_lib_getattr, + .setattr = fuse_lib_setattr, + .access = fuse_lib_access, + .readlink = fuse_lib_readlink, + .mknod = fuse_lib_mknod, + .mkdir = fuse_lib_mkdir, + .unlink = fuse_lib_unlink, + .rmdir = fuse_lib_rmdir, + .symlink = fuse_lib_symlink, + .rename = fuse_lib_rename, + .link = fuse_lib_link, + .create = fuse_lib_create, + .open = fuse_lib_open, + .read = fuse_lib_read, + .write_buf = fuse_lib_write_buf, + .flush = fuse_lib_flush, + .release = fuse_lib_release, + .fsync = fuse_lib_fsync, + .opendir = fuse_lib_opendir, + .readdir = fuse_lib_readdir, + .readdirplus = fuse_lib_readdirplus, + .releasedir = fuse_lib_releasedir, + .fsyncdir = fuse_lib_fsyncdir, + .statfs = fuse_lib_statfs, + .setxattr = fuse_lib_setxattr, + .getxattr = fuse_lib_getxattr, + .listxattr = fuse_lib_listxattr, + .removexattr = fuse_lib_removexattr, + .getlk = fuse_lib_getlk, + .setlk = fuse_lib_setlk, + .flock = fuse_lib_flock, + .bmap = fuse_lib_bmap, + .ioctl = fuse_lib_ioctl, + .poll = fuse_lib_poll, + .fallocate = fuse_lib_fallocate, + .copy_file_range = fuse_lib_copy_file_range, + .lseek = fuse_lib_lseek, +}; + +int fuse_notify_poll(struct fuse_pollhandle *ph) +{ + return fuse_lowlevel_notify_poll(ph); +} + +struct fuse_session *fuse_get_session(struct fuse *f) +{ + return f->se; +} + +static int fuse_session_loop_remember(struct fuse *f) +{ + struct fuse_session *se = f->se; + int res = 0; + struct timespec now; + time_t next_clean; + struct pollfd fds = { + .fd = se->fd, + .events = POLLIN + }; + struct fuse_buf fbuf = { + .mem = NULL, + }; + + curr_time(&now); + next_clean = now.tv_sec; + while (!fuse_session_exited(se)) { + unsigned timeout; + + curr_time(&now); + if (now.tv_sec < next_clean) + timeout = next_clean - now.tv_sec; + else + timeout = 0; + + res = poll(&fds, 1, timeout * 1000); + if (res == -1) { + if (errno == EINTR) + continue; + else + break; + } else if (res > 0) { + res = fuse_session_receive_buf_internal(se, &fbuf, + NULL); + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf_internal(se, &fbuf, NULL); + } else { + timeout = fuse_clean_cache(f); + curr_time(&now); + next_clean = now.tv_sec + timeout; + } + } + + free(fbuf.mem); + fuse_session_reset(se); + return res < 0 ? -1 : 0; +} + +int fuse_loop(struct fuse *f) +{ + if (!f) + return -1; + + if (lru_enabled(f)) + return fuse_session_loop_remember(f); + + return fuse_session_loop(f->se); +} + +FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12") +int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config) +{ + if (f == NULL) + return -1; + + int res = fuse_start_cleanup_thread(f); + if (res) + return -1; + + res = fuse_session_loop_mt_312(fuse_get_session(f), config); + fuse_stop_cleanup_thread(f); + return res; +} + +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1); +FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2") +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1) +{ + struct fuse_loop_config *config = fuse_loop_cfg_create(); + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_convert(config, config_v1); + + int res = fuse_loop_mt_312(f, config); + + fuse_loop_cfg_destroy(config); + + return res; +} + +int fuse_loop_mt_31(struct fuse *f, int clone_fd); +FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0") +int fuse_loop_mt_31(struct fuse *f, int clone_fd) +{ + int err; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_set_clone_fd(config, clone_fd); + + err = fuse_loop_mt_312(f, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + +void fuse_exit(struct fuse *f) +{ + fuse_session_exit(f->se); +} + +struct fuse_context *fuse_get_context(void) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + + if (c) + return &c->ctx; + else + return NULL; +} + +int fuse_getgroups(int size, gid_t list[]) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + if (!c) + return -EINVAL; + + return fuse_req_getgroups(c->req, size, list); +} + +int fuse_interrupted(void) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + + if (c) + return fuse_req_interrupted(c->req); + else + return 0; +} + +int fuse_invalidate_path(struct fuse *f, const char *path) { + fuse_ino_t ino; + int err = lookup_path_in_cache(f, path, &ino); + if (err) { + return err; + } + + return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0); +} + +#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v } + +static const struct fuse_opt fuse_lib_opts[] = { + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_LIB_OPT("debug", debug, 1), + FUSE_LIB_OPT("-d", debug, 1), + FUSE_LIB_OPT("kernel_cache", kernel_cache, 1), + FUSE_LIB_OPT("auto_cache", auto_cache, 1), + FUSE_LIB_OPT("noauto_cache", auto_cache, 0), + FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1), + FUSE_LIB_OPT("umask=", set_mode, 1), + FUSE_LIB_OPT("umask=%o", umask, 0), + FUSE_LIB_OPT("fmask=", set_mode, 1), + FUSE_LIB_OPT("fmask=%o", fmask, 0), + FUSE_LIB_OPT("dmask=", set_mode, 1), + FUSE_LIB_OPT("dmask=%o", dmask, 0), + FUSE_LIB_OPT("uid=", set_uid, 1), + FUSE_LIB_OPT("uid=%d", uid, 0), + FUSE_LIB_OPT("gid=", set_gid, 1), + FUSE_LIB_OPT("gid=%d", gid, 0), + FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0), + FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1), + FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0), + FUSE_LIB_OPT("noforget", remember, -1), + FUSE_LIB_OPT("remember=%u", remember, 0), + FUSE_LIB_OPT("modules=%s", modules, 0), + FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0), + FUSE_OPT_END +}; + +static int fuse_lib_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) arg; (void) outargs; (void) data; (void) key; + + /* Pass through unknown options */ + return 1; +} + + +static const struct fuse_opt fuse_help_opts[] = { + FUSE_LIB_OPT("modules=%s", modules, 1), + FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP), + FUSE_OPT_END +}; + +static void print_module_help(const char *name, + fuse_module_factory_t *fac) +{ + struct fuse_args a = FUSE_ARGS_INIT(0, NULL); + if (fuse_opt_add_arg(&a, "") == -1 || + fuse_opt_add_arg(&a, "-h") == -1) + return; + printf("\nOptions for %s module:\n", name); + (*fac)(&a, NULL); + fuse_opt_free_args(&a); +} + +void fuse_lib_help(struct fuse_args *args) +{ + /* These are not all options, but only the ones that + may be of interest to an end-user */ + printf( +" -o kernel_cache cache files in kernel\n" +" -o [no]auto_cache enable caching based on modification times (off)\n" +" -o no_rofd_flush disable flushing of read-only fd on close (off)\n" +" -o umask=M set file permissions (octal)\n" +" -o fmask=M set file permissions (octal)\n" +" -o dmask=M set dir permissions (octal)\n" +" -o uid=N set file owner\n" +" -o gid=N set file group\n" +" -o entry_timeout=T cache timeout for names (1.0s)\n" +" -o negative_timeout=T cache timeout for deleted names (0.0s)\n" +" -o attr_timeout=T cache timeout for attributes (1.0s)\n" +" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n" +" -o noforget never forget cached inodes\n" +" -o remember=T remember cached inodes for T seconds (0s)\n" +" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n"); + + + /* Print low-level help */ + fuse_lowlevel_help(); + + /* Print help for builtin modules */ + print_module_help("subdir", &fuse_module_subdir_factory); +#ifdef HAVE_ICONV + print_module_help("iconv", &fuse_module_iconv_factory); +#endif + + /* Parse command line options in case we need to + activate more modules */ + struct fuse_config conf = { .modules = NULL }; + if (fuse_opt_parse(args, &conf, fuse_help_opts, + fuse_lib_opt_proc) == -1 + || !conf.modules) + return; + + char *module; + char *next; + struct fuse_module *m; + + // Iterate over all modules + for (module = conf.modules; module; module = next) { + char *p; + for (p = module; *p && *p != ':'; p++); + next = *p ? p + 1 : NULL; + *p = '\0'; + + m = fuse_get_module(module); + if (m) + print_module_help(module, &m->factory); + } +} + +static int fuse_init_intr_signal(int signum, int *installed) +{ + struct sigaction old_sa; + + if (sigaction(signum, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == SIG_DFL) { + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = fuse_intr_sighandler; + sigemptyset(&sa.sa_mask); + + if (sigaction(signum, &sa, NULL) == -1) { + perror("fuse: cannot set interrupt signal handler"); + return -1; + } + *installed = 1; + } + return 0; +} + +static void fuse_restore_intr_signal(int signum) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = SIG_DFL; + sigaction(signum, &sa, NULL); +} + + +static int fuse_push_module(struct fuse *f, const char *module, + struct fuse_args *args) +{ + struct fuse_fs *fs[2] = { f->fs, NULL }; + struct fuse_fs *newfs; + struct fuse_module *m = fuse_get_module(module); + + if (!m) + return -1; + + newfs = m->factory(args, fs); + if (!newfs) { + fuse_put_module(m); + return -1; + } + f->fs = newfs; + return 0; +} + +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *user_data) +{ + struct fuse_fs *fs; + + if (sizeof(struct fuse_operations) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n"); + op_size = sizeof(struct fuse_operations); + } + + fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs)); + if (!fs) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n"); + return NULL; + } + + fs->user_data = user_data; + if (op) + memcpy(&fs->op, op, op_size); + return fs; +} + +static int node_table_init(struct node_table *t) +{ + t->size = NODE_TABLE_MIN_SIZE; + t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size); + if (t->array == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + t->use = 0; + t->split = 0; + + return 0; +} + +static void *fuse_prune_nodes(void *fuse) +{ + struct fuse *f = fuse; + int sleep_time; + +#ifdef HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), "fuse_prune_nodes"); +#endif + + while(1) { + sleep_time = fuse_clean_cache(f); + sleep(sleep_time); + } + return NULL; +} + +int fuse_start_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) + return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f); + + return 0; +} + +void fuse_stop_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) { + pthread_mutex_lock(&f->lock); + pthread_cancel(f->prune_thread); + pthread_mutex_unlock(&f->lock); + pthread_join(f->prune_thread, NULL); + } +} + +/* + * Not supposed to be called directly, but supposed to be called + * through the fuse_new macro + */ +struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) +{ + struct fuse *f; + struct node *root; + struct fuse_fs *fs; + struct fuse_lowlevel_ops llop = fuse_path_ops; + + f = (struct fuse *) calloc(1, sizeof(struct fuse)); + if (f == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n"); + goto out; + } + + f->conf.entry_timeout = 1.0; + f->conf.attr_timeout = 1.0; + f->conf.negative_timeout = 0.0; + f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL; + + /* Parse options */ + if (fuse_opt_parse(args, &f->conf, fuse_lib_opts, + fuse_lib_opt_proc) == -1) + goto out_free; + + pthread_mutex_lock(&fuse_context_lock); + static int builtin_modules_registered = 0; + /* Have the builtin modules already been registered? */ + if (builtin_modules_registered == 0) { + /* If not, register them. */ + fuse_register_module("subdir", fuse_module_subdir_factory, NULL); +#ifdef HAVE_ICONV + fuse_register_module("iconv", fuse_module_iconv_factory, NULL); +#endif + builtin_modules_registered= 1; + } + pthread_mutex_unlock(&fuse_context_lock); + + if (fuse_create_context_key() == -1) + goto out_free; + + fs = fuse_fs_new(op, op_size, user_data); + if (!fs) + goto out_delete_context_key; + + f->fs = fs; + + /* Oh f**k, this is ugly! */ + if (!fs->op.lock) { + llop.getlk = NULL; + llop.setlk = NULL; + } + + f->pagesize = getpagesize(); + init_list_head(&f->partial_slabs); + init_list_head(&f->full_slabs); + init_list_head(&f->lru_table); + + if (f->conf.modules) { + char *module; + char *next; + + for (module = f->conf.modules; module; module = next) { + char *p; + for (p = module; *p && *p != ':'; p++); + next = *p ? p + 1 : NULL; + *p = '\0'; + if (module[0] && + fuse_push_module(f, module, args) == -1) + goto out_free_fs; + } + } + + if (!f->conf.ac_attr_timeout_set) + f->conf.ac_attr_timeout = f->conf.attr_timeout; + +#if defined(__FreeBSD__) || defined(__NetBSD__) + /* + * In FreeBSD, we always use these settings as inode numbers + * are needed to make getcwd(3) work. + */ + f->conf.readdir_ino = 1; +#endif + + /* not declared globally, to restrict usage of this function */ + struct fuse_session *fuse_session_new_versioned( + struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, struct libfuse_version *version, + void *userdata); + f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version, + f); + if (f->se == NULL) + goto out_free_fs; + + if (f->conf.debug) { + fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok); + } + + /* Trace topmost layer by default */ + f->fs->debug = f->conf.debug; + f->ctr = 0; + f->generation = 0; + if (node_table_init(&f->name_table) == -1) + goto out_free_session; + + if (node_table_init(&f->id_table) == -1) + goto out_free_name_table; + + pthread_mutex_init(&f->lock, NULL); + + root = alloc_node(f); + if (root == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + goto out_free_id_table; + } + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(root); + init_list_head(&lnode->lru); + } + + strcpy(root->inline_name, "/"); + root->name = root->inline_name; + root->parent = NULL; + root->nodeid = FUSE_ROOT_ID; + inc_nlookup(root); + hash_id(f, root); + + return f; + +out_free_id_table: + free(f->id_table.array); +out_free_name_table: + free(f->name_table.array); +out_free_session: + fuse_session_destroy(f->se); +out_free_fs: + free(f->fs); + free(f->conf.modules); +out_delete_context_key: + fuse_delete_context_key(); +out_free: + free(f); +out: + return NULL; +} + +/* Emulates 3.0-style fuse_new(), which processes --help */ +FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0") +struct fuse *_fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, + struct libfuse_version *version, + void *user_data) +{ + struct fuse_config conf = {0}; + + const struct fuse_opt opts[] = { + FUSE_LIB_OPT("-h", show_help, 1), + FUSE_LIB_OPT("--help", show_help, 1), + FUSE_OPT_END + }; + + if (fuse_opt_parse(args, &conf, opts, + fuse_lib_opt_proc) == -1) + return NULL; + + if (conf.show_help) { + fuse_lib_help(args); + return NULL; + } else + return _fuse_new_31(args, op, op_size, version, user_data); +} + +/* ABI compat version */ +struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *user_data); +FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1") +struct fuse *fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + /* unknown version */ + struct libfuse_version version = { 0 }; + + return _fuse_new_31(args, op, op_size, &version, user_data); +} + +/* + * ABI compat version + * Emulates 3.0-style fuse_new(), which processes --help + */ +struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *user_data); +FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0") +struct fuse *fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct fuse_config conf = {0}; + + const struct fuse_opt opts[] = { + FUSE_LIB_OPT("-h", show_help, 1), + FUSE_LIB_OPT("--help", show_help, 1), + FUSE_OPT_END + }; + + if (fuse_opt_parse(args, &conf, opts, + fuse_lib_opt_proc) == -1) + return NULL; + + if (conf.show_help) { + fuse_lib_help(args); + return NULL; + } else + return fuse_new_31(args, op, op_size, user_data); +} + + +void fuse_destroy(struct fuse *f) +{ + size_t i; + + if (f->conf.intr && f->intr_installed) + fuse_restore_intr_signal(f->conf.intr_signal); + + if (f->fs) { + fuse_create_context(f); + + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + + for (node = f->id_table.array[i]; node != NULL; + node = node->id_next) { + if (node->is_hidden) { + char *path; + if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) { + fuse_fs_unlink(f->fs, path); + free(path); + } + } + } + } + } + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + struct node *next; + + for (node = f->id_table.array[i]; node != NULL; node = next) { + next = node->id_next; + free_node(f, node); + f->id_table.use--; + } + } + assert(list_empty(&f->partial_slabs)); + assert(list_empty(&f->full_slabs)); + + while (fuse_modules) { + fuse_put_module(fuse_modules); + } + free(f->id_table.array); + free(f->name_table.array); + pthread_mutex_destroy(&f->lock); + fuse_session_destroy(f->se); + free(f->fs); + free(f->conf.modules); + free(f); + fuse_delete_context_key(); +} + +int fuse_mount(struct fuse *f, const char *mountpoint) { + return fuse_session_mount(fuse_get_session(f), mountpoint); +} + + +void fuse_unmount(struct fuse *f) { + fuse_session_unmount(fuse_get_session(f)); +} + +int fuse_version(void) +{ + return FUSE_VERSION; +} + +const char *fuse_pkgversion(void) +{ + return PACKAGE_VERSION; +} diff --git a/lib/fuse_i.h b/lib/fuse_i.h new file mode 100644 index 0000000..48b8294 --- /dev/null +++ b/lib/fuse_i.h @@ -0,0 +1,263 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse.h" +#include "fuse_lowlevel.h" +#include "util.h" + +#include +#include +#include + +#define MIN(a, b) \ +({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ +}) + +struct mount_opts; + +struct fuse_req { + struct fuse_session *se; + uint64_t unique; + _Atomic int ref_cnt; + pthread_mutex_t lock; + struct fuse_ctx ctx; + struct fuse_chan *ch; + int interrupted; + unsigned int ioctl_64bit : 1; + union { + struct { + uint64_t unique; + } i; + struct { + fuse_interrupt_func_t func; + void *data; + } ni; + } u; + struct fuse_req *next; + struct fuse_req *prev; +}; + +struct fuse_notify_req { + uint64_t unique; + void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t, + const void *, const struct fuse_buf *); + struct fuse_notify_req *next; + struct fuse_notify_req *prev; +}; + +struct fuse_session { + char *mountpoint; + volatile int exited; + int fd; + struct fuse_custom_io *io; + struct mount_opts *mo; + int debug; + int deny_others; + struct fuse_lowlevel_ops op; + int got_init; + struct cuse_data *cuse_data; + void *userdata; + uid_t owner; + struct fuse_conn_info conn; + struct fuse_req list; + struct fuse_req interrupts; + pthread_mutex_t lock; + int got_destroy; + pthread_key_t pipe_key; + int broken_splice_nonblock; + uint64_t notify_ctr; + struct fuse_notify_req notify_list; + _Atomic size_t bufsize; + int error; + + /* This is useful if any kind of ABI incompatibility is found at + * a later version, to 'fix' it at run time. + */ + struct libfuse_version version; + + /* true if reading requests from /dev/fuse are handled internally */ + bool buf_reallocable; +}; + +struct fuse_chan { + pthread_mutex_t lock; + int ctr; + int fd; +}; + +/** + * Filesystem module + * + * Filesystem modules are registered with the FUSE_REGISTER_MODULE() + * macro. + * + */ +struct fuse_module { + char *name; + fuse_module_factory_t factory; + struct fuse_module *next; + struct fusemod_so *so; + int ctr; +}; + +/** + * Configuration parameters passed to fuse_session_loop_mt() and + * fuse_loop_mt(). + * + * Internal API to avoid exposing the plain data structure and + * causing compat issues after adding or removing struct members. + * + */ +#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12) +struct fuse_loop_config +{ + /* verififier that a correct struct was was passed. This is especially + * needed, as versions below (3, 12) were using a public struct + * (now called fuse_loop_config_v1), which was hard to extend with + * additional parameters, without risking that file system implementations + * would not have noticed and might either pass uninitialized members + * or even too small structs. + * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0 + * or 1. v2 or even higher version just need to set a value here + * which not conflicting and very unlikely as having been set by + * file system implementation. + */ + int version_id; + + /** + * whether to use separate device fds for each thread + * (may increase performance) + */ + int clone_fd; + /** + * The maximum number of available worker threads before they + * start to get deleted when they become idle. If not + * specified, the default is 10. + * + * Adjusting this has performance implications; a very small number + * of threads in the pool will cause a lot of thread creation and + * deletion overhead and performance may suffer. When set to 0, a new + * thread will be created to service every operation. + * The special value of -1 means that this parameter is disabled. + */ + int max_idle_threads; + + /** + * max number of threads taking and processing kernel requests + * + * As of now threads are created dynamically + */ + unsigned int max_threads; +}; +#endif + +/* ----------------------------------------------------------- * + * Channel interface (when using -o clone_fd) * + * ----------------------------------------------------------- */ + +/** + * Obtain counted reference to the channel + * + * @param ch the channel + * @return the channel + */ +struct fuse_chan *fuse_chan_get(struct fuse_chan *ch); + +/** + * Drop counted reference to a channel + * + * @param ch the channel + */ +void fuse_chan_put(struct fuse_chan *ch); + +struct mount_opts *parse_mount_opts(struct fuse_args *args); +void destroy_mount_opts(struct mount_opts *mo); +void fuse_mount_version(void); +unsigned get_max_read(struct mount_opts *o); +void fuse_kern_unmount(const char *mountpoint, int fd); +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo); + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count); +void fuse_free_req(fuse_req_t req); + +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg); + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg); + +void fuse_buf_free(struct fuse_buf *buf); + +int fuse_session_receive_buf_internal(struct fuse_session *se, + struct fuse_buf *buf, + struct fuse_chan *ch); +void fuse_session_process_buf_internal(struct fuse_session *se, + const struct fuse_buf *buf, + struct fuse_chan *ch); + +struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *private_data); +int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config); +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); + +/** + * Internal verifier for the given config. + * + * @return negative standard error code or 0 on success + */ +int fuse_loop_cfg_verify(struct fuse_loop_config *config); + + +/* + * This can be changed dynamically on recent kernels through the + * /proc/sys/fs/fuse/max_pages_limit interface. + * + * Older kernels will always use the default value. + */ +#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256 +#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32 + +/* room needed in buffer to accommodate header */ +#define FUSE_BUFFER_HEADER_SIZE 0x1000 + +/** + * Get the wanted capability flags, converting from old format if necessary + */ +static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, + uint64_t want_ext_default, + uint32_t want_default) +{ + /* + * Convert want to want_ext if necessary. + * For the high level interface this function might be called + * twice, once from the high level interface and once from the + * low level interface. Both, with different want_ext_default and + * want_default values. In order to suppress a failure for the + * second call, we check if the lower 32 bits of want_ext are + * already set to the value of want. + */ + if (conn->want != want_default && + fuse_lower_32_bits(conn->want_ext) != conn->want) { + if (conn->want_ext != want_ext_default) { + fuse_log(FUSE_LOG_ERR, + "fuse: both 'want' and 'want_ext' are set\n"); + return -EINVAL; + } + + /* high bits from want_ext, low bits from want */ + conn->want_ext = fuse_higher_32_bits(conn->want_ext) | + conn->want; + } + + /* ensure there won't be a second conversion */ + conn->want = fuse_lower_32_bits(conn->want_ext); + + return 0; +} diff --git a/lib/fuse_log.c b/lib/fuse_log.c new file mode 100644 index 0000000..c1d16c1 --- /dev/null +++ b/lib/fuse_log.c @@ -0,0 +1,96 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2019 Red Hat, Inc. + + Logging API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_log.h" + +#include +#include +#include +#include + +#define MAX_SYSLOG_LINE_LEN 512 + +static bool to_syslog = false; + +static void default_log_func(__attribute__((unused)) enum fuse_log_level level, + const char *fmt, va_list ap) +{ + if (to_syslog) { + int sys_log_level = LOG_ERR; + + /* + * with glibc fuse_log_level has identical values as + * syslog levels, but we also support BSD - better we convert to + * be sure. + */ + switch (level) { + case FUSE_LOG_DEBUG: + sys_log_level = LOG_DEBUG; + break; + case FUSE_LOG_INFO: + sys_log_level = LOG_INFO; + break; + case FUSE_LOG_NOTICE: + sys_log_level = LOG_NOTICE; + break; + case FUSE_LOG_WARNING: + sys_log_level = LOG_WARNING; + break; + case FUSE_LOG_ERR: + sys_log_level = LOG_ERR; + break; + case FUSE_LOG_CRIT: + sys_log_level = LOG_CRIT; + break; + case FUSE_LOG_ALERT: + sys_log_level = LOG_ALERT; + break; + case FUSE_LOG_EMERG: + sys_log_level = LOG_EMERG; + } + + char log[MAX_SYSLOG_LINE_LEN]; + vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap); + syslog(sys_log_level, "%s", log); + } else { + vfprintf(stderr, fmt, ap); + } +} + +static fuse_log_func_t log_func = default_log_func; + +void fuse_set_log_func(fuse_log_func_t func) +{ + if (!func) + func = default_log_func; + + log_func = func; +} + +void fuse_log(enum fuse_log_level level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_func(level, fmt, ap); + va_end(ap); +} + +void fuse_log_enable_syslog(const char *ident, int option, int facility) +{ + to_syslog = true; + + openlog(ident, option, facility); +} + +void fuse_log_close_syslog(void) +{ + closelog(); +} diff --git a/lib/fuse_loop.c b/lib/fuse_loop.c new file mode 100644 index 0000000..410f43f --- /dev/null +++ b/lib/fuse_loop.c @@ -0,0 +1,46 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the single-threaded FUSE session loop. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" + +#include +#include +#include + +int fuse_session_loop(struct fuse_session *se) +{ + int res = 0; + struct fuse_buf fbuf = { + .mem = NULL, + }; + + while (!fuse_session_exited(se)) { + res = fuse_session_receive_buf_internal(se, &fbuf, NULL); + + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf(se, &fbuf); + } + + fuse_buf_free(&fbuf); + if(res > 0) + /* No error, just the length of the most recently read + request */ + res = 0; + if(se->error != 0) + res = se->error; + fuse_session_reset(se); + return res; +} diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c new file mode 100644 index 0000000..d6be998 --- /dev/null +++ b/lib/fuse_loop_mt.c @@ -0,0 +1,531 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the multi-threaded FUSE session loop. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_misc.h" +#include "fuse_kernel.h" +#include "fuse_i.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Environment var controlling the thread stack size */ +#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK" + +#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2 +#define FUSE_LOOP_MT_DEF_CLONE_FD 0 +#define FUSE_LOOP_MT_DEF_MAX_THREADS 10 +#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled + * by default */ + +/* an arbitrary large value that cannot be valid */ +#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000) + +struct fuse_worker { + struct fuse_worker *prev; + struct fuse_worker *next; + pthread_t thread_id; + + // We need to include fuse_buf so that we can properly free + // it when a thread is terminated by pthread_cancel(). + struct fuse_buf fbuf; + struct fuse_chan *ch; + struct fuse_mt *mt; +}; + +struct fuse_mt { + pthread_mutex_t lock; + int numworker; + int numavail; + struct fuse_session *se; + struct fuse_worker main; + sem_t finish; + int exit; + int error; + int clone_fd; + int max_idle; + int max_threads; +}; + +static struct fuse_chan *fuse_chan_new(int fd) +{ + struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch)); + if (ch == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n"); + return NULL; + } + + memset(ch, 0, sizeof(*ch)); + ch->fd = fd; + ch->ctr = 1; + pthread_mutex_init(&ch->lock, NULL); + + return ch; +} + +struct fuse_chan *fuse_chan_get(struct fuse_chan *ch) +{ + assert(ch->ctr > 0); + pthread_mutex_lock(&ch->lock); + ch->ctr++; + pthread_mutex_unlock(&ch->lock); + + return ch; +} + +void fuse_chan_put(struct fuse_chan *ch) +{ + if (ch == NULL) + return; + pthread_mutex_lock(&ch->lock); + ch->ctr--; + if (!ch->ctr) { + pthread_mutex_unlock(&ch->lock); + close(ch->fd); + pthread_mutex_destroy(&ch->lock); + free(ch); + } else + pthread_mutex_unlock(&ch->lock); +} + +static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next) +{ + struct fuse_worker *prev = next->prev; + w->next = next; + w->prev = prev; + prev->next = w; + next->prev = w; +} + +static void list_del_worker(struct fuse_worker *w) +{ + struct fuse_worker *prev = w->prev; + struct fuse_worker *next = w->next; + prev->next = next; + next->prev = prev; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt); + +static void *fuse_do_work(void *data) +{ + struct fuse_worker *w = (struct fuse_worker *) data; + struct fuse_mt *mt = w->mt; + +#ifdef HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), "fuse_worker"); +#endif + + while (!fuse_session_exited(mt->se)) { + int isforget = 0; + int res; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + res = fuse_session_receive_buf_internal(mt->se, &w->fbuf, + w->ch); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + if (res == -EINTR) + continue; + if (res <= 0) { + if (res < 0) { + fuse_session_exit(mt->se); + mt->error = res; + } + break; + } + + pthread_mutex_lock(&mt->lock); + if (mt->exit) { + pthread_mutex_unlock(&mt->lock); + return NULL; + } + + /* + * This disgusting hack is needed so that zillions of threads + * are not created on a burst of FORGET messages + */ + if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) { + struct fuse_in_header *in = w->fbuf.mem; + + if (in->opcode == FUSE_FORGET || + in->opcode == FUSE_BATCH_FORGET) + isforget = 1; + } + + if (!isforget) + mt->numavail--; + if (mt->numavail == 0 && mt->numworker < mt->max_threads) + fuse_loop_start_thread(mt); + pthread_mutex_unlock(&mt->lock); + + fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch); + + pthread_mutex_lock(&mt->lock); + if (!isforget) + mt->numavail++; + + /* creating and destroying threads is rather expensive - and there is + * not much gain from destroying existing threads. It is therefore + * discouraged to set max_idle to anything else than -1. If there + * is indeed a good reason to destruct threads it should be done + * delayed, a moving average might be useful for that. + */ + if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) { + if (mt->exit) { + pthread_mutex_unlock(&mt->lock); + return NULL; + } + list_del_worker(w); + mt->numavail--; + mt->numworker--; + pthread_mutex_unlock(&mt->lock); + + pthread_detach(w->thread_id); + fuse_buf_free(&w->fbuf); + fuse_chan_put(w->ch); + free(w); + return NULL; + } + pthread_mutex_unlock(&mt->lock); + } + + sem_post(&mt->finish); + + return NULL; +} + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg) +{ + sigset_t oldset; + sigset_t newset; + int res; + pthread_attr_t attr; + char *stack_size; + + /* Override default stack size + * XXX: This should ideally be a parameter option. It is rather + * well hidden here. + */ + pthread_attr_init(&attr); + stack_size = getenv(ENVNAME_THREAD_STACK); + if (stack_size) { + long size; + + res = libfuse_strtol(stack_size, &size); + if (res) + fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n", + stack_size); + else if (pthread_attr_setstacksize(&attr, size)) + fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n", + size); + } + + /* Disallow signal reception in worker threads */ + sigemptyset(&newset); + sigaddset(&newset, SIGTERM); + sigaddset(&newset, SIGINT); + sigaddset(&newset, SIGHUP); + sigaddset(&newset, SIGQUIT); + pthread_sigmask(SIG_BLOCK, &newset, &oldset); + res = pthread_create(thread_id, &attr, func, arg); + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + pthread_attr_destroy(&attr); + if (res != 0) { + fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n", + strerror(res)); + return -1; + } + + return 0; +} + +static int fuse_clone_chan_fd_default(struct fuse_session *se) +{ + int res; + int clonefd; + uint32_t masterfd; + const char *devname = "/dev/fuse"; + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + clonefd = open(devname, O_RDWR | O_CLOEXEC); + if (clonefd == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname, + strerror(errno)); + return -1; + } +#ifndef O_CLOEXEC + fcntl(clonefd, F_SETFD, FD_CLOEXEC); +#endif + + masterfd = se->fd; + res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n", + strerror(errno)); + close(clonefd); + return -1; + } + return clonefd; +} + +static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt) +{ + int clonefd; + struct fuse_session *se = mt->se; + struct fuse_chan *newch; + + if (se->io != NULL) { + if (se->io->clone_fd != NULL) + clonefd = se->io->clone_fd(se->fd); + else + return NULL; + } else { + clonefd = fuse_clone_chan_fd_default(se); + } + if (clonefd < 0) + return NULL; + + newch = fuse_chan_new(clonefd); + if (newch == NULL) + close(clonefd); + + return newch; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt) +{ + int res; + + struct fuse_worker *w = malloc(sizeof(struct fuse_worker)); + if (!w) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n"); + return -1; + } + memset(w, 0, sizeof(struct fuse_worker)); + w->fbuf.mem = NULL; + w->mt = mt; + + w->ch = NULL; + if (mt->clone_fd) { + w->ch = fuse_clone_chan(mt); + if(!w->ch) { + /* Don't attempt this again */ + fuse_log(FUSE_LOG_ERR, "fuse: trying to continue " + "without -o clone_fd.\n"); + mt->clone_fd = 0; + } + } + + res = fuse_start_thread(&w->thread_id, fuse_do_work, w); + if (res == -1) { + fuse_chan_put(w->ch); + free(w); + return -1; + } + list_add_worker(w, &mt->main); + mt->numavail ++; + mt->numworker ++; + + return 0; +} + +static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w) +{ + pthread_join(w->thread_id, NULL); + pthread_mutex_lock(&mt->lock); + list_del_worker(w); + pthread_mutex_unlock(&mt->lock); + fuse_buf_free(&w->fbuf); + fuse_chan_put(w->ch); + free(w); +} + +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); +FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12") +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config) +{ +int err; + struct fuse_mt mt; + struct fuse_worker *w; + int created_config = 0; + + if (config) { + err = fuse_loop_cfg_verify(config); + if (err) + return err; + } else { + /* The caller does not care about parameters - use the default */ + config = fuse_loop_cfg_create(); + created_config = 1; + } + + + memset(&mt, 0, sizeof(struct fuse_mt)); + mt.se = se; + mt.clone_fd = config->clone_fd; + mt.error = 0; + mt.numworker = 0; + mt.numavail = 0; + mt.max_idle = config->max_idle_threads; + mt.max_threads = config->max_threads; + mt.main.thread_id = pthread_self(); + mt.main.prev = mt.main.next = &mt.main; + sem_init(&mt.finish, 0, 0); + pthread_mutex_init(&mt.lock, NULL); + + pthread_mutex_lock(&mt.lock); + err = fuse_loop_start_thread(&mt); + pthread_mutex_unlock(&mt.lock); + if (!err) { + /* sem_wait() is interruptible */ + while (!fuse_session_exited(se)) + sem_wait(&mt.finish); + + pthread_mutex_lock(&mt.lock); + for (w = mt.main.next; w != &mt.main; w = w->next) + pthread_cancel(w->thread_id); + mt.exit = 1; + pthread_mutex_unlock(&mt.lock); + + while (mt.main.next != &mt.main) + fuse_join_worker(&mt, mt.main.next); + + err = mt.error; + } + + pthread_mutex_destroy(&mt.lock); + sem_destroy(&mt.finish); + if(se->error != 0) + err = se->error; + fuse_session_reset(se); + + if (created_config) { + fuse_loop_cfg_destroy(config); + config = NULL; + } + + return err; +} + +int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1); +FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2") +int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1) +{ + int err; + struct fuse_loop_config *config = NULL; + + if (config_v1 != NULL) { + /* convert the given v1 config */ + config = fuse_loop_cfg_create(); + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_convert(config, config_v1); + } + + err = fuse_session_loop_mt_312(se, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + + +int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd); +FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0") +int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd) +{ + int err; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + if (clone_fd > 0) + fuse_loop_cfg_set_clone_fd(config, clone_fd); + err = fuse_session_loop_mt_312(se, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + +struct fuse_loop_config *fuse_loop_cfg_create(void) +{ + struct fuse_loop_config *config = calloc(1, sizeof(*config)); + if (config == NULL) + return NULL; + + config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER; + config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS; + config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS; + config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD; + + return config; +} + +void fuse_loop_cfg_destroy(struct fuse_loop_config *config) +{ + free(config); +} + +int fuse_loop_cfg_verify(struct fuse_loop_config *config) +{ + if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER) + return -EINVAL; + + return 0; +} + +void fuse_loop_cfg_convert(struct fuse_loop_config *config, + struct fuse_loop_config_v1 *v1_conf) +{ + fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads); + + fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd); +} + +void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config, + unsigned int value) +{ + if (value > FUSE_LOOP_MT_MAX_THREADS) { + if (value != UINT_MAX) + fuse_log(FUSE_LOG_ERR, + "Ignoring invalid max threads value " + "%u > max (%u).\n", value, + FUSE_LOOP_MT_MAX_THREADS); + return; + } + config->max_idle_threads = value; +} + +void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config, + unsigned int value) +{ + config->max_threads = value; +} + +void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config, + unsigned int value) +{ + config->clone_fd = value; +} + diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c new file mode 100644 index 0000000..9ebaaf0 --- /dev/null +++ b/lib/fuse_lowlevel.c @@ -0,0 +1,3574 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of (most of) the low-level FUSE API. The session loop + functions are implemented in separate files. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_kernel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "mount_util.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + + +#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) +#define OFFSET_MAX 0x7fffffffffffffffLL + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct fuse_pollhandle { + uint64_t kh; + struct fuse_session *se; +}; + +static size_t pagesize; + +static __attribute__((constructor)) void fuse_ll_init_pagesize(void) +{ + pagesize = getpagesize(); +} + +static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) +{ + attr->ino = stbuf->st_ino; + attr->mode = stbuf->st_mode; + attr->nlink = stbuf->st_nlink; + attr->uid = stbuf->st_uid; + attr->gid = stbuf->st_gid; + attr->rdev = stbuf->st_rdev; + attr->size = stbuf->st_size; + attr->blksize = stbuf->st_blksize; + attr->blocks = stbuf->st_blocks; + attr->atime = stbuf->st_atime; + attr->mtime = stbuf->st_mtime; + attr->ctime = stbuf->st_ctime; + attr->atimensec = ST_ATIM_NSEC(stbuf); + attr->mtimensec = ST_MTIM_NSEC(stbuf); + attr->ctimensec = ST_CTIM_NSEC(stbuf); +} + +static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf) +{ + stbuf->st_mode = attr->mode; + stbuf->st_uid = attr->uid; + stbuf->st_gid = attr->gid; + stbuf->st_size = attr->size; + stbuf->st_atime = attr->atime; + stbuf->st_mtime = attr->mtime; + stbuf->st_ctime = attr->ctime; + ST_ATIM_NSEC_SET(stbuf, attr->atimensec); + ST_MTIM_NSEC_SET(stbuf, attr->mtimensec); + ST_CTIM_NSEC_SET(stbuf, attr->ctimensec); +} + +static size_t iov_length(const struct iovec *iov, size_t count) +{ + size_t seg; + size_t ret = 0; + + for (seg = 0; seg < count; seg++) + ret += iov[seg].iov_len; + return ret; +} + +static void list_init_req(struct fuse_req *req) +{ + req->next = req; + req->prev = req; +} + +static void list_del_req(struct fuse_req *req) +{ + struct fuse_req *prev = req->prev; + struct fuse_req *next = req->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_req(struct fuse_req *req, struct fuse_req *next) +{ + struct fuse_req *prev = next->prev; + req->next = next; + req->prev = prev; + prev->next = req; + next->prev = req; +} + +static void destroy_req(fuse_req_t req) +{ + assert(req->ch == NULL); + pthread_mutex_destroy(&req->lock); + free(req); +} + +void fuse_free_req(fuse_req_t req) +{ + int ctr; + struct fuse_session *se = req->se; + + if (se->conn.no_interrupt) { + ctr = --req->ref_cnt; + fuse_chan_put(req->ch); + req->ch = NULL; + } else { + pthread_mutex_lock(&se->lock); + req->u.ni.func = NULL; + req->u.ni.data = NULL; + list_del_req(req); + ctr = --req->ref_cnt; + fuse_chan_put(req->ch); + req->ch = NULL; + pthread_mutex_unlock(&se->lock); + } + if (!ctr) + destroy_req(req); +} + +static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se) +{ + struct fuse_req *req; + + req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req)); + if (req == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n"); + } else { + req->se = se; + req->ref_cnt = 1; + list_init_req(req); + pthread_mutex_init(&req->lock, NULL); + } + + return req; +} + +/* Send data. If *ch* is NULL, send via session master fd */ +static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int count) +{ + struct fuse_out_header *out = iov[0].iov_base; + + assert(se != NULL); + out->len = iov_length(iov, count); + if (se->debug) { + if (out->unique == 0) { + fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n", + out->error, out->len); + } else if (out->error) { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, error: %i (%s), outsize: %i\n", + (unsigned long long) out->unique, out->error, + strerror(-out->error), out->len); + } else { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, success, outsize: %i\n", + (unsigned long long) out->unique, out->len); + } + } + + ssize_t res; + if (se->io != NULL) + /* se->io->writev is never NULL if se->io is not NULL as + specified by fuse_session_custom_io()*/ + res = se->io->writev(ch ? ch->fd : se->fd, iov, count, + se->userdata); + else + res = writev(ch ? ch->fd : se->fd, iov, count); + + int err = errno; + + if (res == -1) { + /* ENOENT means the operation was interrupted */ + if (!fuse_session_exited(se) && err != ENOENT) + perror("fuse: writing device"); + return -err; + } + + return 0; +} + + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + struct fuse_out_header out; + +#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32 + const char *str = strerrordesc_np(error * -1); + if ((str == NULL && error != 0) || error > 0) { +#else + if (error <= -1000 || error > 0) { +#endif + fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error); + error = -ERANGE; + } + + out.unique = req->unique; + out.error = error; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(req->se, req->ch, iov, count); +} + +static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + int res; + + res = fuse_send_reply_iov_nofree(req, error, iov, count); + fuse_free_req(req); + return res; +} + +static int send_reply(fuse_req_t req, int error, const void *arg, + size_t argsize) +{ + struct iovec iov[2]; + int count = 1; + if (argsize) { + iov[1].iov_base = (void *) arg; + iov[1].iov_len = argsize; + count++; + } + return send_reply_iov(req, error, iov, count); +} + +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count) +{ + int res; + struct iovec *padded_iov; + + padded_iov = malloc((count + 1) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memcpy(padded_iov + 1, iov, count * sizeof(struct iovec)); + count++; + + res = send_reply_iov(req, 0, padded_iov, count); + free(padded_iov); + + return res; +} + + +/* `buf` is allowed to be empty so that the proper size may be + allocated by the caller */ +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, off_t off) +{ + (void)req; + size_t namelen; + size_t entlen; + size_t entlen_padded; + struct fuse_dirent *dirent; + + namelen = strlen(name); + entlen = FUSE_NAME_OFFSET + namelen; + entlen_padded = FUSE_DIRENT_ALIGN(entlen); + + if ((buf == NULL) || (entlen_padded > bufsize)) + return entlen_padded; + + dirent = (struct fuse_dirent*) buf; + dirent->ino = stbuf->st_ino; + dirent->off = off; + dirent->namelen = namelen; + dirent->type = (stbuf->st_mode & S_IFMT) >> 12; + memcpy(dirent->name, name, namelen); + memset(dirent->name + namelen, 0, entlen_padded - entlen); + + return entlen_padded; +} + +static void convert_statfs(const struct statvfs *stbuf, + struct fuse_kstatfs *kstatfs) +{ + kstatfs->bsize = stbuf->f_bsize; + kstatfs->frsize = stbuf->f_frsize; + kstatfs->blocks = stbuf->f_blocks; + kstatfs->bfree = stbuf->f_bfree; + kstatfs->bavail = stbuf->f_bavail; + kstatfs->files = stbuf->f_files; + kstatfs->ffree = stbuf->f_ffree; + kstatfs->namelen = stbuf->f_namemax; +} + +static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize) +{ + return send_reply(req, 0, arg, argsize); +} + +int fuse_reply_err(fuse_req_t req, int err) +{ + return send_reply(req, -err, NULL, 0); +} + +void fuse_reply_none(fuse_req_t req) +{ + fuse_free_req(req); +} + +static unsigned long calc_timeout_sec(double t) +{ + if (t > (double) ULONG_MAX) + return ULONG_MAX; + else if (t < 0.0) + return 0; + else + return (unsigned long) t; +} + +static unsigned int calc_timeout_nsec(double t) +{ + double f = t - (double) calc_timeout_sec(t); + if (f < 0.0) + return 0; + else if (f >= 0.999999999) + return 999999999; + else + return (unsigned int) (f * 1.0e9); +} + +static void fill_entry(struct fuse_entry_out *arg, + const struct fuse_entry_param *e) +{ + arg->nodeid = e->ino; + arg->generation = e->generation; + arg->entry_valid = calc_timeout_sec(e->entry_timeout); + arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout); + arg->attr_valid = calc_timeout_sec(e->attr_timeout); + arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout); + convert_stat(&e->attr, &arg->attr); +} + +/* `buf` is allowed to be empty so that the proper size may be + allocated by the caller */ +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off) +{ + (void)req; + size_t namelen; + size_t entlen; + size_t entlen_padded; + + namelen = strlen(name); + entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen; + entlen_padded = FUSE_DIRENT_ALIGN(entlen); + if ((buf == NULL) || (entlen_padded > bufsize)) + return entlen_padded; + + struct fuse_direntplus *dp = (struct fuse_direntplus *) buf; + memset(&dp->entry_out, 0, sizeof(dp->entry_out)); + fill_entry(&dp->entry_out, e); + + struct fuse_dirent *dirent = &dp->dirent; + dirent->ino = e->attr.st_ino; + dirent->off = off; + dirent->namelen = namelen; + dirent->type = (e->attr.st_mode & S_IFMT) >> 12; + memcpy(dirent->name, name, namelen); + memset(dirent->name + namelen, 0, entlen_padded - entlen); + + return entlen_padded; +} + +static void fill_open(struct fuse_open_out *arg, + const struct fuse_file_info *f) +{ + arg->fh = f->fh; + if (f->backing_id > 0) { + arg->backing_id = f->backing_id; + arg->open_flags |= FOPEN_PASSTHROUGH; + } + if (f->direct_io) + arg->open_flags |= FOPEN_DIRECT_IO; + if (f->keep_cache) + arg->open_flags |= FOPEN_KEEP_CACHE; + if (f->cache_readdir) + arg->open_flags |= FOPEN_CACHE_DIR; + if (f->nonseekable) + arg->open_flags |= FOPEN_NONSEEKABLE; + if (f->noflush) + arg->open_flags |= FOPEN_NOFLUSH; + if (f->parallel_direct_writes) + arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES; +} + +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) +{ + struct fuse_entry_out arg; + size_t size = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg); + + /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant + negative entry */ + if (!e->ino && req->se->conn.proto_minor < 4) + return fuse_reply_err(req, ENOENT); + + memset(&arg, 0, sizeof(arg)); + fill_entry(&arg, e); + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *f) +{ + alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)]; + size_t entrysize = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out); + struct fuse_entry_out *earg = (struct fuse_entry_out *) buf; + struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize); + + memset(buf, 0, sizeof(buf)); + fill_entry(earg, e); + fill_open(oarg, f); + return send_reply_ok(req, buf, + entrysize + sizeof(struct fuse_open_out)); +} + +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout) +{ + struct fuse_attr_out arg; + size_t size = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + convert_stat(attr, &arg.attr); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_readlink(fuse_req_t req, const char *linkname) +{ + return send_reply_ok(req, linkname, strlen(linkname)); +} + +int fuse_passthrough_open(fuse_req_t req, int fd) +{ + struct fuse_backing_map map = { .fd = fd }; + int ret; + + ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map); + if (ret <= 0) { + fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno)); + return 0; + } + + return ret; +} + +int fuse_passthrough_close(fuse_req_t req, int backing_id) +{ + int ret; + + ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id); + if (ret < 0) + fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno)); + + return ret; +} + +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f) +{ + struct fuse_open_out arg; + + memset(&arg, 0, sizeof(arg)); + fill_open(&arg, f); + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_write(fuse_req_t req, size_t count) +{ + struct fuse_write_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size) +{ + return send_reply_ok(req, buf, size); +} + +static int fuse_send_data_iov_fallback(struct fuse_session *se, + struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, + size_t len) +{ + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + int res; + + /* Optimize common case */ + if (buf->count == 1 && buf->idx == 0 && buf->off == 0 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + /* FIXME: also avoid memory copy if there are multiple buffers + but none of them contain an fd */ + + iov[iov_count].iov_base = buf->buf[0].mem; + iov[iov_count].iov_len = len; + iov_count++; + return fuse_send_msg(se, ch, iov, iov_count); + } + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + return res; + + mem_buf.buf[0].mem = mbuf; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res < 0) { + free(mbuf); + return -res; + } + len = res; + + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(se, ch, iov, iov_count); + free(mbuf); + + return res; +} + +struct fuse_ll_pipe { + size_t size; + int can_grow; + int pipe[2]; +}; + +static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp) +{ + close(llp->pipe[0]); + close(llp->pipe[1]); + free(llp); +} + +#ifdef HAVE_SPLICE +#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC) +static int fuse_pipe(int fds[2]) +{ + int rv = pipe(fds); + + if (rv == -1) + return rv; + + if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 || + fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 || + fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) { + close(fds[0]); + close(fds[1]); + rv = -1; + } + return rv; +} +#else +static int fuse_pipe(int fds[2]) +{ + return pipe2(fds, O_CLOEXEC | O_NONBLOCK); +} +#endif + +static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key); + if (llp == NULL) { + int res; + + llp = malloc(sizeof(struct fuse_ll_pipe)); + if (llp == NULL) + return NULL; + + res = fuse_pipe(llp->pipe); + if (res == -1) { + free(llp); + return NULL; + } + + /* + *the default size is 16 pages on linux + */ + llp->size = pagesize * 16; + llp->can_grow = 1; + + pthread_setspecific(se->pipe_key, llp); + } + + return llp; +} +#endif + +static void fuse_ll_clear_pipe(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key); + if (llp) { + pthread_setspecific(se->pipe_key, NULL); + fuse_ll_pipe_free(llp); + } +} + +#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE) +static int read_back(int fd, char *buf, size_t len) +{ + int res; + + res = read(fd, buf, len); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno)); + return -EIO; + } + if (res != len) { + fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len); + return -EIO; + } + return 0; +} + +static int grow_pipe_to_max(int pipefd) +{ + int res; + long max; + long maxfd; + char buf[32]; + + maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY); + if (maxfd < 0) + return -errno; + + res = read(maxfd, buf, sizeof(buf) - 1); + if (res < 0) { + int saved_errno; + + saved_errno = errno; + close(maxfd); + return -saved_errno; + } + close(maxfd); + buf[res] = '\0'; + + res = libfuse_strtol(buf, &max); + if (res) + return res; + res = fcntl(pipefd, F_SETPIPE_SZ, max); + if (res < 0) + return -errno; + return max; +} + +static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, unsigned int flags) +{ + int res; + size_t len = fuse_buf_size(buf); + struct fuse_out_header *out = iov[0].iov_base; + struct fuse_ll_pipe *llp; + int splice_flags; + size_t pipesize; + size_t total_buf_size; + size_t idx; + size_t headerlen; + struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len); + + if (se->broken_splice_nonblock) + goto fallback; + + if (flags & FUSE_BUF_NO_SPLICE) + goto fallback; + + total_buf_size = 0; + for (idx = buf->idx; idx < buf->count; idx++) { + total_buf_size += buf->buf[idx].size; + if (idx == buf->idx) + total_buf_size -= buf->off; + } + if (total_buf_size < 2 * pagesize) + goto fallback; + + if (se->conn.proto_minor < 14 || + !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE)) + goto fallback; + + llp = fuse_ll_get_pipe(se); + if (llp == NULL) + goto fallback; + + + headerlen = iov_length(iov, iov_count); + + out->len = headerlen + len; + + /* + * Heuristic for the required pipe size, does not work if the + * source contains less than page size fragments + */ + pipesize = pagesize * (iov_count + buf->count + 1) + out->len; + + if (llp->size < pipesize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize); + if (res == -1) { + res = grow_pipe_to_max(llp->pipe[0]); + if (res > 0) + llp->size = res; + llp->can_grow = 0; + goto fallback; + } + llp->size = res; + } + if (llp->size < pipesize) + goto fallback; + } + + + res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK); + if (res == -1) + goto fallback; + + if (res != headerlen) { + res = -EIO; + fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res, + headerlen); + goto clear_pipe; + } + + pipe_buf.buf[0].flags = FUSE_BUF_IS_FD; + pipe_buf.buf[0].fd = llp->pipe[1]; + + res = fuse_buf_copy(&pipe_buf, buf, + FUSE_BUF_FORCE_SPLICE | FUSE_BUF_SPLICE_NONBLOCK); + if (res < 0) { + if (res == -EAGAIN || res == -EINVAL) { + /* + * Should only get EAGAIN on kernels with + * broken SPLICE_F_NONBLOCK support (<= + * 2.6.35) where this error or a short read is + * returned even if the pipe itself is not + * full + * + * EINVAL might mean that splice can't handle + * this combination of input and output. + */ + if (res == -EAGAIN) + se->broken_splice_nonblock = 1; + + pthread_setspecific(se->pipe_key, NULL); + fuse_ll_pipe_free(llp); + goto fallback; + } + res = -res; + goto clear_pipe; + } + + if (res != 0 && res < len) { + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + size_t now_len = res; + /* + * For regular files a short count is either + * 1) due to EOF, or + * 2) because of broken SPLICE_F_NONBLOCK (see above) + * + * For other inputs it's possible that we overflowed + * the pipe because of small buffer fragments. + */ + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + goto clear_pipe; + + mem_buf.buf[0].mem = mbuf; + mem_buf.off = now_len; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res > 0) { + char *tmpbuf; + size_t extra_len = res; + /* + * Trickiest case: got more data. Need to get + * back the data from the pipe and then fall + * back to regular write. + */ + tmpbuf = malloc(headerlen); + if (tmpbuf == NULL) { + free(mbuf); + res = ENOMEM; + goto clear_pipe; + } + res = read_back(llp->pipe[0], tmpbuf, headerlen); + free(tmpbuf); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + res = read_back(llp->pipe[0], mbuf, now_len); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + len = now_len + extra_len; + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(se, ch, iov, iov_count); + free(mbuf); + return res; + } + free(mbuf); + res = now_len; + } + len = res; + out->len = headerlen + len; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, success, outsize: %i (splice)\n", + (unsigned long long) out->unique, out->len); + } + + splice_flags = 0; + if ((flags & FUSE_BUF_SPLICE_MOVE) && + (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE)) + splice_flags |= SPLICE_F_MOVE; + + if (se->io != NULL && se->io->splice_send != NULL) { + res = se->io->splice_send(llp->pipe[0], NULL, + ch ? ch->fd : se->fd, NULL, out->len, + splice_flags, se->userdata); + } else { + res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL, + out->len, splice_flags); + } + if (res == -1) { + res = -errno; + perror("fuse: splice from pipe"); + goto clear_pipe; + } + if (res != out->len) { + res = -EIO; + fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n", + res, out->len); + goto clear_pipe; + } + return 0; + +clear_pipe: + fuse_ll_clear_pipe(se); + return res; + +fallback: + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len); +} +#else +static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, unsigned int flags) +{ + size_t len = fuse_buf_size(buf); + (void) flags; + + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len); +} +#endif + +int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct iovec iov[2]; + struct fuse_out_header out; + int res; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + out.unique = req->unique; + out.error = 0; + + res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags); + if (res <= 0) { + fuse_free_req(req); + return res; + } else { + return fuse_reply_err(req, res); + } +} + +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf) +{ + struct fuse_statfs_out arg; + size_t size = req->se->conn.proto_minor < 4 ? + FUSE_COMPAT_STATFS_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + convert_statfs(stbuf, &arg.st); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_xattr(fuse_req_t req, size_t count) +{ + struct fuse_getxattr_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_lock(fuse_req_t req, const struct flock *lock) +{ + struct fuse_lk_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.lk.type = lock->l_type; + if (lock->l_type != F_UNLCK) { + arg.lk.start = lock->l_start; + if (lock->l_len == 0) + arg.lk.end = OFFSET_MAX; + else + arg.lk.end = lock->l_start + lock->l_len - 1; + } + arg.lk.pid = lock->l_pid; + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_bmap(fuse_req_t req, uint64_t idx) +{ + struct fuse_bmap_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.block = idx; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov, + size_t count) +{ + struct fuse_ioctl_iovec *fiov; + size_t i; + + fiov = malloc(sizeof(fiov[0]) * count); + if (!fiov) + return NULL; + + for (i = 0; i < count; i++) { + fiov[i].base = (uintptr_t) iov[i].iov_base; + fiov[i].len = iov[i].iov_len; + } + + return fiov; +} + +int fuse_reply_ioctl_retry(fuse_req_t req, + const struct iovec *in_iov, size_t in_count, + const struct iovec *out_iov, size_t out_count) +{ + struct fuse_ioctl_out arg; + struct fuse_ioctl_iovec *in_fiov = NULL; + struct fuse_ioctl_iovec *out_fiov = NULL; + struct iovec iov[4]; + size_t count = 1; + int res; + + memset(&arg, 0, sizeof(arg)); + arg.flags |= FUSE_IOCTL_RETRY; + arg.in_iovs = in_count; + arg.out_iovs = out_count; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (req->se->conn.proto_minor < 16) { + if (in_count) { + iov[count].iov_base = (void *)in_iov; + iov[count].iov_len = sizeof(in_iov[0]) * in_count; + count++; + } + + if (out_count) { + iov[count].iov_base = (void *)out_iov; + iov[count].iov_len = sizeof(out_iov[0]) * out_count; + count++; + } + } else { + /* Can't handle non-compat 64bit ioctls on 32bit */ + if (sizeof(void *) == 4 && req->ioctl_64bit) { + res = fuse_reply_err(req, EINVAL); + goto out; + } + + if (in_count) { + in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count); + if (!in_fiov) + goto enomem; + + iov[count].iov_base = (void *)in_fiov; + iov[count].iov_len = sizeof(in_fiov[0]) * in_count; + count++; + } + if (out_count) { + out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count); + if (!out_fiov) + goto enomem; + + iov[count].iov_base = (void *)out_fiov; + iov[count].iov_len = sizeof(out_fiov[0]) * out_count; + count++; + } + } + + res = send_reply_iov(req, 0, iov, count); +out: + free(in_fiov); + free(out_fiov); + + return res; + +enomem: + res = fuse_reply_err(req, ENOMEM); + goto out; +} + +int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size) +{ + struct fuse_ioctl_out arg; + struct iovec iov[3]; + size_t count = 1; + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (size) { + iov[count].iov_base = (char *) buf; + iov[count].iov_len = size; + count++; + } + + return send_reply_iov(req, 0, iov, count); +} + +int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, + int count) +{ + struct iovec *padded_iov; + struct fuse_ioctl_out arg; + int res; + + padded_iov = malloc((count + 2) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + padded_iov[1].iov_base = &arg; + padded_iov[1].iov_len = sizeof(arg); + + memcpy(&padded_iov[2], iov, count * sizeof(struct iovec)); + + res = send_reply_iov(req, 0, padded_iov, count + 2); + free(padded_iov); + + return res; +} + +int fuse_reply_poll(fuse_req_t req, unsigned revents) +{ + struct fuse_poll_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.revents = revents; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_lseek(fuse_req_t req, off_t off) +{ + struct fuse_lseek_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.offset = off; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->se->op.lookup) + req->se->op.lookup(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg; + + if (req->se->op.forget) + req->se->op.forget(req, nodeid, arg->nlookup); + else + fuse_reply_none(req); +} + +static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg) +{ + struct fuse_batch_forget_in *arg = (void *) inarg; + struct fuse_forget_one *param = (void *) PARAM(arg); + unsigned int i; + + (void) nodeid; + + if (req->se->op.forget_multi) { + req->se->op.forget_multi(req, arg->count, + (struct fuse_forget_data *) param); + } else if (req->se->op.forget) { + for (i = 0; i < arg->count; i++) { + struct fuse_forget_one *forget = ¶m[i]; + struct fuse_req *dummy_req; + + dummy_req = fuse_ll_alloc_req(req->se); + if (dummy_req == NULL) + break; + + dummy_req->unique = req->unique; + dummy_req->ctx = req->ctx; + dummy_req->ch = NULL; + + req->se->op.forget(dummy_req, forget->nodeid, + forget->nlookup); + } + fuse_reply_none(req); + } else { + fuse_reply_none(req); + } +} + +static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (req->se->conn.proto_minor >= 9) { + struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg; + + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fip = &fi; + } + } + + if (req->se->op.getattr) + req->se->op.getattr(req, nodeid, fip); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg; + + if (req->se->op.setattr) { + struct fuse_file_info *fi = NULL; + struct fuse_file_info fi_store; + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + convert_attr(arg, &stbuf); + if (arg->valid & FATTR_FH) { + arg->valid &= ~FATTR_FH; + memset(&fi_store, 0, sizeof(fi_store)); + fi = &fi_store; + fi->fh = arg->fh; + } + arg->valid &= + FUSE_SET_ATTR_MODE | + FUSE_SET_ATTR_UID | + FUSE_SET_ATTR_GID | + FUSE_SET_ATTR_SIZE | + FUSE_SET_ATTR_ATIME | + FUSE_SET_ATTR_MTIME | + FUSE_SET_ATTR_KILL_SUID | + FUSE_SET_ATTR_KILL_SGID | + FUSE_SET_ATTR_ATIME_NOW | + FUSE_SET_ATTR_MTIME_NOW | + FUSE_SET_ATTR_CTIME; + + req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_access_in *arg = (struct fuse_access_in *) inarg; + + if (req->se->op.access) + req->se->op.access(req, nodeid, arg->mask); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + (void) inarg; + + if (req->se->op.readlink) + req->se->op.readlink(req, nodeid); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg; + char *name = PARAM(arg); + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + else + name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE; + + if (req->se->op.mknod) + req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + if (req->se->op.mkdir) + req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->se->op.unlink) + req->se->op.unlink(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->se->op.rmdir) + req->se->op.rmdir(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1; + + if (req->se->op.symlink) + req->se->op.symlink(req, linkname, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg; + char *oldname = PARAM(arg); + char *newname = oldname + strlen(oldname) + 1; + + if (req->se->op.rename) + req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, + 0); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg; + char *oldname = PARAM(arg); + char *newname = oldname + strlen(oldname) + 1; + + if (req->se->op.rename) + req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_link_in *arg = (struct fuse_link_in *) inarg; + + if (req->se->op.link) + req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg)); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_create_in *arg = (struct fuse_create_in *) inarg; + + if (req->se->op.tmpfile) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + req->se->op.tmpfile(req, nodeid, arg->mode, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_create_in *arg = (struct fuse_create_in *) inarg; + + if (req->se->op.create) { + struct fuse_file_info fi; + char *name = PARAM(arg); + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + else + name = (char *) inarg + sizeof(struct fuse_open_in); + + req->se->op.create(req, nodeid, name, arg->mode, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_open_in *arg = (struct fuse_open_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->op.open) + req->se->op.open(req, nodeid, &fi); + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT) + fuse_reply_err(req, ENOSYS); + else + fuse_reply_open(req, &fi); +} + +static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + + if (req->se->op.read) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + if (req->se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + req->se->op.read(req, nodeid, arg->size, arg->offset, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_write_in *arg = (struct fuse_write_in *) inarg; + struct fuse_file_info fi; + char *param; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0; + + if (req->se->conn.proto_minor < 9) { + param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE; + } else { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + param = PARAM(arg); + } + + if (req->se->op.write) + req->se->op.write(req, nodeid, param, arg->size, + arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg, + const struct fuse_buf *ibuf) +{ + struct fuse_session *se = req->se; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + struct fuse_write_in *arg = (struct fuse_write_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.writepage = arg->write_flags & FUSE_WRITE_CACHE; + + if (se->conn.proto_minor < 9) { + bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE; + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + FUSE_COMPAT_WRITE_IN_SIZE; + assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD)); + } else { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + } + if (bufv.buf[0].size < arg->size) { + fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n"); + fuse_reply_err(req, EIO); + goto out; + } + bufv.buf[0].size = arg->size; + + se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi); + +out: + /* Need to reset the pipe if ->write_buf() didn't consume all data */ + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(se); +} + +static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.flush = 1; + if (req->se->conn.proto_minor >= 7) + fi.lock_owner = arg->lock_owner; + + if (req->se->op.flush) + req->se->op.flush(req, nodeid, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_release_in *arg = (struct fuse_release_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + if (req->se->conn.proto_minor >= 8) { + fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0; + fi.lock_owner = arg->lock_owner; + } + if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) { + fi.flock_release = 1; + fi.lock_owner = arg->lock_owner; + } + + if (req->se->op.release) + req->se->op.release(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; + struct fuse_file_info fi; + int datasync = arg->fsync_flags & 1; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fsync) + req->se->op.fsync(req, nodeid, datasync, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_open_in *arg = (struct fuse_open_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->op.opendir) + req->se->op.opendir(req, nodeid, &fi); + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT) + fuse_reply_err(req, ENOSYS); + else + fuse_reply_open(req, &fi); +} + +static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.readdir) + req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.readdirplus) + req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_release_in *arg = (struct fuse_release_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + + if (req->se->op.releasedir) + req->se->op.releasedir(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; + struct fuse_file_info fi; + int datasync = arg->fsync_flags & 1; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fsyncdir) + req->se->op.fsyncdir(req, nodeid, datasync, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + (void) nodeid; + (void) inarg; + + if (req->se->op.statfs) + req->se->op.statfs(req, nodeid); + else { + struct statvfs buf = { + .f_namemax = 255, + .f_bsize = 512, + }; + fuse_reply_statfs(req, &buf); + } +} + +static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_session *se = req->se; + unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT); + struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg; + char *name = xattr_ext ? PARAM(arg) : + (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE; + char *value = name + strlen(name) + 1; + + /* XXX:The API should be extended to support extra_flags/setxattr_flags */ + if (req->se->op.setxattr) + req->se->op.setxattr(req, nodeid, name, value, arg->size, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; + + if (req->se->op.getxattr) + req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; + + if (req->se->op.listxattr) + req->se->op.listxattr(req, nodeid, arg->size); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + char *name = (char *) inarg; + + if (req->se->op.removexattr) + req->se->op.removexattr(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void convert_fuse_file_lock(struct fuse_file_lock *fl, + struct flock *flock) +{ + memset(flock, 0, sizeof(struct flock)); + flock->l_type = fl->type; + flock->l_whence = SEEK_SET; + flock->l_start = fl->start; + if (fl->end == OFFSET_MAX) + flock->l_len = 0; + else + flock->l_len = fl->end - fl->start + 1; + flock->l_pid = fl->pid; +} + +static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + convert_fuse_file_lock(&arg->lk, &flock); + if (req->se->op.getlk) + req->se->op.getlk(req, nodeid, &fi, &flock); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg, int sleep) +{ + struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + if (arg->lk_flags & FUSE_LK_FLOCK) { + int op = 0; + + switch (arg->lk.type) { + case F_RDLCK: + op = LOCK_SH; + break; + case F_WRLCK: + op = LOCK_EX; + break; + case F_UNLCK: + op = LOCK_UN; + break; + } + if (!sleep) + op |= LOCK_NB; + + if (req->se->op.flock) + req->se->op.flock(req, nodeid, &fi, op); + else + fuse_reply_err(req, ENOSYS); + } else { + convert_fuse_file_lock(&arg->lk, &flock); + if (req->se->op.setlk) + req->se->op.setlk(req, nodeid, &fi, &flock, sleep); + else + fuse_reply_err(req, ENOSYS); + } +} + +static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + do_setlk_common(req, nodeid, inarg, 0); +} + +static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + do_setlk_common(req, nodeid, inarg, 1); +} + +static int find_interrupted(struct fuse_session *se, struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = se->list.next; curr != &se->list; curr = curr->next) { + if (curr->unique == req->u.i.unique) { + fuse_interrupt_func_t func; + void *data; + + curr->ref_cnt++; + pthread_mutex_unlock(&se->lock); + + /* Ugh, ugly locking */ + pthread_mutex_lock(&curr->lock); + pthread_mutex_lock(&se->lock); + curr->interrupted = 1; + func = curr->u.ni.func; + data = curr->u.ni.data; + pthread_mutex_unlock(&se->lock); + if (func) + func(curr, data); + pthread_mutex_unlock(&curr->lock); + + pthread_mutex_lock(&se->lock); + curr->ref_cnt--; + if (!curr->ref_cnt) { + destroy_req(curr); + } + + return 1; + } + } + for (curr = se->interrupts.next; curr != &se->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->u.i.unique) + return 1; + } + return 0; +} + +static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg; + struct fuse_session *se = req->se; + + (void) nodeid; + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n", + (unsigned long long) arg->unique); + + req->u.i.unique = arg->unique; + + pthread_mutex_lock(&se->lock); + if (find_interrupted(se, req)) { + fuse_chan_put(req->ch); + req->ch = NULL; + destroy_req(req); + } else + list_add_req(req, &se->interrupts); + pthread_mutex_unlock(&se->lock); +} + +static struct fuse_req *check_interrupt(struct fuse_session *se, + struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = se->interrupts.next; curr != &se->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->unique) { + req->interrupted = 1; + list_del_req(curr); + fuse_chan_put(curr->ch); + curr->ch = NULL; + destroy_req(curr); + return NULL; + } + } + curr = se->interrupts.next; + if (curr != &se->interrupts) { + list_del_req(curr); + list_init_req(curr); + return curr; + } else + return NULL; +} + +static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg; + + if (req->se->op.bmap) + req->se->op.bmap(req, nodeid, arg->blocksize, arg->block); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg; + unsigned int flags = arg->flags; + void *in_buf = arg->in_size ? PARAM(arg) : NULL; + struct fuse_file_info fi; + + if (flags & FUSE_IOCTL_DIR && + !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) { + fuse_reply_err(req, ENOTTY); + return; + } + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 && + !(flags & FUSE_IOCTL_32BIT)) { + req->ioctl_64bit = 1; + } + + if (req->se->op.ioctl) + req->se->op.ioctl(req, nodeid, arg->cmd, + (void *)(uintptr_t)arg->arg, &fi, flags, + in_buf, arg->in_size, arg->out_size); + else + fuse_reply_err(req, ENOSYS); +} + +void fuse_pollhandle_destroy(struct fuse_pollhandle *ph) +{ + free(ph); +} + +static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.poll_events = arg->events; + + if (req->se->op.poll) { + struct fuse_pollhandle *ph = NULL; + + if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) { + ph = malloc(sizeof(struct fuse_pollhandle)); + if (ph == NULL) { + fuse_reply_err(req, ENOMEM); + return; + } + ph->kh = arg->kh; + ph->se = req->se; + } + + req->se->op.poll(req, nodeid, &fi, ph); + } else { + fuse_reply_err(req, ENOSYS); + } +} + +static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fallocate) + req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg) +{ + struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg; + struct fuse_file_info fi_in, fi_out; + + memset(&fi_in, 0, sizeof(fi_in)); + fi_in.fh = arg->fh_in; + + memset(&fi_out, 0, sizeof(fi_out)); + fi_out.fh = arg->fh_out; + + + if (req->se->op.copy_file_range) + req->se->op.copy_file_range(req, nodeid_in, arg->off_in, + &fi_in, arg->nodeid_out, + arg->off_out, &fi_out, arg->len, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.lseek) + req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static bool want_flags_valid(uint64_t capable, uint64_t want) +{ + uint64_t unknown_flags = want & (~capable); + if (unknown_flags != 0) { + fuse_log(FUSE_LOG_ERR, + "fuse: unknown connection 'want' flags: 0x%08lx\n", + unknown_flags); + return false; + } + return true; +} + +/* Prevent bogus data races (bogus since "init" is called before + * multi-threading becomes relevant */ +static __attribute__((no_sanitize("thread"))) +void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_init_in *arg = (struct fuse_init_in *) inarg; + struct fuse_init_out outarg; + struct fuse_session *se = req->se; + size_t bufsize = se->bufsize; + size_t outargsize = sizeof(outarg); + uint64_t inargflags = 0; + uint64_t outargflags = 0; + bool buf_reallocable = se->buf_reallocable; + (void) nodeid; + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor); + if (arg->major == 7 && arg->minor >= 6) { + fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags); + fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n", + arg->max_readahead); + } + } + se->conn.proto_major = arg->major; + se->conn.proto_minor = arg->minor; + se->conn.capable_ext = 0; + se->conn.want_ext = 0; + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + + if (arg->major < 7) { + fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (arg->major > 7) { + /* Wait for a second INIT request with a 7.X version */ + send_reply_ok(req, &outarg, sizeof(outarg)); + return; + } + + if (arg->minor >= 6) { + if (arg->max_readahead < se->conn.max_readahead) + se->conn.max_readahead = arg->max_readahead; + inargflags = arg->flags; + if (inargflags & FUSE_INIT_EXT) + inargflags = inargflags | (uint64_t) arg->flags2 << 32; + if (inargflags & FUSE_ASYNC_READ) + se->conn.capable_ext |= FUSE_CAP_ASYNC_READ; + if (inargflags & FUSE_POSIX_LOCKS) + se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS; + if (inargflags & FUSE_ATOMIC_O_TRUNC) + se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC; + if (inargflags & FUSE_EXPORT_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT; + if (inargflags & FUSE_DONT_MASK) + se->conn.capable_ext |= FUSE_CAP_DONT_MASK; + if (inargflags & FUSE_FLOCK_LOCKS) + se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS; + if (inargflags & FUSE_AUTO_INVAL_DATA) + se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA; + if (inargflags & FUSE_DO_READDIRPLUS) + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS; + if (inargflags & FUSE_READDIRPLUS_AUTO) + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO; + if (inargflags & FUSE_ASYNC_DIO) + se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO; + if (inargflags & FUSE_WRITEBACK_CACHE) + se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE; + if (inargflags & FUSE_NO_OPEN_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT; + if (inargflags & FUSE_PARALLEL_DIROPS) + se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS; + if (inargflags & FUSE_POSIX_ACL) + se->conn.capable_ext |= FUSE_CAP_POSIX_ACL; + if (inargflags & FUSE_HANDLE_KILLPRIV) + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV; + if (inargflags & FUSE_HANDLE_KILLPRIV_V2) + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2; + if (inargflags & FUSE_CACHE_SYMLINKS) + se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS; + if (inargflags & FUSE_NO_OPENDIR_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT; + if (inargflags & FUSE_EXPLICIT_INVAL_DATA) + se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA; + if (inargflags & FUSE_SETXATTR_EXT) + se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT; + if (!(inargflags & FUSE_MAX_PAGES)) { + size_t max_bufsize = + FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize() + + FUSE_BUFFER_HEADER_SIZE; + if (bufsize > max_bufsize) { + bufsize = max_bufsize; + } + buf_reallocable = false; + } + if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP) + se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP; + if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY)) + se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY; + if (inargflags & FUSE_PASSTHROUGH) + se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH; + if (inargflags & FUSE_NO_EXPORT_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT; + } else { + se->conn.max_readahead = 0; + } + + if (se->conn.proto_minor >= 14) { +#ifdef HAVE_SPLICE +#ifdef HAVE_VMSPLICE + if ((se->io == NULL) || (se->io->splice_send != NULL)) { + se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE; + } +#endif + if ((se->io == NULL) || (se->io->splice_receive != NULL)) { + se->conn.capable_ext |= FUSE_CAP_SPLICE_READ; + } +#endif + } + if (se->conn.proto_minor >= 18) + se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR; + + /* Default settings for modern filesystems. + * + * Most of these capabilities were disabled by default in + * libfuse2 for backwards compatibility reasons. In libfuse3, + * we can finally enable them by default (as long as they're + * supported by the kernel). + */ +#define LL_SET_DEFAULT(cond, cap) \ + if ((cond)) \ + fuse_set_feature_flag(&se->conn, cap) + + LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ); + LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA); + LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO); + LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR); + LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC); + LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ); + LL_SET_DEFAULT(se->op.getlk && se->op.setlk, + FUSE_CAP_POSIX_LOCKS); + LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS); + LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS); + LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir, + FUSE_CAP_READDIRPLUS_AUTO); + + /* This could safely become default, but libfuse needs an API extension + * to support it + * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT); + */ + + se->conn.time_gran = 1; + + se->got_init = 1; + if (se->op.init) { + uint64_t want_ext_default = se->conn.want_ext; + uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext); + int rc; + + // Apply the first 32 bits of capable_ext to capable + se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); + se->conn.want = want_default; + + se->op.init(se->userdata, &se->conn); + + /* + * se->conn.want is 32-bit value and deprecated in favour of + * se->conn.want_ext + * Userspace might still use conn.want - we need to convert it + */ + rc = convert_to_conn_want_ext(&se->conn, want_ext_default, + want_default); + if (rc != 0) { + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + } + + if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) { + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + + unsigned max_read_mo = get_max_read(se->mo); + if (se->conn.max_read != max_read_mo) { + fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() " + "requested different maximum read size (%u vs %u)\n", + se->conn.max_read, max_read_mo); + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fuse_log(FUSE_LOG_ERR, + "fuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + if (buf_reallocable) + bufsize = UINT_MAX; + se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE); + se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; + + if (arg->flags & FUSE_MAX_PAGES) { + outarg.flags |= FUSE_MAX_PAGES; + outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1; + } + outargflags = outarg.flags; + /* Always enable big writes, this is superseded + by the max_write option */ + outargflags |= FUSE_BIG_WRITES; + + if (se->conn.want_ext & FUSE_CAP_ASYNC_READ) + outargflags |= FUSE_ASYNC_READ; + if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS) + outargflags |= FUSE_POSIX_LOCKS; + if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC) + outargflags |= FUSE_ATOMIC_O_TRUNC; + if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT) + outargflags |= FUSE_EXPORT_SUPPORT; + if (se->conn.want_ext & FUSE_CAP_DONT_MASK) + outargflags |= FUSE_DONT_MASK; + if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS) + outargflags |= FUSE_FLOCK_LOCKS; + if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA) + outargflags |= FUSE_AUTO_INVAL_DATA; + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS) + outargflags |= FUSE_DO_READDIRPLUS; + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO) + outargflags |= FUSE_READDIRPLUS_AUTO; + if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO) + outargflags |= FUSE_ASYNC_DIO; + if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE) + outargflags |= FUSE_WRITEBACK_CACHE; + if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS) + outargflags |= FUSE_PARALLEL_DIROPS; + if (se->conn.want_ext & FUSE_CAP_POSIX_ACL) + outargflags |= FUSE_POSIX_ACL; + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV) + outargflags |= FUSE_HANDLE_KILLPRIV; + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) + outargflags |= FUSE_HANDLE_KILLPRIV_V2; + if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS) + outargflags |= FUSE_CACHE_SYMLINKS; + if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA) + outargflags |= FUSE_EXPLICIT_INVAL_DATA; + if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT) + outargflags |= FUSE_SETXATTR_EXT; + if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP) + outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP; + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) { + outargflags |= FUSE_PASSTHROUGH; + /* + * outarg.max_stack_depth includes the fuse stack layer, + * so it is one more than max_backing_stack_depth. + */ + outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1; + } + if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT) + outargflags |= FUSE_NO_EXPORT_SUPPORT; + + if (inargflags & FUSE_INIT_EXT) { + outargflags |= FUSE_INIT_EXT; + outarg.flags2 = outargflags >> 32; + } + + outarg.flags = outargflags; + + outarg.max_readahead = se->conn.max_readahead; + outarg.max_write = se->conn.max_write; + if (se->conn.proto_minor >= 13) { + if (se->conn.max_background >= (1 << 16)) + se->conn.max_background = (1 << 16) - 1; + if (se->conn.congestion_threshold > se->conn.max_background) + se->conn.congestion_threshold = se->conn.max_background; + if (!se->conn.congestion_threshold) { + se->conn.congestion_threshold = + se->conn.max_background * 3 / 4; + } + + outarg.max_background = se->conn.max_background; + outarg.congestion_threshold = se->conn.congestion_threshold; + } + if (se->conn.proto_minor >= 23) + outarg.time_gran = se->conn.time_gran; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor); + fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags); + fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n", + outarg.max_readahead); + fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write); + fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n", + outarg.max_background); + fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n", + outarg.congestion_threshold); + fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n", + outarg.time_gran); + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) + fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n", + outarg.max_stack_depth); + } + if (arg->minor < 5) + outargsize = FUSE_COMPAT_INIT_OUT_SIZE; + else if (arg->minor < 23) + outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE; + + send_reply_ok(req, &outarg, outargsize); +} + +static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_session *se = req->se; + + (void) nodeid; + (void) inarg; + + se->got_destroy = 1; + se->got_init = 0; + if (se->op.destroy) + se->op.destroy(se->userdata); + + send_reply_ok(req, NULL, 0); +} + +static void list_del_nreq(struct fuse_notify_req *nreq) +{ + struct fuse_notify_req *prev = nreq->prev; + struct fuse_notify_req *next = nreq->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_nreq(struct fuse_notify_req *nreq, + struct fuse_notify_req *next) +{ + struct fuse_notify_req *prev = next->prev; + nreq->next = next; + nreq->prev = prev; + prev->next = nreq; + next->prev = nreq; +} + +static void list_init_nreq(struct fuse_notify_req *nreq) +{ + nreq->next = nreq; + nreq->prev = nreq; +} + +static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *buf) +{ + struct fuse_session *se = req->se; + struct fuse_notify_req *nreq; + struct fuse_notify_req *head; + + pthread_mutex_lock(&se->lock); + head = &se->notify_list; + for (nreq = head->next; nreq != head; nreq = nreq->next) { + if (nreq->unique == req->unique) { + list_del_nreq(nreq); + break; + } + } + pthread_mutex_unlock(&se->lock); + + if (nreq != head) + nreq->reply(nreq, req, nodeid, inarg, buf); +} + +static int send_notify_iov(struct fuse_session *se, int notify_code, + struct iovec *iov, int count) +{ + struct fuse_out_header out; + + if (!se->got_init) + return -ENOTCONN; + + out.unique = 0; + out.error = notify_code; + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(se, NULL, iov, count); +} + +int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph) +{ + if (ph != NULL) { + struct fuse_notify_poll_wakeup_out outarg; + struct iovec iov[2]; + + outarg.kh = ph->kh; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2); + } else { + return 0; + } +} + +int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, + off_t off, off_t len) +{ + struct fuse_notify_inval_inode_out outarg; + struct iovec iov[2]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 12) + return -ENOSYS; + + outarg.ino = ino; + outarg.off = off; + outarg.len = len; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2); +} + +/** + * Notify parent attributes and the dentry matching parent/name + * + * Underlying base function for fuse_lowlevel_notify_inval_entry() and + * fuse_lowlevel_notify_expire_entry(). + * + * @warning + * Only checks if fuse_lowlevel_notify_inval_entry() is supported by + * the kernel. All other flags will fall back to + * fuse_lowlevel_notify_inval_entry() if not supported! + * DO THE PROPER CHECKS IN THE DERIVED FUNCTION! + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @param flags flags to control if the entry should be expired or invalidated + * @return zero for success, -errno for failure +*/ +static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen, + enum fuse_notify_entry_flags flags) +{ + struct fuse_notify_inval_entry_out outarg; + struct iovec iov[3]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 12) + return -ENOSYS; + + outarg.parent = parent; + outarg.namelen = namelen; + outarg.flags = 0; + if (flags & FUSE_LL_EXPIRE_ONLY) + outarg.flags |= FUSE_EXPIRE_ONLY; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3); +} + +int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen) +{ + return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE); +} + +int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen) +{ + if (!se) + return -EINVAL; + + if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY)) + return -ENOSYS; + + return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY); +} + + +int fuse_lowlevel_notify_delete(struct fuse_session *se, + fuse_ino_t parent, fuse_ino_t child, + const char *name, size_t namelen) +{ + struct fuse_notify_delete_out outarg; + struct iovec iov[3]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 18) + return -ENOSYS; + + outarg.parent = parent; + outarg.child = child; + outarg.namelen = namelen; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3); +} + +int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct fuse_out_header out; + struct fuse_notify_store_out outarg; + struct iovec iov[3]; + size_t size = fuse_buf_size(bufv); + int res; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 15) + return -ENOSYS; + + out.unique = 0; + out.error = FUSE_NOTIFY_STORE; + + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + outarg.padding = 0; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(out); + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags); + if (res > 0) + res = -res; + + return res; +} + +struct fuse_retrieve_req { + struct fuse_notify_req nreq; + void *cookie; +}; + +static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq, + fuse_req_t req, fuse_ino_t ino, + const void *inarg, + const struct fuse_buf *ibuf) +{ + struct fuse_session *se = req->se; + struct fuse_retrieve_req *rreq = + container_of(nreq, struct fuse_retrieve_req, nreq); + const struct fuse_notify_retrieve_in *arg = inarg; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + sizeof(struct fuse_notify_retrieve_in); + + if (bufv.buf[0].size < arg->size) { + fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n"); + fuse_reply_none(req); + goto out; + } + bufv.buf[0].size = arg->size; + + if (se->op.retrieve_reply) { + se->op.retrieve_reply(req, rreq->cookie, ino, + arg->offset, &bufv); + } else { + fuse_reply_none(req); + } +out: + free(rreq); + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(se); +} + +int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, + size_t size, off_t offset, void *cookie) +{ + struct fuse_notify_retrieve_out outarg; + struct iovec iov[2]; + struct fuse_retrieve_req *rreq; + int err; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 15) + return -ENOSYS; + + rreq = malloc(sizeof(*rreq)); + if (rreq == NULL) + return -ENOMEM; + + pthread_mutex_lock(&se->lock); + rreq->cookie = cookie; + rreq->nreq.unique = se->notify_ctr++; + rreq->nreq.reply = fuse_ll_retrieve_reply; + list_add_nreq(&rreq->nreq, &se->notify_list); + pthread_mutex_unlock(&se->lock); + + outarg.notify_unique = rreq->nreq.unique; + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2); + if (err) { + pthread_mutex_lock(&se->lock); + list_del_nreq(&rreq->nreq); + pthread_mutex_unlock(&se->lock); + free(rreq); + } + + return err; +} + +void *fuse_req_userdata(fuse_req_t req) +{ + return req->se->userdata; +} + +const struct fuse_ctx *fuse_req_ctx(fuse_req_t req) +{ + return &req->ctx; +} + +void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, + void *data) +{ + pthread_mutex_lock(&req->lock); + pthread_mutex_lock(&req->se->lock); + req->u.ni.func = func; + req->u.ni.data = data; + pthread_mutex_unlock(&req->se->lock); + if (req->interrupted && func) + func(req, data); + pthread_mutex_unlock(&req->lock); +} + +int fuse_req_interrupted(fuse_req_t req) +{ + int interrupted; + + pthread_mutex_lock(&req->se->lock); + interrupted = req->interrupted; + pthread_mutex_unlock(&req->se->lock); + + return interrupted; +} + +static struct { + void (*func)(fuse_req_t, fuse_ino_t, const void *); + const char *name; +} fuse_ll_ops[] = { + [FUSE_LOOKUP] = { do_lookup, "LOOKUP" }, + [FUSE_FORGET] = { do_forget, "FORGET" }, + [FUSE_GETATTR] = { do_getattr, "GETATTR" }, + [FUSE_SETATTR] = { do_setattr, "SETATTR" }, + [FUSE_READLINK] = { do_readlink, "READLINK" }, + [FUSE_SYMLINK] = { do_symlink, "SYMLINK" }, + [FUSE_MKNOD] = { do_mknod, "MKNOD" }, + [FUSE_MKDIR] = { do_mkdir, "MKDIR" }, + [FUSE_UNLINK] = { do_unlink, "UNLINK" }, + [FUSE_RMDIR] = { do_rmdir, "RMDIR" }, + [FUSE_RENAME] = { do_rename, "RENAME" }, + [FUSE_LINK] = { do_link, "LINK" }, + [FUSE_OPEN] = { do_open, "OPEN" }, + [FUSE_READ] = { do_read, "READ" }, + [FUSE_WRITE] = { do_write, "WRITE" }, + [FUSE_STATFS] = { do_statfs, "STATFS" }, + [FUSE_RELEASE] = { do_release, "RELEASE" }, + [FUSE_FSYNC] = { do_fsync, "FSYNC" }, + [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" }, + [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" }, + [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" }, + [FUSE_FLUSH] = { do_flush, "FLUSH" }, + [FUSE_INIT] = { do_init, "INIT" }, + [FUSE_OPENDIR] = { do_opendir, "OPENDIR" }, + [FUSE_READDIR] = { do_readdir, "READDIR" }, + [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" }, + [FUSE_GETLK] = { do_getlk, "GETLK" }, + [FUSE_SETLK] = { do_setlk, "SETLK" }, + [FUSE_SETLKW] = { do_setlkw, "SETLKW" }, + [FUSE_ACCESS] = { do_access, "ACCESS" }, + [FUSE_CREATE] = { do_create, "CREATE" }, + [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" }, + [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" }, + [FUSE_BMAP] = { do_bmap, "BMAP" }, + [FUSE_IOCTL] = { do_ioctl, "IOCTL" }, + [FUSE_POLL] = { do_poll, "POLL" }, + [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" }, + [FUSE_DESTROY] = { do_destroy, "DESTROY" }, + [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" }, + [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" }, + [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"}, + [FUSE_RENAME2] = { do_rename2, "RENAME2" }, + [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_LSEEK] = { do_lseek, "LSEEK" }, + [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, +}; + +/* + * For ABI compatibility we cannot allow higher values than CUSE_INIT. + * Without ABI compatibility we could use the size of the array. + * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + */ +#define FUSE_MAXOP (CUSE_INIT + 1) + +static const char *opname(enum fuse_opcode opcode) +{ + if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) + return "???"; + else + return fuse_ll_ops[opcode].name; +} + +static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst, + struct fuse_bufvec *src) +{ + ssize_t res = fuse_buf_copy(dst, src, 0); + if (res < 0) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res)); + return res; + } + if ((size_t)res < fuse_buf_size(dst)) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n"); + return -1; + } + return 0; +} + +void fuse_session_process_buf(struct fuse_session *se, + const struct fuse_buf *buf) +{ + fuse_session_process_buf_internal(se, buf, NULL); +} + +/* libfuse internal handler */ +void fuse_session_process_buf_internal(struct fuse_session *se, + const struct fuse_buf *buf, struct fuse_chan *ch) +{ + const size_t write_header_size = sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 }; + struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size); + struct fuse_in_header *in; + const void *inarg; + struct fuse_req *req; + void *mbuf = NULL; + int err; + int res; + + if (buf->flags & FUSE_BUF_IS_FD) { + if (buf->size < tmpbuf.buf[0].size) + tmpbuf.buf[0].size = buf->size; + + mbuf = malloc(tmpbuf.buf[0].size); + if (mbuf == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n"); + goto clear_pipe; + } + tmpbuf.buf[0].mem = mbuf; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + if (res < 0) + goto clear_pipe; + + in = mbuf; + } else { + in = buf->mem; + } + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, + "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", + (unsigned long long) in->unique, + opname((enum fuse_opcode) in->opcode), in->opcode, + (unsigned long long) in->nodeid, buf->size, in->pid); + } + + req = fuse_ll_alloc_req(se); + if (req == NULL) { + struct fuse_out_header out = { + .unique = in->unique, + .error = -ENOMEM, + }; + struct iovec iov = { + .iov_base = &out, + .iov_len = sizeof(struct fuse_out_header), + }; + + fuse_send_msg(se, ch, &iov, 1); + goto clear_pipe; + } + + req->unique = in->unique; + req->ctx.uid = in->uid; + req->ctx.gid = in->gid; + req->ctx.pid = in->pid; + req->ch = ch ? fuse_chan_get(ch) : NULL; + + err = EIO; + if (!se->got_init) { + enum fuse_opcode expected; + + expected = se->cuse_data ? CUSE_INIT : FUSE_INIT; + if (in->opcode != expected) + goto reply_err; + } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT) + goto reply_err; + + err = EACCES; + /* Implement -o allow_root */ + if (se->deny_others && in->uid != se->owner && in->uid != 0 && + in->opcode != FUSE_INIT && in->opcode != FUSE_READ && + in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC && + in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR && + in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR && + in->opcode != FUSE_NOTIFY_REPLY && + in->opcode != FUSE_READDIRPLUS) + goto reply_err; + + err = ENOSYS; + if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) + goto reply_err; + /* Do not process interrupt request */ + if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) { + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n"); + goto reply_err; + } + if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) { + struct fuse_req *intr; + pthread_mutex_lock(&se->lock); + intr = check_interrupt(se, req); + list_add_req(req, &se->list); + pthread_mutex_unlock(&se->lock); + if (intr) + fuse_reply_err(intr, EAGAIN); + } + + if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size && + (in->opcode != FUSE_WRITE || !se->op.write_buf) && + in->opcode != FUSE_NOTIFY_REPLY) { + void *newmbuf; + + err = ENOMEM; + newmbuf = realloc(mbuf, buf->size); + if (newmbuf == NULL) + goto reply_err; + mbuf = newmbuf; + + tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size); + tmpbuf.buf[0].mem = (char *)mbuf + write_header_size; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + err = -res; + if (res < 0) + goto reply_err; + + in = mbuf; + } + + inarg = (void *) &in[1]; + if (in->opcode == FUSE_WRITE && se->op.write_buf) + do_write_buf(req, in->nodeid, inarg, buf); + else if (in->opcode == FUSE_NOTIFY_REPLY) + do_notify_reply(req, in->nodeid, inarg, buf); + else + fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg); + +out_free: + free(mbuf); + return; + +reply_err: + fuse_reply_err(req, err); +clear_pipe: + if (buf->flags & FUSE_BUF_IS_FD) + fuse_ll_clear_pipe(se); + goto out_free; +} + +#define LL_OPTION(n,o,v) \ + { n, offsetof(struct fuse_session, o), v } + +static const struct fuse_opt fuse_ll_opts[] = { + LL_OPTION("debug", debug, 1), + LL_OPTION("-d", debug, 1), + LL_OPTION("--debug", debug, 1), + LL_OPTION("allow_root", deny_others, 1), + FUSE_OPT_END +}; + +void fuse_lowlevel_version(void) +{ + printf("using FUSE kernel interface version %i.%i\n", + FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION); + fuse_mount_version(); +} + +void fuse_lowlevel_help(void) +{ + /* These are not all options, but the ones that are + potentially of interest to an end-user */ + printf( +" -o allow_other allow access by all users\n" +" -o allow_root allow access by root\n" +" -o auto_unmount auto unmount on process termination\n"); +} + +void fuse_session_destroy(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp; + + if (se->got_init && !se->got_destroy) { + if (se->op.destroy) + se->op.destroy(se->userdata); + } + llp = pthread_getspecific(se->pipe_key); + if (llp != NULL) + fuse_ll_pipe_free(llp); + pthread_key_delete(se->pipe_key); + pthread_mutex_destroy(&se->lock); + free(se->cuse_data); + if (se->fd != -1) + close(se->fd); + if (se->io != NULL) + free(se->io); + destroy_mount_opts(se->mo); + free(se); +} + + +static void fuse_ll_pipe_destructor(void *data) +{ + struct fuse_ll_pipe *llp = data; + fuse_ll_pipe_free(llp); +} + +void fuse_buf_free(struct fuse_buf *buf) +{ + if (buf->mem == NULL) + return; + + size_t write_header_sz = + sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in); + + char *ptr = (char *)buf->mem - pagesize + write_header_sz; + free(ptr); + buf->mem = NULL; +} + +/* + * This is used to allocate buffers that hold fuse requests + */ +static void *buf_alloc(size_t size, bool internal) +{ + /* + * For libfuse internal caller add in alignment. That cannot be done + * for an external caller, as it is not guaranteed that the external + * caller frees the raw pointer. + */ + if (internal) { + size_t write_header_sz = sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + size_t new_size = ROUND_UP(size + write_header_sz, pagesize); + + char *buf = aligned_alloc(pagesize, new_size); + if (buf == NULL) + return NULL; + + buf += pagesize - write_header_sz; + + return buf; + } else { + return malloc(size); + } +} + +/* + *@param internal true if called from libfuse internal code + */ +static int _fuse_session_receive_buf(struct fuse_session *se, + struct fuse_buf *buf, struct fuse_chan *ch, + bool internal) +{ + int err; + ssize_t res; + size_t bufsize; +#ifdef HAVE_SPLICE + struct fuse_ll_pipe *llp; + struct fuse_buf tmpbuf; + +pipe_retry: + bufsize = se->bufsize; + + if (se->conn.proto_minor < 14 || + !(se->conn.want_ext & FUSE_CAP_SPLICE_READ)) + goto fallback; + + llp = fuse_ll_get_pipe(se); + if (llp == NULL) + goto fallback; + + if (llp->size < bufsize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize); + if (res == -1) { + llp->can_grow = 0; + res = grow_pipe_to_max(llp->pipe[0]); + if (res > 0) + llp->size = res; + goto fallback; + } + llp->size = res; + } + if (llp->size < bufsize) + goto fallback; + } + + if (se->io != NULL && se->io->splice_receive != NULL) { + res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL, + llp->pipe[1], NULL, bufsize, 0, + se->userdata); + } else { + res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL, + bufsize, 0); + } + err = errno; + + if (fuse_session_exited(se)) + return 0; + + if (res == -1) { + if (err == ENODEV) { + /* Filesystem was unmounted, or connection was aborted + via /sys/fs/fuse/connections */ + fuse_session_exit(se); + return 0; + } + + /* FUSE_INIT might have increased the required bufsize */ + if (err == EINVAL && bufsize < se->bufsize) { + fuse_ll_clear_pipe(se); + goto pipe_retry; + } + + if (err != EINTR && err != EAGAIN) + perror("fuse: splice from device"); + return -err; + } + + if (res < sizeof(struct fuse_in_header)) { + fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n"); + return -EIO; + } + + tmpbuf = (struct fuse_buf){ + .size = res, + .flags = FUSE_BUF_IS_FD, + .fd = llp->pipe[0], + }; + + /* + * Don't bother with zero copy for small requests. + * fuse_loop_mt() needs to check for FORGET so this more than + * just an optimization. + */ + if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + + pagesize) { + struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 }; + struct fuse_bufvec dst = { .count = 1 }; + + if (!buf->mem) { + buf->mem = buf_alloc(bufsize, internal); + if (!buf->mem) { + fuse_log( + FUSE_LOG_ERR, + "fuse: failed to allocate read buffer\n"); + return -ENOMEM; + } + buf->mem_size = bufsize; + } + buf->size = bufsize; + buf->flags = 0; + dst.buf[0] = *buf; + + res = fuse_buf_copy(&dst, &src, 0); + if (res < 0) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", + strerror(-res)); + fuse_ll_clear_pipe(se); + return res; + } + if (res < tmpbuf.size) { + fuse_log(FUSE_LOG_ERR, + "fuse: copy from pipe: short read\n"); + fuse_ll_clear_pipe(se); + return -EIO; + } + assert(res == tmpbuf.size); + + } else { + /* Don't overwrite buf->mem, as that would cause a leak */ + buf->fd = tmpbuf.fd; + buf->flags = tmpbuf.flags; + } + buf->size = tmpbuf.size; + + return res; + +fallback: +#endif + bufsize = internal ? buf->mem_size : se->bufsize; + if (!buf->mem) { + bufsize = se->bufsize; /* might have changed */ + buf->mem = buf_alloc(bufsize, internal); + if (!buf->mem) { + fuse_log(FUSE_LOG_ERR, + "fuse: failed to allocate read buffer\n"); + return -ENOMEM; + } + + if (internal) + buf->mem_size = bufsize; + } + +restart: + if (se->io != NULL) { + /* se->io->read is never NULL if se->io is not NULL as + specified by fuse_session_custom_io()*/ + res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize, + se->userdata); + } else { + res = read(ch ? ch->fd : se->fd, buf->mem, bufsize); + } + err = errno; + + if (fuse_session_exited(se)) + return 0; + if (res == -1) { + if (err == EINVAL && internal && se->bufsize > bufsize) { + /* FUSE_INIT might have increased the required bufsize */ + bufsize = se->bufsize; + void *newbuf = buf_alloc(bufsize, internal); + if (!newbuf) { + fuse_log( + FUSE_LOG_ERR, + "fuse: failed to (re)allocate read buffer\n"); + return -ENOMEM; + } + fuse_buf_free(buf); + buf->mem = newbuf; + buf->mem_size = bufsize; + goto restart; + } + + /* ENOENT means the operation was interrupted, it's safe + to restart */ + if (err == ENOENT) + goto restart; + + if (err == ENODEV) { + /* Filesystem was unmounted, or connection was aborted + via /sys/fs/fuse/connections */ + fuse_session_exit(se); + return 0; + } + /* Errors occurring during normal operation: EINTR (read + interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem + umounted) */ + if (err != EINTR && err != EAGAIN) + perror("fuse: reading device"); + return -err; + } + if ((size_t)res < sizeof(struct fuse_in_header)) { + fuse_log(FUSE_LOG_ERR, "short read on fuse device\n"); + return -EIO; + } + + buf->size = res; + + return res; +} + +int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf) +{ + return _fuse_session_receive_buf(se, buf, NULL, false); +} + +/* libfuse internal handler */ +int fuse_session_receive_buf_internal(struct fuse_session *se, + struct fuse_buf *buf, + struct fuse_chan *ch) +{ + /* + * if run internally thread buffers are from libfuse - we can + * reallocate them + */ + if (unlikely(!se->got_init) && !se->buf_reallocable) + se->buf_reallocable = true; + + return _fuse_session_receive_buf(se, buf, ch, true); +} + +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata) +{ + int err; + struct fuse_session *se; + struct mount_opts *mo; + + if (sizeof(struct fuse_lowlevel_ops) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n"); + op_size = sizeof(struct fuse_lowlevel_ops); + } + + if (args->argc == 0) { + fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n"); + return NULL; + } + + se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session)); + if (se == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n"); + goto out1; + } + se->fd = -1; + se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize(); + se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; + se->conn.max_readahead = UINT_MAX; + + /* Parse options */ + if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1) + goto out2; + if(se->deny_others) { + /* Allowing access only by root is done by instructing + * kernel to allow access by everyone, and then restricting + * access to root and mountpoint owner in libfuse. + */ + // We may be adding the option a second time, but + // that doesn't hurt. + if(fuse_opt_add_arg(args, "-oallow_other") == -1) + goto out2; + } + mo = parse_mount_opts(args); + if (mo == NULL) + goto out3; + + if(args->argc == 1 && + args->argv[0][0] == '-') { + fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but " + "will be ignored\n"); + } else if (args->argc != 1) { + int i; + fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `"); + for(i = 1; i < args->argc-1; i++) + fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]); + fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]); + goto out4; + } + + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION); + + list_init_req(&se->list); + list_init_req(&se->interrupts); + list_init_nreq(&se->notify_list); + se->notify_ctr = 1; + pthread_mutex_init(&se->lock, NULL); + + err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor); + if (err) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + goto out5; + } + + memcpy(&se->op, op, op_size); + se->owner = getuid(); + se->userdata = userdata; + + se->mo = mo; + + /* Fuse server application should pass the version it was compiled + * against and pass it. If a libfuse version accidentally introduces an + * ABI incompatibility, it might be possible to 'fix' that at run time, + * by checking the version numbers. + */ + se->version = *version; + + return se; + +out5: + pthread_mutex_destroy(&se->lock); +out4: + fuse_opt_free_args(args); +out3: + if (mo != NULL) + destroy_mount_opts(mo); +out2: + free(se); +out1: + return NULL; +} + +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, + void *userdata) +{ + /* unknown version */ + struct libfuse_version version = { 0 }; + + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); +} + +FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17") +int fuse_session_custom_io_317(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd) +{ + if (sizeof(struct fuse_custom_io) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n"); + op_size = sizeof(struct fuse_custom_io); + } + + if (fd < 0) { + fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to " + "fuse_session_custom_io()\n", fd); + return -EBADF; + } + if (io == NULL) { + fuse_log(FUSE_LOG_ERR, "No custom IO passed to " + "fuse_session_custom_io()\n"); + return -EINVAL; + } else if (io->read == NULL || io->writev == NULL) { + /* If the user provides their own file descriptor, we can't + guarantee that the default behavior of the io operations made + in libfuse will function properly. Therefore, we enforce the + user to implement these io operations when using custom io. */ + fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must " + "implement both io->read() and io->writev\n"); + return -EINVAL; + } + + se->io = calloc(1, sizeof(struct fuse_custom_io)); + if (se->io == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. " + "Error: %s\n", strerror(errno)); + return -errno; + } + + se->fd = fd; + memcpy(se->io, io, op_size); + return 0; +} + +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0") +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) +{ + return fuse_session_custom_io_317(se, io, + offsetof(struct fuse_custom_io, clone_fd), fd); +} + +int fuse_session_mount(struct fuse_session *se, const char *mountpoint) +{ + int fd; + + if (mountpoint == NULL) { + fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n"); + return -1; + } + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + /* + * To allow FUSE daemons to run without privileges, the caller may open + * /dev/fuse before launching the file system and pass on the file + * descriptor by specifying /dev/fd/N as the mount point. Note that the + * parent process takes care of performing the mount in this case. + */ + fd = fuse_mnt_parse_fuse_fd(mountpoint); + if (fd != -1) { + if (fcntl(fd, F_GETFD) == -1) { + fuse_log(FUSE_LOG_ERR, + "fuse: Invalid file descriptor /dev/fd/%u\n", + fd); + return -1; + } + se->fd = fd; + return 0; + } + + /* Open channel */ + fd = fuse_kern_mount(mountpoint, se->mo); + if (fd == -1) + return -1; + se->fd = fd; + + /* Save mountpoint */ + se->mountpoint = strdup(mountpoint); + if (se->mountpoint == NULL) + goto error_out; + + return 0; + +error_out: + fuse_kern_unmount(mountpoint, fd); + return -1; +} + +int fuse_session_fd(struct fuse_session *se) +{ + return se->fd; +} + +void fuse_session_unmount(struct fuse_session *se) +{ + if (se->mountpoint != NULL) { + fuse_kern_unmount(se->mountpoint, se->fd); + se->fd = -1; + free(se->mountpoint); + se->mountpoint = NULL; + } +} + +#ifdef linux +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + char *buf; + size_t bufsize = 1024; + char path[128]; + int ret; + int fd; + unsigned long pid = req->ctx.pid; + char *s; + + sprintf(path, "/proc/%lu/task/%lu/status", pid, pid); + +retry: + buf = malloc(bufsize); + if (buf == NULL) + return -ENOMEM; + + ret = -EIO; + fd = open(path, O_RDONLY); + if (fd == -1) + goto out_free; + + ret = read(fd, buf, bufsize); + close(fd); + if (ret < 0) { + ret = -EIO; + goto out_free; + } + + if ((size_t)ret == bufsize) { + free(buf); + bufsize *= 4; + goto retry; + } + + buf[ret] = '\0'; + ret = -EIO; + s = strstr(buf, "\nGroups:"); + if (s == NULL) + goto out_free; + + s += 8; + ret = 0; + while (1) { + char *end; + unsigned long val = strtoul(s, &end, 0); + if (end == s) + break; + + s = end; + if (ret < size) + list[ret] = val; + ret++; + } + +out_free: + free(buf); + return ret; +} +#else /* linux */ +/* + * This is currently not implemented on other than Linux... + */ +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + (void) req; (void) size; (void) list; + return -ENOSYS; +} +#endif + +/* Prevent spurious data race warning - we don't care + * about races for this flag */ +__attribute__((no_sanitize_thread)) +void fuse_session_exit(struct fuse_session *se) +{ + se->exited = 1; +} + +__attribute__((no_sanitize_thread)) +void fuse_session_reset(struct fuse_session *se) +{ + se->exited = 0; + se->error = 0; +} + +__attribute__((no_sanitize_thread)) +int fuse_session_exited(struct fuse_session *se) +{ + return se->exited; +} diff --git a/lib/fuse_misc.h b/lib/fuse_misc.h new file mode 100644 index 0000000..855edc3 --- /dev/null +++ b/lib/fuse_misc.h @@ -0,0 +1,51 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include + +/* + Versioned symbols cannot be used in some cases because it + - not supported on MacOSX (in MachO binary format) + + Note: "@@" denotes the default symbol, "@" is binary a compat version. + +*/ +#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS +# if HAVE_SYMVER_ATTRIBUTE +# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2))) +# else +# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2); +# endif +#else +#define FUSE_SYMVER(sym1, sym2) +#endif + +#ifdef HAVE_STRUCT_STAT_ST_ATIM +/* Linux */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val) +#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val) +#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC) +/* FreeBSD */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val) +#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val) +#else +#define ST_ATIM_NSEC(stbuf) 0 +#define ST_CTIM_NSEC(stbuf) 0 +#define ST_MTIM_NSEC(stbuf) 0 +#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0) +#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0) +#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0) +#endif diff --git a/lib/fuse_opt.c b/lib/fuse_opt.c new file mode 100644 index 0000000..1d3b6a1 --- /dev/null +++ b/lib/fuse_opt.c @@ -0,0 +1,423 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of option parsing routines (dealing with `struct + fuse_args`). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_opt.h" +#include "fuse_misc.h" + +#include +#include +#include +#include + +struct fuse_opt_context { + void *data; + const struct fuse_opt *opt; + fuse_opt_proc_t proc; + int argctr; + int argc; + char **argv; + struct fuse_args outargs; + char *opts; + int nonopt; +}; + +void fuse_opt_free_args(struct fuse_args *args) +{ + if (args) { + if (args->argv && args->allocated) { + int i; + for (i = 0; i < args->argc; i++) + free(args->argv[i]); + free(args->argv); + } + args->argc = 0; + args->argv = NULL; + args->allocated = 0; + } +} + +static int alloc_failed(void) +{ + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; +} + +int fuse_opt_add_arg(struct fuse_args *args, const char *arg) +{ + char **newargv; + char *newarg; + + assert(!args->argv || args->allocated); + + newarg = strdup(arg); + if (!newarg) + return alloc_failed(); + + newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *)); + if (!newargv) { + free(newarg); + return alloc_failed(); + } + + args->argv = newargv; + args->allocated = 1; + args->argv[args->argc++] = newarg; + args->argv[args->argc] = NULL; + return 0; +} + +static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos, + const char *arg) +{ + assert(pos <= args->argc); + if (fuse_opt_add_arg(args, arg) == -1) + return -1; + + if (pos != args->argc - 1) { + char *newarg = args->argv[args->argc - 1]; + memmove(&args->argv[pos + 1], &args->argv[pos], + sizeof(char *) * (args->argc - pos - 1)); + args->argv[pos] = newarg; + } + return 0; +} + +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg) +{ + return fuse_opt_insert_arg_common(args, pos, arg); +} + +static int next_arg(struct fuse_opt_context *ctx, const char *opt) +{ + if (ctx->argctr + 1 >= ctx->argc) { + fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt); + return -1; + } + ctx->argctr++; + return 0; +} + +static int add_arg(struct fuse_opt_context *ctx, const char *arg) +{ + return fuse_opt_add_arg(&ctx->outargs, arg); +} + +static int add_opt_common(char **opts, const char *opt, int esc) +{ + unsigned oldlen = *opts ? strlen(*opts) : 0; + char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1); + + if (!d) + return alloc_failed(); + + *opts = d; + if (oldlen) { + d += oldlen; + *d++ = ','; + } + + for (; *opt; opt++) { + if (esc && (*opt == ',' || *opt == '\\')) + *d++ = '\\'; + *d++ = *opt; + } + *d = '\0'; + + return 0; +} + +int fuse_opt_add_opt(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 0); +} + +int fuse_opt_add_opt_escaped(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 1); +} + +static int add_opt(struct fuse_opt_context *ctx, const char *opt) +{ + return add_opt_common(&ctx->opts, opt, 1); +} + +static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key, + int iso) +{ + if (key == FUSE_OPT_KEY_DISCARD) + return 0; + + if (key != FUSE_OPT_KEY_KEEP && ctx->proc) { + int res = ctx->proc(ctx->data, arg, key, &ctx->outargs); + if (res == -1 || !res) + return res; + } + if (iso) + return add_opt(ctx, arg); + else + return add_arg(ctx, arg); +} + +static int match_template(const char *t, const char *arg, unsigned *sepp) +{ + int arglen = strlen(arg); + const char *sep = strchr(t, '='); + sep = sep ? sep : strchr(t, ' '); + if (sep && (!sep[1] || sep[1] == '%')) { + int tlen = sep - t; + if (sep[0] == '=') + tlen ++; + if (arglen >= tlen && strncmp(arg, t, tlen) == 0) { + *sepp = sep - t; + return 1; + } + } + if (strcmp(t, arg) == 0) { + *sepp = 0; + return 1; + } + return 0; +} + +static const struct fuse_opt *find_opt(const struct fuse_opt *opt, + const char *arg, unsigned *sepp) +{ + for (; opt && opt->templ; opt++) + if (match_template(opt->templ, arg, sepp)) + return opt; + return NULL; +} + +int fuse_opt_match(const struct fuse_opt *opts, const char *opt) +{ + unsigned dummy; + return find_opt(opts, opt, &dummy) ? 1 : 0; +} + +static int process_opt_param(void *var, const char *format, const char *param, + const char *arg) +{ + assert(format[0] == '%'); + if (format[1] == 's') { + char **s = var; + char *copy = strdup(param); + if (!copy) + return alloc_failed(); + + free(*s); + *s = copy; + } else { + if (sscanf(param, format, var) != 1) { + fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg); + return -1; + } + } + return 0; +} + +static int process_opt(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + if (opt->offset == -1U) { + if (call_proc(ctx, arg, opt->value, iso) == -1) + return -1; + } else { + void *var = (char *)ctx->data + opt->offset; + if (sep && opt->templ[sep + 1]) { + const char *param = arg + sep; + if (opt->templ[sep] == '=') + param ++; + if (process_opt_param(var, opt->templ + sep + 1, + param, arg) == -1) + return -1; + } else + *(int *)var = opt->value; + } + return 0; +} + +static int process_opt_sep_arg(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + int res; + char *newarg; + char *param; + + if (next_arg(ctx, arg) == -1) + return -1; + + param = ctx->argv[ctx->argctr]; + newarg = malloc(sep + strlen(param) + 1); + if (!newarg) + return alloc_failed(); + + memcpy(newarg, arg, sep); + strcpy(newarg + sep, param); + res = process_opt(ctx, opt, sep, newarg, iso); + free(newarg); + + return res; +} + +static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso) +{ + unsigned sep; + const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep); + if (opt) { + for (; opt; opt = find_opt(opt + 1, arg, &sep)) { + int res; + if (sep && opt->templ[sep] == ' ' && !arg[sep]) + res = process_opt_sep_arg(ctx, opt, sep, arg, + iso); + else + res = process_opt(ctx, opt, sep, arg, iso); + if (res == -1) + return -1; + } + return 0; + } else + return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso); +} + +static int process_real_option_group(struct fuse_opt_context *ctx, char *opts) +{ + char *s = opts; + char *d = s; + int end = 0; + + while (!end) { + if (*s == '\0') + end = 1; + if (*s == ',' || end) { + int res; + + *d = '\0'; + res = process_gopt(ctx, opts, 1); + if (res == -1) + return -1; + d = opts; + } else { + if (s[0] == '\\' && s[1] != '\0') { + s++; + if (s[0] >= '0' && s[0] <= '3' && + s[1] >= '0' && s[1] <= '7' && + s[2] >= '0' && s[2] <= '7') { + *d++ = (s[0] - '0') * 0100 + + (s[1] - '0') * 0010 + + (s[2] - '0'); + s += 2; + } else { + *d++ = *s; + } + } else { + *d++ = *s; + } + } + s++; + } + + return 0; +} + +static int process_option_group(struct fuse_opt_context *ctx, const char *opts) +{ + int res; + char *copy = strdup(opts); + + if (!copy) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + res = process_real_option_group(ctx, copy); + free(copy); + return res; +} + +static int process_one(struct fuse_opt_context *ctx, const char *arg) +{ + if (ctx->nonopt || arg[0] != '-') + return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0); + else if (arg[1] == 'o') { + if (arg[2]) + return process_option_group(ctx, arg + 2); + else { + if (next_arg(ctx, arg) == -1) + return -1; + + return process_option_group(ctx, + ctx->argv[ctx->argctr]); + } + } else if (arg[1] == '-' && !arg[2]) { + if (add_arg(ctx, arg) == -1) + return -1; + ctx->nonopt = ctx->outargs.argc; + return 0; + } else + return process_gopt(ctx, arg, 0); +} + +static int opt_parse(struct fuse_opt_context *ctx) +{ + if (ctx->argc) { + if (add_arg(ctx, ctx->argv[0]) == -1) + return -1; + } + + for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++) + if (process_one(ctx, ctx->argv[ctx->argctr]) == -1) + return -1; + + if (ctx->opts) { + if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 || + fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1) + return -1; + } + + /* If option separator ("--") is the last argument, remove it */ + if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc && + strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) { + free(ctx->outargs.argv[ctx->outargs.argc - 1]); + ctx->outargs.argv[--ctx->outargs.argc] = NULL; + } + + return 0; +} + +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc) +{ + int res; + struct fuse_opt_context ctx = { + .data = data, + .opt = opts, + .proc = proc, + }; + + if (!args || !args->argv || !args->argc) + return 0; + + ctx.argc = args->argc; + ctx.argv = args->argv; + + res = opt_parse(&ctx); + if (res != -1) { + struct fuse_args tmp = *args; + *args = ctx.outargs; + ctx.outargs = tmp; + } + free(ctx.opts); + fuse_opt_free_args(&ctx.outargs); + return res; +} diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c new file mode 100644 index 0000000..6ac7230 --- /dev/null +++ b/lib/fuse_signals.c @@ -0,0 +1,203 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Utility functions for setting signal handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_BACKTRACE +#include +#endif + +static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM }; +static int ignore_sigs[] = { SIGPIPE}; +static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV }; +static struct fuse_session *fuse_instance; + +#define BT_STACK_SZ (1024 * 1024) +static void *backtrace_buffer[BT_STACK_SZ]; + +static void dump_stack(void) +{ +#ifdef HAVE_BACKTRACE + char **strings; + + int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ); + strings = backtrace_symbols(backtrace_buffer, nptrs); + + if (strings == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n", + strerror(errno)); + return; + } + + for (int idx = 0; idx < nptrs; idx++) + fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]); + + free(strings); +#endif +} + +static void exit_handler(int sig) +{ + if (fuse_instance == NULL) + return; + + fuse_session_exit(fuse_instance); + + if (sig < 0) { + fuse_log(FUSE_LOG_ERR, + "assertion error: signal value <= 0\n"); + dump_stack(); + abort(); + fuse_instance->error = sig; + } + + fuse_instance->error = sig; +} + +static void exit_backtrace(int sig) +{ + if (fuse_instance == NULL) + return; + + fuse_session_exit(fuse_instance); + + fuse_remove_signal_handlers(fuse_instance); + fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig); + dump_stack(); + abort(); +} + + +static void do_nothing(int sig) +{ + (void) sig; +} + +static int set_one_signal_handler(int sig, void (*handler)(int), int remove) +{ + struct sigaction sa; + struct sigaction old_sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = remove ? SIG_DFL : handler; + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = 0; + + if (sigaction(sig, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == (remove ? handler : SIG_DFL) && + sigaction(sig, &sa, NULL) == -1) { + perror("fuse: cannot set signal handler"); + return -1; + } + return 0; +} + +static int _fuse_set_signal_handlers(int signals[], int nr_signals, + void (*handler)(int)) +{ + for (int idx = 0; idx < nr_signals; idx++) { + int signal = signals[idx]; + + /* + * If we used SIG_IGN instead of the do_nothing function, + * then we would be unable to tell if we set SIG_IGN (and + * thus should reset to SIG_DFL in fuse_remove_signal_handlers) + * or if it was already set to SIG_IGN (and should be left + * untouched. + */ + if (set_one_signal_handler(signal, handler, 0) == -1) { + fuse_log(FUSE_LOG_ERR, + "Failed to install signal handler for sig %d\n", + signal); + return -1; + } + } + + return 0; +} + +int fuse_set_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals; + int rc; + + nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]); + rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler); + if (rc < 0) + return rc; + + nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]); + rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing); + if (rc < 0) + return rc; + + /* + * needs to be set independently if already set, as some applications + * may have multiple sessions and might rely on traditional behavior + * that the last session is used. + */ + fuse_instance = se; + + return 0; +} + +int fuse_set_fail_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]); + + int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals, + exit_backtrace); + if (rc < 0) + return rc; + + /* See fuse_set_signal_handlers, why set unconditionally */ + fuse_instance = se; + + return 0; +} + +static void _fuse_remove_signal_handlers(int signals[], int nr_signals, + void (*handler)(int)) +{ + for (int idx = 0; idx < nr_signals; idx++) + set_one_signal_handler(signals[idx], handler, 1); +} + +void fuse_remove_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals; + + if (fuse_instance != se) + fuse_log(FUSE_LOG_ERR, + "fuse: fuse_remove_signal_handlers: unknown session\n"); + else + fuse_instance = NULL; + + nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]); + _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler); + + nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]); + _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing); + + nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]); + _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace); +} diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript new file mode 100644 index 0000000..6c5fc83 --- /dev/null +++ b/lib/fuse_versionscript @@ -0,0 +1,207 @@ +FUSE_3.0 { + global: + fuse_destroy; + fuse_exit; + fuse_loop; + fuse_loop_mt; + fuse_reply_attr; + fuse_reply_buf; + fuse_reply_entry; + fuse_reply_err; + fuse_reply_none; + fuse_reply_readlink; + fuse_reply_write; + fuse_reply_xattr; + fuse_req_userdata; + fuse_session_destroy; + fuse_session_exit; + fuse_session_exited; + fuse_session_loop; + fuse_session_loop_mt; + fuse_session_reset; + fuse_session_fd; + fuse_opt_parse; + fuse_opt_add_opt; + fuse_opt_add_arg; + fuse_opt_free_args; + fuse_opt_match; + fuse_parse_cmdline; + fuse_remove_signal_handlers; + fuse_reply_create; + fuse_reply_open; + fuse_reply_statfs; + fuse_set_signal_handlers; + fuse_add_direntry; + fuse_add_direntry_plus; + fuse_daemonize; + fuse_get_session; + fuse_interrupted; + fuse_session_new; + fuse_main_real; + fuse_mount; + fuse_session_custom_io; + fuse_session_mount; + fuse_new; + fuse_opt_insert_arg; + fuse_reply_lock; + fuse_req_interrupt_func; + fuse_req_interrupted; + fuse_unmount; + fuse_session_unmount; + fuse_fs_access; + fuse_fs_bmap; + fuse_fs_chmod; + fuse_fs_chown; + fuse_fs_create; + fuse_fs_destroy; + fuse_fs_flush; + fuse_fs_fsync; + fuse_fs_fsyncdir; + fuse_fs_getattr; + fuse_fs_getxattr; + fuse_fs_init; + fuse_fs_link; + fuse_fs_listxattr; + fuse_fs_lock; + fuse_fs_mkdir; + fuse_fs_mknod; + fuse_fs_new; + fuse_fs_open; + fuse_fs_opendir; + fuse_fs_read; + fuse_fs_readdir; + fuse_fs_readlink; + fuse_fs_release; + fuse_fs_releasedir; + fuse_fs_removexattr; + fuse_fs_rename; + fuse_fs_rmdir; + fuse_fs_setxattr; + fuse_fs_statfs; + fuse_fs_symlink; + fuse_fs_truncate; + fuse_fs_unlink; + fuse_fs_utimens; + fuse_fs_write; + fuse_reply_iov; + fuse_version; + fuse_pkgversion; + fuse_reply_bmap; + cuse_lowlevel_new; + cuse_lowlevel_main; + cuse_lowlevel_setup; + cuse_lowlevel_teardown; + fuse_fs_ioctl; + fuse_fs_poll; + fuse_get_context; + fuse_getgroups; + fuse_lowlevel_notify_inval_entry; + fuse_lowlevel_notify_inval_inode; + fuse_lowlevel_notify_poll; + fuse_notify_poll; + fuse_opt_add_opt_escaped; + fuse_pollhandle_destroy; + fuse_reply_ioctl; + fuse_reply_ioctl_iov; + fuse_reply_ioctl_retry; + fuse_reply_poll; + fuse_req_ctx; + fuse_req_getgroups; + fuse_buf_copy; + fuse_buf_size; + fuse_fs_read_buf; + fuse_fs_write_buf; + fuse_lowlevel_notify_retrieve; + fuse_lowlevel_notify_store; + fuse_reply_data; + fuse_session_process_buf; + fuse_session_receive_buf; + fuse_start_cleanup_thread; + fuse_stop_cleanup_thread; + fuse_clean_cache; + fuse_lowlevel_notify_delete; + fuse_fs_flock; + fuse_fs_fallocate; + fuse_lowlevel_help; + fuse_lowlevel_version; + fuse_cmdline_help; + fuse_apply_conn_info_opts; + fuse_parse_conn_info_opts; + fuse_fs_lseek; + fuse_reply_lseek; + + local: + *; +}; + +FUSE_3.1 { + global: + fuse_lib_help; + fuse_invalidate_path; + fuse_new_30; + fuse_new_31; + fuse_new; +} FUSE_3.0; + +FUSE_3.2 { + global: + fuse_session_loop_mt; + fuse_session_loop_mt_31; + fuse_session_loop_mt_32; + fuse_loop_mt; + fuse_loop_mt_31; +} FUSE_3.1; + +FUSE_3.3 { + global: + fuse_open_channel; +} FUSE_3.2; + +FUSE_3.4 { + global: + fuse_fs_copy_file_range; +} FUSE_3.3; + +FUSE_3.7 { + global: + fuse_set_log_func; + fuse_log; +} FUSE_3.4; + +FUSE_3.12 { + global: + fuse_session_loop_mt; + fuse_session_loop_mt_312; + fuse_loop_mt; + fuse_loop_mt_32; + fuse_loop_mt_312; + fuse_loop_cfg_create; + fuse_loop_cfg_destroy; + fuse_loop_cfg_set_idle_threads; + fuse_loop_cfg_set_max_threads; + fuse_loop_cfg_set_clone_fd; + fuse_loop_cfg_convert; + fuse_parse_cmdline; + fuse_parse_cmdline_30; + fuse_parse_cmdline_312; + fuse_lowlevel_notify_expire_entry; +} FUSE_3.4; + +FUSE_3.17 { + global: + fuse_main_real_versioned; + fuse_session_new_versioned; + _fuse_new_30; + _fuse_new_31; + fuse_passthrough_open; + fuse_passthrough_close; + fuse_session_custom_io_30; + fuse_session_custom_io_317; + fuse_set_fail_signal_handlers; + fuse_log_enable_syslog; + fuse_log_close_syslog; +} FUSE_3.12; + +# Local Variables: +# indent-tabs-mode: t +# End: diff --git a/lib/helper.c b/lib/helper.c new file mode 100644 index 0000000..59dd488 --- /dev/null +++ b/lib/helper.c @@ -0,0 +1,491 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Helper functions to create (simple) standalone programs. With the + aid of these functions it should be possible to create full FUSE + file system by implementing nothing but the request handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "fuse_lowlevel.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_HELPER_OPT(t, p) \ + { t, offsetof(struct fuse_cmdline_opts, p), 1 } + +static const struct fuse_opt fuse_helper_opts[] = { + FUSE_HELPER_OPT("-h", show_help), + FUSE_HELPER_OPT("--help", show_help), + FUSE_HELPER_OPT("-V", show_version), + FUSE_HELPER_OPT("--version", show_version), + FUSE_HELPER_OPT("-d", debug), + FUSE_HELPER_OPT("debug", debug), + FUSE_HELPER_OPT("-d", foreground), + FUSE_HELPER_OPT("debug", foreground), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_HELPER_OPT("-f", foreground), + FUSE_HELPER_OPT("-s", singlethread), + FUSE_HELPER_OPT("fsname=", nodefault_subtype), + FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP), +#ifndef __FreeBSD__ + FUSE_HELPER_OPT("subtype=", nodefault_subtype), + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP), +#endif + FUSE_HELPER_OPT("clone_fd", clone_fd), + FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads), + FUSE_HELPER_OPT("max_threads=%u", max_threads), + FUSE_OPT_END +}; + +struct fuse_conn_info_opts { + int atomic_o_trunc; + int no_remote_posix_lock; + int no_remote_flock; + int splice_write; + int splice_move; + int splice_read; + int no_splice_write; + int no_splice_move; + int no_splice_read; + int auto_inval_data; + int no_auto_inval_data; + int no_readdirplus; + int no_readdirplus_auto; + int async_dio; + int no_async_dio; + int writeback_cache; + int no_writeback_cache; + int async_read; + int sync_read; + unsigned max_write; + unsigned max_readahead; + unsigned max_background; + unsigned congestion_threshold; + unsigned time_gran; + int set_max_write; + int set_max_readahead; + int set_max_background; + int set_congestion_threshold; + int set_time_gran; +}; + +#define CONN_OPTION(t, p, v) \ + { t, offsetof(struct fuse_conn_info_opts, p), v } +static const struct fuse_opt conn_info_opt_spec[] = { + CONN_OPTION("max_write=%u", max_write, 0), + CONN_OPTION("max_write=", set_max_write, 1), + CONN_OPTION("max_readahead=%u", max_readahead, 0), + CONN_OPTION("max_readahead=", set_max_readahead, 1), + CONN_OPTION("max_background=%u", max_background, 0), + CONN_OPTION("max_background=", set_max_background, 1), + CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0), + CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1), + CONN_OPTION("sync_read", sync_read, 1), + CONN_OPTION("async_read", async_read, 1), + CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1), + CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1), + CONN_OPTION("no_remote_lock", no_remote_flock, 1), + CONN_OPTION("no_remote_flock", no_remote_flock, 1), + CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1), + CONN_OPTION("splice_write", splice_write, 1), + CONN_OPTION("no_splice_write", no_splice_write, 1), + CONN_OPTION("splice_move", splice_move, 1), + CONN_OPTION("no_splice_move", no_splice_move, 1), + CONN_OPTION("splice_read", splice_read, 1), + CONN_OPTION("no_splice_read", no_splice_read, 1), + CONN_OPTION("auto_inval_data", auto_inval_data, 1), + CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1), + CONN_OPTION("readdirplus=no", no_readdirplus, 1), + CONN_OPTION("readdirplus=yes", no_readdirplus, 0), + CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1), + CONN_OPTION("readdirplus=auto", no_readdirplus, 0), + CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0), + CONN_OPTION("async_dio", async_dio, 1), + CONN_OPTION("no_async_dio", no_async_dio, 1), + CONN_OPTION("writeback_cache", writeback_cache, 1), + CONN_OPTION("no_writeback_cache", no_writeback_cache, 1), + CONN_OPTION("time_gran=%u", time_gran, 0), + CONN_OPTION("time_gran=", set_time_gran, 1), + FUSE_OPT_END +}; + + +void fuse_cmdline_help(void) +{ + printf(" -h --help print help\n" + " -V --version print version\n" + " -d -o debug enable debug output (implies -f)\n" + " -f foreground operation\n" + " -s disable multi-threaded operation\n" + " -o clone_fd use separate fuse device fd for each thread\n" + " (may improve performance)\n" + " -o max_idle_threads the maximum number of idle worker threads\n" + " allowed (default: -1)\n" + " -o max_threads the maximum number of worker threads\n" + " allowed (default: 10)\n"); +} + +static int fuse_helper_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct fuse_cmdline_opts *opts = data; + + switch (key) { + case FUSE_OPT_KEY_NONOPT: + if (!opts->mountpoint) { + if (fuse_mnt_parse_fuse_fd(arg) != -1) { + return fuse_opt_add_opt(&opts->mountpoint, arg); + } + + char mountpoint[PATH_MAX] = ""; + if (realpath(arg, mountpoint) == NULL) { + fuse_log(FUSE_LOG_ERR, + "fuse: bad mount point `%s': %s\n", + arg, strerror(errno)); + return -1; + } + return fuse_opt_add_opt(&opts->mountpoint, mountpoint); + } else { + fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg); + return -1; + } + + default: + /* Pass through unknown options */ + return 1; + } +} + +/* Under FreeBSD, there is no subtype option so this + function actually sets the fsname */ +static int add_default_subtype(const char *progname, struct fuse_args *args) +{ + int res; + char *subtype_opt; + + const char *basename = strrchr(progname, '/'); + if (basename == NULL) + basename = progname; + else if (basename[1] != '\0') + basename++; + + subtype_opt = (char *) malloc(strlen(basename) + 64); + if (subtype_opt == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } +#ifdef __FreeBSD__ + sprintf(subtype_opt, "-ofsname=%s", basename); +#else + sprintf(subtype_opt, "-osubtype=%s", basename); +#endif + res = fuse_opt_add_arg(args, subtype_opt); + free(subtype_opt); + return res; +} + +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12") +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts) +{ + memset(opts, 0, sizeof(struct fuse_cmdline_opts)); + + opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */ + opts->max_threads = 10; + + if (fuse_opt_parse(args, opts, fuse_helper_opts, + fuse_helper_opt_proc) == -1) + return -1; + + /* *Linux*: if neither -o subtype nor -o fsname are specified, + set subtype to program's basename. + *FreeBSD*: if fsname is not specified, set to program's + basename. */ + if (!opts->nodefault_subtype) + if (add_default_subtype(args->argv[0], args) == -1) + return -1; + + return 0; +} + +/** + * struct fuse_cmdline_opts got extended in libfuse-3.12 + */ +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0") +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *out_opts) +{ + struct fuse_cmdline_opts opts; + + int rc = fuse_parse_cmdline_312(args, &opts); + if (rc == 0) { + /* copy up to the size of the old pre 3.12 struct */ + memcpy(out_opts, &opts, + offsetof(struct fuse_cmdline_opts, max_idle_threads) + + sizeof(opts.max_idle_threads)); + } + + return rc; +} + +int fuse_daemonize(int foreground) +{ + if (!foreground) { + int nullfd; + int waiter[2]; + char completed; + + if (pipe(waiter)) { + perror("fuse_daemonize: pipe"); + return -1; + } + + /* + * demonize current process by forking it and killing the + * parent. This makes current process as a child of 'init'. + */ + switch(fork()) { + case -1: + perror("fuse_daemonize: fork"); + return -1; + case 0: + break; + default: + (void) read(waiter[0], &completed, sizeof(completed)); + _exit(0); + } + + if (setsid() == -1) { + perror("fuse_daemonize: setsid"); + return -1; + } + + (void) chdir("/"); + + nullfd = open("/dev/null", O_RDWR, 0); + if (nullfd != -1) { + (void) dup2(nullfd, 0); + (void) dup2(nullfd, 1); + (void) dup2(nullfd, 2); + if (nullfd > 2) + close(nullfd); + } + + /* Propagate completion of daemon initialization */ + completed = 1; + (void) write(waiter[1], &completed, sizeof(completed)); + close(waiter[0]); + close(waiter[1]); + } else { + (void) chdir("/"); + } + return 0; +} + +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse *fuse; + struct fuse_cmdline_opts opts; + int res; + struct fuse_loop_config *loop_config = NULL; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_version) { + printf("FUSE library version %s\n", PACKAGE_VERSION); + fuse_lowlevel_version(); + res = 0; + goto out1; + } + + if (opts.show_help) { + if(args.argv[0][0] != '\0') + printf("usage: %s [options] \n\n", + args.argv[0]); + printf("FUSE options:\n"); + fuse_cmdline_help(); + fuse_lib_help(&args); + res = 0; + goto out1; + } + + if (!opts.show_help && + !opts.mountpoint) { + fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n"); + res = 2; + goto out1; + } + + struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); + fuse = _fuse_new_31(&args, op, op_size, version, user_data); + if (fuse == NULL) { + res = 3; + goto out1; + } + + if (fuse_mount(fuse,opts.mountpoint) != 0) { + res = 4; + goto out2; + } + + if (fuse_daemonize(opts.foreground) != 0) { + res = 5; + goto out3; + } + + struct fuse_session *se = fuse_get_session(fuse); + if (fuse_set_signal_handlers(se) != 0) { + res = 6; + goto out3; + } + + if (opts.singlethread) + res = fuse_loop(fuse); + else { + loop_config = fuse_loop_cfg_create(); + if (loop_config == NULL) { + res = 7; + goto out3; + } + + fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd); + + fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads); + fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads); + res = fuse_loop_mt(fuse, loop_config); + } + if (res) + res = 8; + + fuse_remove_signal_handlers(se); +out3: + fuse_unmount(fuse); +out2: + fuse_destroy(fuse); +out1: + fuse_loop_cfg_destroy(loop_config); + free(opts.mountpoint); + fuse_opt_free_args(&args); + return res; +} + +/* Not symboled, as not part of the official API */ +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { 0 }; + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); +} + +void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, + struct fuse_conn_info *conn) +{ + if(opts->set_max_write) + conn->max_write = opts->max_write; + if(opts->set_max_background) + conn->max_background = opts->max_background; + if(opts->set_congestion_threshold) + conn->congestion_threshold = opts->congestion_threshold; + if(opts->set_time_gran) + conn->time_gran = opts->time_gran; + if(opts->set_max_readahead) + conn->max_readahead = opts->max_readahead; + +#define LL_ENABLE(cond,cap) \ + if (cond) conn->want_ext |= (cap) +#define LL_DISABLE(cond,cap) \ + if (cond) conn->want_ext &= ~(cap) + + LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); + LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ); + + LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE); + LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE); + + LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE); + LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE); + + LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA); + LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA); + + LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS); + LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO); + + LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO); + LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO); + + LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE); + LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE); + + LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ); + LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ); + + LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS); + LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS); +} + +struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args) +{ + struct fuse_conn_info_opts *opts; + + opts = calloc(1, sizeof(struct fuse_conn_info_opts)); + if(opts == NULL) { + fuse_log(FUSE_LOG_ERR, "calloc failed\n"); + return NULL; + } + if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) { + free(opts); + return NULL; + } + return opts; +} + +int fuse_open_channel(const char *mountpoint, const char* options) +{ + struct mount_opts *opts = NULL; + int fd = -1; + const char *argv[] = { "", "-o", options }; + int argc = sizeof(argv) / sizeof(argv[0]); + struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv); + + opts = parse_mount_opts(&args); + if (opts == NULL) + return -1; + + fd = fuse_kern_mount(mountpoint, opts); + destroy_mount_opts(opts); + + return fd; +} diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..6a52d06 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,58 @@ +libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c', + 'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c', + 'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c', + 'helper.c', 'modules/subdir.c', 'mount_util.c', + 'fuse_log.c', 'compat.c', 'util.c', 'util.h' ] + +if host_machine.system().startswith('linux') + libfuse_sources += [ 'mount.c' ] +else + libfuse_sources += [ 'mount_bsd.c' ] +endif + +deps = [ thread_dep ] +if private_cfg.get('HAVE_ICONV') + libfuse_sources += [ 'modules/iconv.c' ] + libiconv = cc.find_library('iconv', required: false) + if libiconv.found() + deps += [ libiconv ] + endif +endif + +libdl = cc.find_library('dl', required: false) +if libdl.found() + deps += [ libdl ] +endif + +if host_machine.system().startswith('netbsd') + deps += [ cc.find_library('perfuse'), + cc.find_library('puffs') ] +else + # Required for clock_gettime before glibc 2.17 + deps += cc.find_library('rt') +endif + +fusermount_path = join_paths(get_option('prefix'), get_option('bindir')) +libfuse = library('fuse3', + libfuse_sources, + version: base_version, + soversion: '4', + include_directories: include_dirs, + dependencies: deps, + install: true, + link_depends: 'fuse_versionscript', + c_args: [ '-DFUSE_USE_VERSION=317', + '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ], + link_args: ['-Wl,--version-script,' + meson.current_source_dir() + + '/fuse_versionscript' ]) + +pkg = import('pkgconfig') +pkg.generate(libraries: [ libfuse, '-lpthread' ], + libraries_private: '-ldl', + version: meson.project_version(), + name: 'fuse3', + description: 'Filesystem in Userspace', + subdirs: 'fuse3') + +libfuse_dep = declare_dependency(include_directories: include_dirs, + link_with: libfuse, dependencies: deps) diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c new file mode 100644 index 0000000..a0bf72b --- /dev/null +++ b/lib/modules/iconv.c @@ -0,0 +1,737 @@ +/* + fuse iconv module: file name charset conversion + Copyright (C) 2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct iconv { + struct fuse_fs *next; + pthread_mutex_t lock; + char *from_code; + char *to_code; + iconv_t tofs; + iconv_t fromfs; +}; + +struct iconv_dh { + struct iconv *ic; + void *prev_buf; + fuse_fill_dir_t prev_filler; +}; + +static struct iconv *iconv_get(void) +{ + return fuse_get_context()->private_data; +} + +static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp, + int fromfs) +{ + size_t pathlen; + size_t newpathlen; + char *newpath; + size_t plen; + char *p; + size_t res; + int err; + + if (path == NULL) { + *newpathp = NULL; + return 0; + } + + pathlen = strlen(path); + newpathlen = pathlen * 4; + newpath = malloc(newpathlen + 1); + if (!newpath) + return -ENOMEM; + + plen = newpathlen; + p = newpath; + pthread_mutex_lock(&ic->lock); + do { + res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path, + &pathlen, &p, &plen); + if (res == (size_t) -1) { + char *tmp; + size_t inc; + + err = -EILSEQ; + if (errno != E2BIG) + goto err; + + inc = (pathlen + 1) * 4; + newpathlen += inc; + int dp = p - newpath; + tmp = realloc(newpath, newpathlen + 1); + err = -ENOMEM; + if (!tmp) + goto err; + + p = tmp + dp; + plen += inc; + newpath = tmp; + } + } while (res == (size_t) -1); + pthread_mutex_unlock(&ic->lock); + *p = '\0'; + *newpathp = newpath; + return 0; + +err: + iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL); + pthread_mutex_unlock(&ic->lock); + free(newpath); + return err; +} + +static int iconv_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getattr(ic->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int iconv_access(const char *path, int mask) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_access(ic->next, newpath, mask); + free(newpath); + } + return err; +} + +static int iconv_readlink(const char *path, char *buf, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_readlink(ic->next, newpath, buf, size); + if (!err) { + char *newlink; + err = iconv_convpath(ic, buf, &newlink, 1); + if (!err) { + strncpy(buf, newlink, size - 1); + buf[size - 1] = '\0'; + free(newlink); + } + } + free(newpath); + } + return err; +} + +static int iconv_opendir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_opendir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_dir_fill(void *buf, const char *name, + const struct stat *stbuf, off_t off, + enum fuse_fill_dir_flags flags) +{ + struct iconv_dh *dh = buf; + char *newname; + int res = 0; + if (iconv_convpath(dh->ic, name, &newname, 1) == 0) { + res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags); + free(newname); + } + return res; +} + +static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + struct iconv_dh dh; + dh.ic = ic; + dh.prev_buf = buf; + dh.prev_filler = filler; + err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill, + offset, fi, flags); + free(newpath); + } + return err; +} + +static int iconv_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_releasedir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mknod(ic->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int iconv_mkdir(const char *path, mode_t mode) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mkdir(ic->next, newpath, mode); + free(newpath); + } + return err; +} + +static int iconv_unlink(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_unlink(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_rmdir(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_rmdir(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_symlink(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_symlink(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_rename(const char *from, const char *to, unsigned int flags) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_rename(ic->next, newfrom, newto, flags); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_link(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_link(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chmod(ic->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int iconv_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chown(ic->next, newpath, uid, gid, fi); + free(newpath); + } + return err; +} + +static int iconv_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_truncate(ic->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int iconv_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_utimens(ic->next, newpath, ts, fi); + free(newpath); + } + return err; +} + +static int iconv_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_create(ic->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int iconv_open_file(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_open(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_statfs(const char *path, struct statvfs *stbuf) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_statfs(ic->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int iconv_flush(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flush(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_release(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_release(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_setxattr(ic->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int iconv_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getxattr(ic->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int iconv_listxattr(const char *path, char *list, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_listxattr(ic->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int iconv_removexattr(const char *path, const char *name) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_removexattr(ic->next, newpath, name); + free(newpath); + } + return err; +} + +static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int iconv_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flock(ic->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_bmap(ic->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static off_t iconv_lseek(const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int res = iconv_convpath(ic, path, &newpath, 0); + if (!res) { + res = fuse_fs_lseek(ic->next, newpath, off, whence, fi); + free(newpath); + } + return res; +} + +static void *iconv_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + struct iconv *ic = iconv_get(); + fuse_fs_init(ic->next, conn, cfg); + /* Don't touch cfg->nullpath_ok, we can work with + either */ + return ic; +} + +static void iconv_destroy(void *data) +{ + struct iconv *ic = data; + fuse_fs_destroy(ic->next); + iconv_close(ic->tofs); + iconv_close(ic->fromfs); + pthread_mutex_destroy(&ic->lock); + free(ic->from_code); + free(ic->to_code); + free(ic); +} + +static const struct fuse_operations iconv_oper = { + .destroy = iconv_destroy, + .init = iconv_init, + .getattr = iconv_getattr, + .access = iconv_access, + .readlink = iconv_readlink, + .opendir = iconv_opendir, + .readdir = iconv_readdir, + .releasedir = iconv_releasedir, + .mknod = iconv_mknod, + .mkdir = iconv_mkdir, + .symlink = iconv_symlink, + .unlink = iconv_unlink, + .rmdir = iconv_rmdir, + .rename = iconv_rename, + .link = iconv_link, + .chmod = iconv_chmod, + .chown = iconv_chown, + .truncate = iconv_truncate, + .utimens = iconv_utimens, + .create = iconv_create, + .open = iconv_open_file, + .read_buf = iconv_read_buf, + .write_buf = iconv_write_buf, + .statfs = iconv_statfs, + .flush = iconv_flush, + .release = iconv_release, + .fsync = iconv_fsync, + .fsyncdir = iconv_fsyncdir, + .setxattr = iconv_setxattr, + .getxattr = iconv_getxattr, + .listxattr = iconv_listxattr, + .removexattr = iconv_removexattr, + .lock = iconv_lock, + .flock = iconv_flock, + .bmap = iconv_bmap, + .lseek = iconv_lseek, +}; + +static const struct fuse_opt iconv_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "from_code=%s", offsetof(struct iconv, from_code), 0 }, + { "to_code=%s", offsetof(struct iconv, to_code), 1 }, + FUSE_OPT_END +}; + +static void iconv_help(void) +{ + char *charmap; + const char *old = setlocale(LC_CTYPE, ""); + + charmap = strdup(nl_langinfo(CODESET)); + if (old) + setlocale(LC_CTYPE, old); + else + perror("setlocale"); + + printf( +" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n" +" -o to_code=CHARSET new encoding of the file names (default: %s)\n", + charmap); + free(charmap); +} + +static int iconv_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + iconv_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *iconv_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct iconv *ic; + const char *old = NULL; + const char *from; + const char *to; + + ic = calloc(1, sizeof(struct iconv)); + if (ic == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n"); + goto out_free; + } + + from = ic->from_code ? ic->from_code : "UTF-8"; + to = ic->to_code ? ic->to_code : ""; + /* FIXME: detect charset equivalence? */ + if (!to[0]) + old = setlocale(LC_CTYPE, ""); + ic->tofs = iconv_open(from, to); + if (ic->tofs == (iconv_t) -1) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n", + to, from); + goto out_free; + } + ic->fromfs = iconv_open(to, from); + if (ic->tofs == (iconv_t) -1) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n", + from, to); + goto out_iconv_close_to; + } + if (old) { + setlocale(LC_CTYPE, old); + old = NULL; + } + + ic->next = next[0]; + fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic); + if (!fs) + goto out_iconv_close_from; + + return fs; + +out_iconv_close_from: + iconv_close(ic->fromfs); +out_iconv_close_to: + iconv_close(ic->tofs); +out_free: + free(ic->from_code); + free(ic->to_code); + free(ic); + if (old) { + setlocale(LC_CTYPE, old); + } + return NULL; +} + +FUSE_REGISTER_MODULE(iconv, iconv_new); diff --git a/lib/modules/subdir.c b/lib/modules/subdir.c new file mode 100644 index 0000000..e92eb62 --- /dev/null +++ b/lib/modules/subdir.c @@ -0,0 +1,689 @@ +/* + fuse subdir module: offset paths with a base directory + Copyright (C) 2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB +*/ + +#include + +#include +#include +#include +#include +#include +#include + +struct subdir { + char *base; + size_t baselen; + int rellinks; + struct fuse_fs *next; +}; + +static struct subdir *subdir_get(void) +{ + return fuse_get_context()->private_data; +} + +static int subdir_addpath(struct subdir *d, const char *path, char **newpathp) +{ + char *newpath = NULL; + + if (path != NULL) { + unsigned newlen = d->baselen + strlen(path); + + newpath = malloc(newlen + 2); + if (!newpath) + return -ENOMEM; + + if (path[0] == '/') + path++; + strcpy(newpath, d->base); + strcpy(newpath + d->baselen, path); + if (!newpath[0]) + strcpy(newpath, "."); + } + *newpathp = newpath; + + return 0; +} + +static int subdir_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getattr(d->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int subdir_access(const char *path, int mask) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_access(d->next, newpath, mask); + free(newpath); + } + return err; +} + + +static int count_components(const char *p) +{ + int ctr; + + for (; *p == '/'; p++); + for (ctr = 0; *p; ctr++) { + for (; *p && *p != '/'; p++); + for (; *p == '/'; p++); + } + return ctr; +} + +static void strip_common(const char **sp, const char **tp) +{ + const char *s = *sp; + const char *t = *tp; + do { + for (; *s == '/'; s++); + for (; *t == '/'; t++); + *tp = t; + *sp = s; + for (; *s == *t && *s && *s != '/'; s++, t++); + } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); +} + +static void transform_symlink(struct subdir *d, const char *path, + char *buf, size_t size) +{ + const char *l = buf; + size_t llen; + char *s; + int dotdots; + int i; + + if (l[0] != '/' || d->base[0] != '/') + return; + + strip_common(&l, &path); + if (l - buf < (long) d->baselen) + return; + + dotdots = count_components(path); + if (!dotdots) + return; + dotdots--; + + llen = strlen(l); + if (dotdots * 3 + llen + 2 > size) + return; + + s = buf + dotdots * 3; + if (llen) + memmove(s, l, llen + 1); + else if (!dotdots) + strcpy(s, "."); + else + *s = '\0'; + + for (s = buf, i = 0; i < dotdots; i++, s += 3) + memcpy(s, "../", 3); +} + + +static int subdir_readlink(const char *path, char *buf, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readlink(d->next, newpath, buf, size); + if (!err && d->rellinks) + transform_symlink(d, newpath, buf, size); + free(newpath); + } + return err; +} + +static int subdir_opendir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_opendir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_readdir(const char *path, void *buf, + fuse_fill_dir_t filler, off_t offset, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readdir(d->next, newpath, buf, filler, offset, + fi, flags); + free(newpath); + } + return err; +} + +static int subdir_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_releasedir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mknod(d->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int subdir_mkdir(const char *path, mode_t mode) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mkdir(d->next, newpath, mode); + free(newpath); + } + return err; +} + +static int subdir_unlink(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_unlink(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_rmdir(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_rmdir(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_symlink(const char *from, const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_symlink(d->next, from, newpath); + free(newpath); + } + return err; +} + +static int subdir_rename(const char *from, const char *to, unsigned int flags) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_rename(d->next, newfrom, newto, flags); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_link(const char *from, const char *to) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_link(d->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chmod(d->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int subdir_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chown(d->next, newpath, uid, gid, fi); + free(newpath); + } + return err; +} + +static int subdir_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_truncate(d->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int subdir_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_utimens(d->next, newpath, ts, fi); + free(newpath); + } + return err; +} + +static int subdir_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_create(d->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int subdir_open(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_open(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_statfs(const char *path, struct statvfs *stbuf) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_statfs(d->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int subdir_flush(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flush(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_release(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_release(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsync(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_setxattr(d->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int subdir_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getxattr(d->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int subdir_listxattr(const char *path, char *list, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_listxattr(d->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int subdir_removexattr(const char *path, const char *name) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_removexattr(d->next, newpath, name); + free(newpath); + } + return err; +} + +static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_lock(d->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int subdir_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flock(d->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_bmap(d->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static off_t subdir_lseek(const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct subdir *ic = subdir_get(); + char *newpath; + int res = subdir_addpath(ic, path, &newpath); + if (!res) { + res = fuse_fs_lseek(ic->next, newpath, off, whence, fi); + free(newpath); + } + return res; +} + +static void *subdir_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + struct subdir *d = subdir_get(); + fuse_fs_init(d->next, conn, cfg); + /* Don't touch cfg->nullpath_ok, we can work with + either */ + return d; +} + +static void subdir_destroy(void *data) +{ + struct subdir *d = data; + fuse_fs_destroy(d->next); + free(d->base); + free(d); +} + +static const struct fuse_operations subdir_oper = { + .destroy = subdir_destroy, + .init = subdir_init, + .getattr = subdir_getattr, + .access = subdir_access, + .readlink = subdir_readlink, + .opendir = subdir_opendir, + .readdir = subdir_readdir, + .releasedir = subdir_releasedir, + .mknod = subdir_mknod, + .mkdir = subdir_mkdir, + .symlink = subdir_symlink, + .unlink = subdir_unlink, + .rmdir = subdir_rmdir, + .rename = subdir_rename, + .link = subdir_link, + .chmod = subdir_chmod, + .chown = subdir_chown, + .truncate = subdir_truncate, + .utimens = subdir_utimens, + .create = subdir_create, + .open = subdir_open, + .read_buf = subdir_read_buf, + .write_buf = subdir_write_buf, + .statfs = subdir_statfs, + .flush = subdir_flush, + .release = subdir_release, + .fsync = subdir_fsync, + .fsyncdir = subdir_fsyncdir, + .setxattr = subdir_setxattr, + .getxattr = subdir_getxattr, + .listxattr = subdir_listxattr, + .removexattr = subdir_removexattr, + .lock = subdir_lock, + .flock = subdir_flock, + .bmap = subdir_bmap, + .lseek = subdir_lseek, +}; + +static const struct fuse_opt subdir_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "subdir=%s", offsetof(struct subdir, base), 0 }, + { "rellinks", offsetof(struct subdir, rellinks), 1 }, + { "norellinks", offsetof(struct subdir, rellinks), 0 }, + FUSE_OPT_END +}; + +static void subdir_help(void) +{ + printf( +" -o subdir=DIR prepend this directory to all paths (mandatory)\n" +" -o [no]rellinks transform absolute symlinks to relative\n"); +} + +static int subdir_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + subdir_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *subdir_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct subdir *d; + + d = calloc(1, sizeof(struct subdir)); + if (d == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n"); + goto out_free; + } + + if (!d->base) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n"); + goto out_free; + } + + if (d->base[0] && d->base[strlen(d->base)-1] != '/') { + char *tmp = realloc(d->base, strlen(d->base) + 2); + if (!tmp) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); + goto out_free; + } + d->base = tmp; + strcat(d->base, "/"); + } + d->baselen = strlen(d->base); + d->next = next[0]; + fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d); + if (!fs) + goto out_free; + return fs; + +out_free: + free(d->base); + free(d); + return NULL; +} + +FUSE_REGISTER_MODULE(subdir, subdir_new); diff --git a/lib/mount.c b/lib/mount.c new file mode 100644 index 0000000..6ed4444 --- /dev/null +++ b/lib/mount.c @@ -0,0 +1,723 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Architecture specific file system mounting (Linux). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +/* For environ */ +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuse_mount_compat.h" + +#ifdef __NetBSD__ +#include + +#define MS_RDONLY MNT_RDONLY +#define MS_NOSUID MNT_NOSUID +#define MS_NODEV MNT_NODEV +#define MS_NOEXEC MNT_NOEXEC +#define MS_SYNCHRONOUS MNT_SYNCHRONOUS +#define MS_NOATIME MNT_NOATIME +#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW + +#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) +#endif + +#define FUSERMOUNT_PROG "fusermount3" +#define FUSE_COMMFD_ENV "_FUSE_COMMFD" +#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2" + +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +enum { + KEY_KERN_FLAG, + KEY_KERN_OPT, + KEY_FUSERMOUNT_OPT, + KEY_SUBTYPE_OPT, + KEY_MTAB_OPT, + KEY_ALLOW_OTHER, + KEY_RO, +}; + +struct mount_opts { + int allow_other; + int flags; + int auto_unmount; + int blkdev; + char *fsname; + char *subtype; + char *subtype_opt; + char *mtab_opts; + char *fusermount_opts; + char *kernel_opts; + unsigned max_read; +}; + +#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 } + +static const struct fuse_opt fuse_mount_opts[] = { + FUSE_MOUNT_OPT("allow_other", allow_other), + FUSE_MOUNT_OPT("blkdev", blkdev), + FUSE_MOUNT_OPT("auto_unmount", auto_unmount), + FUSE_MOUNT_OPT("fsname=%s", fsname), + FUSE_MOUNT_OPT("max_read=%u", max_read), + FUSE_MOUNT_OPT("subtype=%s", subtype), + FUSE_OPT_KEY("allow_other", KEY_KERN_OPT), + FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT), + FUSE_OPT_KEY("blksize=", KEY_KERN_OPT), + FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT), + FUSE_OPT_KEY("context=", KEY_KERN_OPT), + FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("max_read=", KEY_KERN_OPT), + FUSE_OPT_KEY("user=", KEY_MTAB_OPT), + FUSE_OPT_KEY("-n", KEY_MTAB_OPT), + FUSE_OPT_KEY("-r", KEY_RO), + FUSE_OPT_KEY("ro", KEY_KERN_FLAG), + FUSE_OPT_KEY("rw", KEY_KERN_FLAG), + FUSE_OPT_KEY("suid", KEY_KERN_FLAG), + FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG), + FUSE_OPT_KEY("dev", KEY_KERN_FLAG), + FUSE_OPT_KEY("nodev", KEY_KERN_FLAG), + FUSE_OPT_KEY("exec", KEY_KERN_FLAG), + FUSE_OPT_KEY("noexec", KEY_KERN_FLAG), + FUSE_OPT_KEY("async", KEY_KERN_FLAG), + FUSE_OPT_KEY("sync", KEY_KERN_FLAG), + FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG), + FUSE_OPT_KEY("noatime", KEY_KERN_FLAG), + FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG), + FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG), + FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG), + FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG), + FUSE_OPT_END +}; + +/* + * Running fusermount by calling 'posix_spawn' + * + * @param out_pid might be NULL + */ +static int fusermount_posix_spawn(posix_spawn_file_actions_t *action, + char const * const argv[], pid_t *out_pid) +{ + const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG; + pid_t pid; + + /* See man 7 environ for the global environ pointer */ + + /* first try the install path */ + int status = posix_spawn(&pid, full_path, action, NULL, + (char * const *) argv, environ); + if (status != 0) { + /* if that fails, try a system install */ + status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL, + (char * const *) argv, environ); + } + + if (status != 0) { + fuse_log(FUSE_LOG_ERR, + "On calling fusermount posix_spawn failed: %s\n", + strerror(status)); + return -status; + } + + if (out_pid) + *out_pid = pid; + else + waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */ + + return 0; +} + +void fuse_mount_version(void) +{ + char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL}; + int status = fusermount_posix_spawn(NULL, argv, NULL); + + if(status != 0) + fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed", + FUSERMOUNT_PROG); +} + +struct mount_flags { + const char *opt; + unsigned long flag; + int on; +}; + +static const struct mount_flags mount_flags[] = { + {"rw", MS_RDONLY, 0}, + {"ro", MS_RDONLY, 1}, + {"suid", MS_NOSUID, 0}, + {"nosuid", MS_NOSUID, 1}, + {"dev", MS_NODEV, 0}, + {"nodev", MS_NODEV, 1}, + {"exec", MS_NOEXEC, 0}, + {"noexec", MS_NOEXEC, 1}, + {"async", MS_SYNCHRONOUS, 0}, + {"sync", MS_SYNCHRONOUS, 1}, + {"noatime", MS_NOATIME, 1}, + {"nodiratime", MS_NODIRATIME, 1}, + {"norelatime", MS_RELATIME, 0}, + {"nostrictatime", MS_STRICTATIME, 0}, + {"symfollow", MS_NOSYMFOLLOW, 0}, + {"nosymfollow", MS_NOSYMFOLLOW, 1}, +#ifndef __NetBSD__ + {"dirsync", MS_DIRSYNC, 1}, +#endif + {NULL, 0, 0} +}; + +unsigned get_max_read(struct mount_opts *o) +{ + return o->max_read; +} + +static void set_mount_flag(const char *s, int *flags) +{ + int i; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + const char *opt = mount_flags[i].opt; + if (strcmp(opt, s) == 0) { + if (mount_flags[i].on) + *flags |= mount_flags[i].flag; + else + *flags &= ~mount_flags[i].flag; + return; + } + } + fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n"); + abort(); +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct mount_opts *mo = data; + + switch (key) { + case KEY_RO: + arg = "ro"; + /* fall through */ + case KEY_KERN_FLAG: + set_mount_flag(arg, &mo->flags); + return 0; + + case KEY_KERN_OPT: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + + case KEY_FUSERMOUNT_OPT: + return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg); + + case KEY_SUBTYPE_OPT: + return fuse_opt_add_opt(&mo->subtype_opt, arg); + + case KEY_MTAB_OPT: + return fuse_opt_add_opt(&mo->mtab_opts, arg); + + /* Third party options like 'x-gvfs-notrash' */ + case FUSE_OPT_KEY_OPT: + return (strncmp("x-", arg, 2) == 0) ? + fuse_opt_add_opt(&mo->mtab_opts, arg) : + 1; + } + + /* Pass through unknown options */ + return 1; +} + +/* return value: + * >= 0 => fd + * -1 => error + */ +static int receive_fd(int fd) +{ + struct msghdr msg; + struct iovec iov; + char buf[1]; + int rv; + size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)]; + struct cmsghdr *cmsg; + + iov.iov_base = buf; + iov.iov_len = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + /* old BSD implementations should use msg_accrights instead of + * msg_control; the interface is different. */ + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof(ccmsg); + + while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR); + if (rv == -1) { + fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno)); + return -1; + } + if(!rv) { + /* EOF */ + return -1; + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_type != SCM_RIGHTS) { + fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n", + cmsg->cmsg_type); + return -1; + } + return *(int*)CMSG_DATA(cmsg); +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + int res; + + if (fd != -1) { + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = 0; + res = poll(&pfd, 1, 0); + + /* Need to close file descriptor, otherwise synchronous umount + would recurse into filesystem, and deadlock. + + Caller expects fuse_kern_unmount to close the fd, so close it + anyway. */ + close(fd); + + /* If file poll returns POLLERR on the device file descriptor, + then the filesystem is already unmounted or the connection + was severed via /sys/fs/fuse/connections/NNN/abort */ + if (res == 1 && (pfd.revents & POLLERR)) + return; + } + + if (geteuid() == 0) { + fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); + return; + } + + res = umount2(mountpoint, 2); + if (res == 0) + return; + + char const * const argv[] = + { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy", + "--", mountpoint, NULL }; + int status = fusermount_posix_spawn(NULL, argv, NULL); + if(status != 0) { + fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s", + FUSERMOUNT_PROG, strerror(-status)); + return; + } +} + +static int setup_auto_unmount(const char *mountpoint, int quiet) +{ + int fds[2]; + pid_t pid; + int res; + + if (!mountpoint) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); + if(res == -1) { + fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed", + strerror(errno)); + return -1; + } + + char arg_fd_entry[30]; + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); + setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); + + char const *const argv[] = { + FUSERMOUNT_PROG, + "--auto-unmount", + "--", + mountpoint, + NULL, + }; + + // TODO: add error handling for all manipulations of action. + posix_spawn_file_actions_t action; + posix_spawn_file_actions_init(&action); + + if (quiet) { + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + } + posix_spawn_file_actions_addclose(&action, fds[1]); + + /* + * auto-umount runs in the background - it is not waiting for the + * process + */ + int status = fusermount_posix_spawn(&action, argv, &pid); + + posix_spawn_file_actions_destroy(&action); + + if(status != 0) { + close(fds[0]); + close(fds[1]); + fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s", + strerror(-status)); + return -1; + } + // passed to child now, so can close here. + close(fds[0]); + + // Now fusermount3 will only exit when fds[1] closes automatically when our + // process exits. + return 0; + // Note: fds[1] is leakend and doesn't get FD_CLOEXEC +} + +static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, + const char *opts, int quiet) +{ + int fds[2]; + pid_t pid; + int res; + + if (!mountpoint) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); + if(res == -1) { + fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n", + FUSERMOUNT_PROG, strerror(errno)); + return -1; + } + + char arg_fd_entry[30]; + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); + setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); + + char const *const argv[] = { + FUSERMOUNT_PROG, + "-o", opts ? opts : "", + "--", + mountpoint, + NULL, + }; + + + posix_spawn_file_actions_t action; + posix_spawn_file_actions_init(&action); + + if (quiet) { + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + } + posix_spawn_file_actions_addclose(&action, fds[1]); + + int status = fusermount_posix_spawn(&action, argv, &pid); + + posix_spawn_file_actions_destroy(&action); + + if(status != 0) { + close(fds[0]); + close(fds[1]); + fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s", + FUSERMOUNT_PROG, strerror(-status)); + return -1; + } + + // passed to child now, so can close here. + close(fds[0]); + + int fd = receive_fd(fds[1]); + + if (!mo->auto_unmount) { + /* with auto_unmount option fusermount3 will not exit until + this socket is closed */ + close(fds[1]); + waitpid(pid, NULL, 0); /* bury zombie */ + } + + if (fd >= 0) + fcntl(fd, F_SETFD, FD_CLOEXEC); + + return fd; +} + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, + const char *mnt_opts) +{ + char tmp[128]; + const char *devname = "/dev/fuse"; + char *source = NULL; + char *type = NULL; + struct stat stbuf; + int fd; + int res; + + if (!mnt) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = stat(mnt, &stbuf); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n", + mnt, strerror(errno)); + return -1; + } + + fd = open(devname, O_RDWR | O_CLOEXEC); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n"); + else + fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", + devname, strerror(errno)); + return -1; + } + if (!O_CLOEXEC) + fcntl(fd, F_SETFD, FD_CLOEXEC); + + snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u", + fd, stbuf.st_mode & S_IFMT, getuid(), getgid()); + + res = fuse_opt_add_opt(&mo->kernel_opts, tmp); + if (res == -1) + goto out_close; + + source = malloc((mo->fsname ? strlen(mo->fsname) : 0) + + (mo->subtype ? strlen(mo->subtype) : 0) + + strlen(devname) + 32); + + type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32); + if (!type || !source) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n"); + goto out_close; + } + + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->subtype) { + strcat(type, "."); + strcat(type, mo->subtype); + } + strcpy(source, + mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname)); + + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + if (res == -1 && errno == ENODEV && mo->subtype) { + /* Probably missing subtype support */ + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->fsname) { + if (!mo->blkdev) + sprintf(source, "%s#%s", mo->subtype, + mo->fsname); + } else { + strcpy(source, type); + } + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + } + if (res == -1) { + /* + * Maybe kernel doesn't support unprivileged mounts, in this + * case try falling back to fusermount3 + */ + if (errno == EPERM) { + res = -2; + } else { + int errno_save = errno; + if (mo->blkdev && errno == ENODEV && + !fuse_mnt_check_fuseblk()) + fuse_log(FUSE_LOG_ERR, + "fuse: 'fuseblk' support missing\n"); + else + fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n", + strerror(errno_save)); + } + + goto out_close; + } + +#ifndef IGNORE_MTAB + if (geteuid() == 0) { + char *newmnt = fuse_mnt_resolve_path("fuse", mnt); + res = -1; + if (!newmnt) + goto out_umount; + + res = fuse_mnt_add_mount("fuse", source, newmnt, type, + mnt_opts); + free(newmnt); + if (res == -1) + goto out_umount; + } +#endif /* IGNORE_MTAB */ + free(type); + free(source); + + return fd; + +out_umount: + umount2(mnt, 2); /* lazy umount */ +out_close: + free(type); + free(source); + close(fd); + return res; +} + +static int get_mnt_flag_opts(char **mnt_optsp, int flags) +{ + int i; + + if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1) + return -1; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + if (mount_flags[i].on && (flags & mount_flags[i].flag) && + fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1) + return -1; + } + return 0; +} + +struct mount_opts *parse_mount_opts(struct fuse_args *args) +{ + struct mount_opts *mo; + + mo = (struct mount_opts*) malloc(sizeof(struct mount_opts)); + if (mo == NULL) + return NULL; + + memset(mo, 0, sizeof(struct mount_opts)); + mo->flags = MS_NOSUID | MS_NODEV; + + if (args && + fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + goto err_out; + + return mo; + +err_out: + destroy_mount_opts(mo); + return NULL; +} + +void destroy_mount_opts(struct mount_opts *mo) +{ + free(mo->fsname); + free(mo->subtype); + free(mo->fusermount_opts); + free(mo->subtype_opt); + free(mo->kernel_opts); + free(mo->mtab_opts); + free(mo); +} + + +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo) +{ + int res = -1; + char *mnt_opts = NULL; + + res = -1; + if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1) + goto out; + if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1) + goto out; + if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1) + goto out; + + res = fuse_mount_sys(mountpoint, mo, mnt_opts); + if (res >= 0 && mo->auto_unmount) { + if(0 > setup_auto_unmount(mountpoint, 0)) { + // Something went wrong, let's umount like in fuse_mount_sys. + umount2(mountpoint, MNT_DETACH); /* lazy umount */ + res = -1; + } + } else if (res == -2) { + if (mo->fusermount_opts && + fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1) + goto out; + + if (mo->subtype) { + char *tmp_opts = NULL; + + res = -1; + if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 || + fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) { + free(tmp_opts); + goto out; + } + + res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1); + free(tmp_opts); + if (res == -1) + res = fuse_mount_fusermount(mountpoint, mo, + mnt_opts, 0); + } else { + res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0); + } + } +out: + free(mnt_opts); + return res; +} diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c new file mode 100644 index 0000000..bd95a76 --- /dev/null +++ b/lib/mount_bsd.c @@ -0,0 +1,266 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2005-2008 Csaba Henk + + Architecture specific file system mounting (FreeBSD). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "util.h" + +#include +#include "fuse_mount_compat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSERMOUNT_PROG "mount_fusefs" +#define FUSE_DEV_TRUNK "/dev/fuse" + +enum { + KEY_RO, + KEY_KERN +}; + +struct mount_opts { + int allow_other; + char *kernel_opts; + unsigned max_read; +}; + +#define FUSE_DUAL_OPT_KEY(templ, key) \ + FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key) + +static const struct fuse_opt fuse_mount_opts[] = { + { "allow_other", offsetof(struct mount_opts, allow_other), 1 }, + { "max_read=%u", offsetof(struct mount_opts, max_read), 1 }, + FUSE_OPT_KEY("-r", KEY_RO), + /* standard FreeBSD mount options */ + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("async", KEY_KERN), + FUSE_DUAL_OPT_KEY("atime", KEY_KERN), + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("exec", KEY_KERN), + FUSE_DUAL_OPT_KEY("suid", KEY_KERN), + FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN), + FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN), + FUSE_DUAL_OPT_KEY("sync", KEY_KERN), + FUSE_DUAL_OPT_KEY("union", KEY_KERN), + FUSE_DUAL_OPT_KEY("userquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN), + FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN), + FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN), + FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN), + FUSE_DUAL_OPT_KEY("acls", KEY_KERN), + FUSE_DUAL_OPT_KEY("force", KEY_KERN), + FUSE_DUAL_OPT_KEY("update", KEY_KERN), + FUSE_DUAL_OPT_KEY("ro", KEY_KERN), + FUSE_DUAL_OPT_KEY("rw", KEY_KERN), + FUSE_DUAL_OPT_KEY("auto", KEY_KERN), + FUSE_DUAL_OPT_KEY("automounted", KEY_KERN), + /* options supported under both Linux and FBSD */ + FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN), + FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN), + FUSE_OPT_KEY("max_read=", KEY_KERN), + FUSE_OPT_KEY("subtype=", KEY_KERN), + /* FBSD FUSE specific mount options */ + FUSE_DUAL_OPT_KEY("private", KEY_KERN), + FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN), + FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN), +#if __FreeBSD_version >= 1200519 + FUSE_DUAL_OPT_KEY("intr", KEY_KERN), +#endif + /* stock FBSD mountopt parsing routine lets anything be negated... */ + /* + * Linux specific mount options, but let just the mount util + * handle them + */ + FUSE_OPT_KEY("fsname=", KEY_KERN), + FUSE_OPT_END +}; + +void fuse_mount_version(void) +{ + system(FUSERMOUNT_PROG " --version"); +} + +unsigned get_max_read(struct mount_opts *o) +{ + return o->max_read; +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct mount_opts *mo = data; + + switch (key) { + case KEY_RO: + arg = "ro"; + /* fall through */ + + case KEY_KERN: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + } + + /* Pass through unknown options */ + return 1; +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + if (close(fd) < 0) + fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno)); + if (unmount(mountpoint, MNT_FORCE) < 0) + fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s", + mountpoint, strerror(errno)); +} + +static int fuse_mount_core(const char *mountpoint, const char *opts) +{ + const char *mountprog = FUSERMOUNT_PROG; + long fd; + char *fdnam, *dev; + pid_t pid, cpid; + int status; + int err; + + fdnam = getenv("FUSE_DEV_FD"); + + if (fdnam) { + err = libfuse_strtol(fdnam, &fd); + if (err || fd < 0) { + fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n"); + return -1; + } + + goto mount; + } + + dev = getenv("FUSE_DEV_NAME"); + + if (! dev) + dev = (char *)FUSE_DEV_TRUNK; + + if ((fd = open(dev, O_RDWR)) < 0) { + perror("fuse: failed to open fuse device"); + return -1; + } + +mount: + if (getenv("FUSE_NO_MOUNT") || ! mountpoint) + goto out; + + pid = fork(); + cpid = pid; + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + return -1; + } + + if (pid == 0) { + pid = fork(); + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + _exit(EXIT_FAILURE); + } + + if (pid == 0) { + const char *argv[32]; + int a = 0; + int ret = -1; + + if (! fdnam) + { + ret = asprintf(&fdnam, "%ld", fd); + if(ret == -1) + { + perror("fuse: failed to assemble mount arguments"); + close(fd); + _exit(EXIT_FAILURE); + } + } + + argv[a++] = mountprog; + if (opts) { + argv[a++] = "-o"; + argv[a++] = opts; + } + argv[a++] = fdnam; + argv[a++] = mountpoint; + argv[a++] = NULL; + execvp(mountprog, (char **) argv); + perror("fuse: failed to exec mount program"); + free(fdnam); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { + perror("fuse: failed to mount file system"); + if (close(fd) < 0) + perror("fuse: closing FD"); + return -1; + } + +out: + return fd; +} + +struct mount_opts *parse_mount_opts(struct fuse_args *args) +{ + struct mount_opts *mo; + + mo = (struct mount_opts*) malloc(sizeof(struct mount_opts)); + if (mo == NULL) + return NULL; + + memset(mo, 0, sizeof(struct mount_opts)); + + if (args && + fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + goto err_out; + + return mo; + +err_out: + destroy_mount_opts(mo); + return NULL; +} + +void destroy_mount_opts(struct mount_opts *mo) +{ + free(mo->kernel_opts); + free(mo); +} + +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo) +{ + /* mount util should not try to spawn the daemon */ + setenv("MOUNT_FUSEFS_SAFE", "1", 1); + /* to notify the mount util it's called from lib */ + setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1); + + return fuse_mount_core(mountpoint, mo->kernel_opts); +} diff --git a/lib/mount_util.c b/lib/mount_util.c new file mode 100644 index 0000000..089ca45 --- /dev/null +++ b/lib/mount_util.c @@ -0,0 +1,373 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Architecture-independent mounting code. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include "fuse_config.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__) +#include +#else +#define IGNORE_MTAB +#endif +#include +#include + +#include "fuse_mount_compat.h" + +#include + +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) +#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0) +#endif + +#ifdef IGNORE_MTAB +#define mtab_needs_update(mnt) 0 +#else +static int mtab_needs_update(const char *mnt) +{ + int res; + struct stat stbuf; + + /* If mtab is within new mount, don't touch it */ + if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 && + _PATH_MOUNTED[strlen(mnt)] == '/') + return 0; + + /* + * Skip mtab update if /etc/mtab: + * + * - doesn't exist, + * - is on a read-only filesystem. + */ + res = lstat(_PATH_MOUNTED, &stbuf); + if (res == -1) { + if (errno == ENOENT) + return 0; + } else { + uid_t ruid; + int err; + + ruid = getuid(); + if (ruid != 0) + setreuid(0, -1); + + res = access(_PATH_MOUNTED, W_OK); + err = (res == -1) ? errno : 0; + if (ruid != 0) + setreuid(ruid, -1); + + if (err == EROFS) + return 0; + } + + return 1; +} +#endif /* IGNORE_MTAB */ + +static int add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i", + "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env); + fprintf(stderr, "%s: failed to execute /bin/mount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + return res; +} + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return add_mount(progname, fsname, mnt, type, opts); +} + +static int exec_umount(const char *progname, const char *rel_mnt, int lazy) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + if (lazy) { + execle("/bin/umount", "/bin/umount", "-i", rel_mnt, + "-l", NULL, &env); + } else { + execle("/bin/umount", "/bin/umount", "-i", rel_mnt, + NULL, &env); + } + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) { + res = -1; + } + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; + +} + +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy) +{ + int res; + + if (!mtab_needs_update(abs_mnt)) { + res = umount2(rel_mnt, lazy ? 2 : 0); + if (res == -1) + fprintf(stderr, "%s: failed to unmount %s: %s\n", + progname, abs_mnt, strerror(errno)); + return res; + } + + return exec_umount(progname, rel_mnt, lazy); +} + +static int remove_mount(const char *progname, const char *mnt) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i", + "--fake", mnt, NULL, &env); + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; +} + +int fuse_mnt_remove_mount(const char *progname, const char *mnt) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return remove_mount(progname, mnt); +} + +char *fuse_mnt_resolve_path(const char *progname, const char *orig) +{ + char buf[PATH_MAX]; + char *copy; + char *dst; + char *end; + char *lastcomp; + const char *toresolv; + + if (!orig[0]) { + fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname, + orig); + return NULL; + } + + copy = strdup(orig); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return NULL; + } + + toresolv = copy; + lastcomp = NULL; + for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --); + if (end[0] != '/') { + char *tmp; + end[1] = '\0'; + tmp = strrchr(copy, '/'); + if (tmp == NULL) { + lastcomp = copy; + toresolv = "."; + } else { + lastcomp = tmp + 1; + if (tmp == copy) + toresolv = "/"; + } + if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) { + lastcomp = NULL; + toresolv = copy; + } + else if (tmp) + tmp[0] = '\0'; + } + if (realpath(toresolv, buf) == NULL) { + fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig, + strerror(errno)); + free(copy); + return NULL; + } + if (lastcomp == NULL) + dst = strdup(buf); + else { + dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1); + if (dst) { + unsigned buflen = strlen(buf); + if (buflen && buf[buflen-1] == '/') + sprintf(dst, "%s%s", buf, lastcomp); + else + sprintf(dst, "%s/%s", buf, lastcomp); + } + } + free(copy); + if (dst == NULL) + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return dst; +} + +int fuse_mnt_check_fuseblk(void) +{ + char buf[256]; + FILE *f = fopen("/proc/filesystems", "r"); + if (!f) + return 1; + + while (fgets(buf, sizeof(buf), f)) + if (strstr(buf, "fuseblk\n")) { + fclose(f); + return 1; + } + + fclose(f); + return 0; +} + +int fuse_mnt_parse_fuse_fd(const char *mountpoint) +{ + int fd = -1; + int len = 0; + + if (mountpoint == NULL) { + fprintf(stderr, "Invalid null-ptr mount-point!\n"); + return -1; + } + + if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 && + len == strlen(mountpoint)) { + return fd; + } + + return -1; +} diff --git a/lib/mount_util.h b/lib/mount_util.h new file mode 100644 index 0000000..0ef0fbe --- /dev/null +++ b/lib/mount_util.h @@ -0,0 +1,18 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file COPYING.LIB. +*/ + +#include + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts); +int fuse_mnt_remove_mount(const char *progname, const char *mnt); +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy); +char *fuse_mnt_resolve_path(const char *progname, const char *orig); +int fuse_mnt_check_fuseblk(void); +int fuse_mnt_parse_fuse_fd(const char *mountpoint); diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..a529d38 --- /dev/null +++ b/lib/util.c @@ -0,0 +1,27 @@ +#include +#include + +#include "util.h" + +int libfuse_strtol(const char *str, long *res) +{ + char *endptr; + int base = 10; + long val; + + errno = 0; + + if (!str) + return -EINVAL; + + val = strtol(str, &endptr, base); + + if (errno) + return -errno; + + if (endptr == str || *endptr != '\0') + return -EINVAL; + + *res = val; + return 0; +} diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 0000000..508fafb --- /dev/null +++ b/lib/util.h @@ -0,0 +1,33 @@ +#ifndef FUSE_UTIL_H_ +#define FUSE_UTIL_H_ + +#include + +#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1)) + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +int libfuse_strtol(const char *str, long *res); + +/** + * Return the low bits of a number + */ +static inline uint32_t fuse_lower_32_bits(uint64_t nr) +{ + return (uint32_t)(nr & 0xffffffff); +} + +/** + * Return the high bits of a number + */ +static inline uint64_t fuse_higher_32_bits(uint64_t nr) +{ + return nr & ~0xffffffffULL; +} + +#ifndef FUSE_VAR_UNUSED +#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var) +#endif + +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..ba551ed --- /dev/null +++ b/meson.build @@ -0,0 +1,251 @@ +project('libfuse3', ['c'], + version: '3.17.2', + meson_version: '>= 0.51.0', + default_options: [ + 'buildtype=debugoptimized', + 'c_std=gnu11', + 'cpp_std=c++17', + 'warning_level=2', + ]) + +# Would be better to create the version string +# from integers, i.e. concatenating strings instead +# of splitting a string, but 'project' needs to be +# the first meson.build keyword... + +# split version into base and rc +version_parts = meson.project_version().split('-') +base_version = version_parts[0] + +version_list = base_version.split('.') +FUSE_MAJOR_VERSION = version_list[0] +FUSE_MINOR_VERSION = version_list[1] +FUSE_HOTFIX_VERSION = version_list[2] +FUSE_RC_VERSION = version_parts.length() > 1 ? version_parts[1] : '' + +platform = host_machine.system() +if platform == 'darwin' + error('libfuse does not support OS-X.\n' + + 'Take a look at http://osxfuse.github.io/ or the more recent\n' + + 'https://www.fuse-t.org/ instead') +elif platform == 'cygwin' or platform == 'windows' + error('libfuse does not support Windows.\n' + + 'Take a look at http://www.secfs.net/winfsp/ instead') +endif + +cc = meson.get_compiler('c') + +# +# Feature detection, the resulting config file is installed +# with the package. +# Note: Symbols need to be care fully named, to avoid conflicts +# with applications linking to libfuse and including +# this config. +# +public_cfg = configuration_data() + +public_cfg.set('FUSE_MAJOR_VERSION', FUSE_MAJOR_VERSION) +public_cfg.set('FUSE_MINOR_VERSION', FUSE_MINOR_VERSION) +public_cfg.set('FUSE_HOTFIX_VERSION', FUSE_HOTFIX_VERSION) +public_cfg.set('FUSE_RC_VERSION', FUSE_RC_VERSION) + +# Default includes when checking for presence of functions and +# struct members +include_default = ''' +#include +#include +#include +#include +#include +#include +#include +''' +args_default = [ '-D_GNU_SOURCE' ] + +# +# Feature detection, only available at libfuse compilation time, +# but not for application linking to libfuse. +# +private_cfg = configuration_data() +private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) + +# Test for presence of some functions +test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', + 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', + 'utimensat', 'copy_file_range', 'fallocate', 'static_assert', + 'pthread_setname_np' ] +foreach func : test_funcs + private_cfg.set('HAVE_' + func.to_upper(), + cc.has_function(func, prefix: include_default, args: args_default)) +endforeach +private_cfg.set('HAVE_SETXATTR', + cc.has_function('setxattr', prefix: '#include ')) +private_cfg.set('HAVE_ICONV', + cc.has_function('iconv', prefix: '#include ')) +private_cfg.set('HAVE_BACKTRACE', + cc.has_function('backtrace', prefix: '#include ')) + +# Test if headers exist +private_cfg.set('HAVE_LINUX_CLOSE_RANGE_H', + cc.check_header('#include ')) + +# Test if structs have specific member +private_cfg.set('HAVE_STRUCT_STAT_ST_ATIM', + cc.has_member('struct stat', 'st_atim', + prefix: include_default, + args: args_default)) +private_cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC', + cc.has_member('struct stat', 'st_atimespec', + prefix: include_default, + args: args_default)) + +# +# Compiler configuration +# +add_project_arguments('-D_REENTRANT', '-DHAVE_LIBFUSE_PRIVATE_CONFIG_H', '-Wno-sign-compare', '-D_FILE_OFFSET_BITS=64', + '-Wstrict-prototypes', '-Wmissing-declarations', '-Wwrite-strings', + '-fno-strict-aliasing', language: 'c') +add_project_arguments('-D_REENTRANT', '-DHAVE_LIBFUSE_PRIVATE_CONFIG_H', '-D_GNU_SOURCE', '-D_FILE_OFFSET_BITS=64', + '-Wno-sign-compare', '-Wmissing-declarations', + '-Wwrite-strings', '-fno-strict-aliasing', language: 'cpp') + +# Some (stupid) GCC versions warn about unused return values even when they are +# casted to void. This makes -Wunused-result pretty useless, since there is no +# way to suppress the warning when we really *want* to ignore the value. +code = ''' +__attribute__((warn_unused_result)) int get_4() { + return 4; +} +int main(void) { + (void) get_4(); + return 0; +}''' +if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ]) + message('Compiler warns about unused result even when casting to void') + add_project_arguments('-Wno-unused-result', language: 'c') +endif + +# It is hard to detect if the libc supports versioned symbols. Only gnu-libc +# seems to provide that, but then glibc is the main target for libfuse, so +# enable it by default +versioned_symbols = 1 + +# This is an attempt to detect if another libc is used. +code = ''' +int main(void) { +#if (defined(__UCLIBC__) || defined(__APPLE__)) +#error /* libc does not have versioned symbols */ +#endif + return 0; +}''' +if not cc.compiles(code, args: [ '-O0' ]) + versioned_symbols = 0 +endif + +# The detection can be overridden, which is useful for other (above unhandled) +# libcs and also especially useful for testing +if get_option('disable-libc-symbol-version') + versioned_symbols = 0 +endif + +if versioned_symbols == 1 + message('Enabling versioned libc symbols') + public_cfg.set('LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS', 1) + + # gcc-10 and newer support the symver attribute which we need to use if we + # want to support LTO + # recent clang and gcc both support __has_attribute (and if they are too old + # to have __has_attribute, then they are too old to support symver) + # other compilers might not have __has_attribute, but in those cases + # it is safe for this check to fail and for us to fallback to the old _asm_ + # method for symver. Anyway the attributes not supported by __has_attribute() + # unfortunately return true giving a false positive. So let's try to build + # using __attribute__ ((symver )) and see the result. + code = ''' + __attribute__ ((symver ("test@TEST"))) + void foo(void) { + } + + int main(void) { + return 0; + }''' + if cc.compiles(code, args: [ '-O0', '-c', '-Werror']) + message('Compiler supports symver attribute') + add_project_arguments('-DHAVE_SYMVER_ATTRIBUTE', language: 'c') + else + message('Compiler does not support symver attribute') + endif +else + message('Disabling versioned libc symbols') +endif + +# Older versions of musl libc don't unescape entries in /etc/mtab +# Try to detect this behaviour, and work around, if necessary. +detect_getmntent_needs_unescape = ''' +#define _GNU_SOURCE +#include +#include +#include +#include + +#define dir_space_tab "dir\\040space\\011tab" + +int main() +{ + const char *fake_mtab = "name " dir_space_tab " type opts 0 0\n"; + FILE *f = fmemopen((void *)fake_mtab, strlen(fake_mtab) + 1, "r"); + struct mntent *entp = getmntent(f); + fclose(f); + if(NULL == entp) + exit(EXIT_FAILURE); + if (0 == strcmp(entp->mnt_dir, dir_space_tab)) + printf("needs escaping\n"); + else + printf("no need to escape\n"); +} +''' + +if not meson.is_cross_build() + result = cc.run(detect_getmntent_needs_unescape) + if result.compiled() and result.returncode() == 0 and result.stdout().strip() == 'needs escaping' + message('getmntent does not unescape') + add_project_arguments('-DGETMNTENT_NEEDS_UNESCAPING', language: 'c') + endif +endif + +# Write private test results into fuse_config.h (stored in build directory) +configure_file(output: 'fuse_config.h', configuration : private_cfg) + +# Write the test results, installed with the package, +# symbols need to be properly prefixed to avoid +# symbol (define) conflicts +configure_file(output: 'libfuse_config.h', + configuration : public_cfg, + install: true, install_dir: join_paths(get_option('includedir'), 'fuse3')) + +# '.' will refer to current build directory, which contains config.h +include_dirs = include_directories('include', 'lib', '.') + +# Common dependencies +thread_dep = dependency('threads') + +# +# Read build files from sub-directories +# +subdirs = [ 'lib', 'include'] +if get_option('utils') and not platform.endswith('bsd') and platform != 'dragonfly' + subdirs += [ 'util', 'doc' ] +endif + +if get_option('examples') + subdirs += 'example' +endif + +if get_option('tests') + subdirs += 'test' +endif + +foreach n : subdirs + subdir(n) +endforeach + diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..fa4749c --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,24 @@ +option('disable-mtab', type : 'boolean', value : false, + description: 'Disable and ignore usage of /etc/mtab') + +option('udevrulesdir', type : 'string', value : '', + description: 'Where to install udev rules (if empty, query pkg-config(1))') + +option('initscriptdir', type : 'string', value : '/etc/init.d/', + description: 'Init script installation location (if empty, disable init script installation)') + +option('utils', type : 'boolean', value : true, + description: 'Whether or not to build and install helper programs') + +option('examples', type : 'boolean', value : true, + description: 'Whether or not to build example programs') + +option('useroot', type : 'boolean', value : true, + description: 'Set owner and setuid bits on installed files') + +option('tests', type : 'boolean', value : true, + description: 'Compile the test files') + +option('disable-libc-symbol-version', type : 'boolean', value : false, + description: 'Disable versioned symbols through libc') + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2c6a4a0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# Packages required for building and testing libfuse only, +# no python code is required when using libfuse. +# Build packages: +meson +ninja +# Test packages: +pytest diff --git a/signify/fuse-3.15.pub b/signify/fuse-3.15.pub new file mode 100644 index 0000000..b8c688c --- /dev/null +++ b/signify/fuse-3.15.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWRsNRJIAGSHE0C6WuVmusQlvudAPlmOOkCw1LbuOLuu+25DuAmPCpDf diff --git a/signify/fuse-3.16.pub b/signify/fuse-3.16.pub new file mode 100644 index 0000000..13fdcb3 --- /dev/null +++ b/signify/fuse-3.16.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQtnc3WSoYwHGAdfvtTTVX8RsAXrNwMb8xqVwlY8lYY2Fxn2QUDiPYK diff --git a/signify/fuse-3.17.pub b/signify/fuse-3.17.pub new file mode 100644 index 0000000..02525e1 --- /dev/null +++ b/signify/fuse-3.17.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQqzcI/bjQ4/nouPgwUbs0WorZncrBxJHmiCb2N+GrMs9L6YAcGSFY/ diff --git a/signify/fuse-3.18.pub b/signify/fuse-3.18.pub new file mode 100644 index 0000000..eeee6bf --- /dev/null +++ b/signify/fuse-3.18.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWS6gMnNrKp/zRSYWv13J+KwXE26vCUsbC/hVZmjQ8PA3xjixGLjodz3 diff --git a/test/ci-build.sh b/test/ci-build.sh new file mode 100755 index 0000000..40bb79e --- /dev/null +++ b/test/ci-build.sh @@ -0,0 +1,155 @@ +#!/bin/bash -x + +set -e + +TEST_CMD="pytest -v --maxfail=1 --log-level=INFO --log-cli-level=INFO test/" +SAN="-Db_sanitize=address,undefined" + +# not default +export UBSAN_OPTIONS=halt_on_error=1 + +# Make sure binaries can be accessed when invoked by root. +umask 0022 + +# There are tests that run as root but without CAP_DAC_OVERRIDE. To allow these +# to launch built binaries, the directory tree must be accessible to the root +# user. Since the source directory isn't necessarily accessible to root, we +# build and run tests in a temporary directory that we can set up to be world +# readable/executable. +SOURCE_DIR="$(readlink -f .)" +TEST_DIR="$(mktemp -dt libfuse-build-XXXXXX)" + +PREFIX_DIR="$(mktemp -dt libfuse-install-XXXXXXX)" + +chmod 0755 "${TEST_DIR}" +cd "${TEST_DIR}" +echo "Running in ${TEST_DIR}" + +cp -v "${SOURCE_DIR}/test/lsan_suppress.txt" . +export LSAN_OPTIONS="suppressions=$(pwd)/lsan_suppress.txt" +export ASAN_OPTIONS="detect_leaks=1" +export CC + +non_sanitized_build() +( + echo "Standard build (without sanitizers)" + for CC in gcc gcc-9 gcc-10 clang; do + echo "=== Building with ${CC} ===" + mkdir build-${CC}; pushd build-${CC} + if [ "${CC}" == "clang" ]; then + export CXX="clang++" + export TEST_WITH_VALGRIND=false + else + unset CXX + export TEST_WITH_VALGRIND=true + fi + if [ ${CC} == 'gcc-7' ]; then + build_opts='-D b_lundef=false' + else + build_opts='' + fi + if [ ${CC} == 'gcc-10' ]; then + build_opts='-Dc_args=-flto=auto' + else + build_opts='' + fi + + meson setup -Dprefix=${PREFIX_DIR} -D werror=true ${build_opts} "${SOURCE_DIR}" || (cat meson-logs/meson-log.txt; false) + ninja + sudo env PATH=$PATH ninja install + + # libfuse will first try the install path and then system defaults + sudo chmod 4755 ${PREFIX_DIR}/bin/fusermount3 + + # also needed for some of the tests + sudo chown root:root util/fusermount3 + sudo chmod 4755 util/fusermount3 + + ${TEST_CMD} + popd + rm -fr build-${CC} + sudo rm -fr ${PREFIX_DIR} + + done +) + +sanitized_build() +( + echo "=== Building with clang and sanitizers" + + mkdir build-san; pushd build-san + + meson setup -Dprefix=${PREFIX_DIR} -D werror=true\ + "${SOURCE_DIR}" \ + || (cat meson-logs/meson-log.txt; false) + meson configure $SAN + + # b_lundef=false is required to work around clang + # bug, cf. https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4 + meson configure -D b_lundef=false + + # additional options + if [[ $# -gt 0 ]]; then + meson configure "$@" + fi + + # print all options + meson configure --no-pager + + # reconfigure to ensure it uses all additional options + meson setup --reconfigure "${SOURCE_DIR}" + ninja + sudo env PATH=$PATH ninja install + sudo chmod 4755 ${PREFIX_DIR}/bin/fusermount3 + + # also needed for some of the tests + sudo chown root:root util/fusermount3 + sudo chmod 4755 util/fusermount3 + + # Test as root and regular user + sudo env PATH=$PATH ${TEST_CMD} + # Cleanup temporary files (since they are now owned by root) + sudo rm -rf test/.pytest_cache/ test/__pycache__ + + ${TEST_CMD} + + popd + rm -fr build-san + sudo rm -fr ${PREFIX_DIR} +) + +# 32-bit sanitized build +export CC=clang +export CXX=clang++ +export CFLAGS="-m32" +export CXXFLAGS="-m32" +export LDFLAGS="-m32" +export PKG_CONFIG_PATH="/usr/lib/i386-linux-gnu/pkgconfig" +TEST_WITH_VALGRIND=false +sanitized_build +unset CFLAGS +unset CXXFLAGS +unset LDFLAGS +unset PKG_CONFIG_PATH +unset TEST_WITH_VALGRIND +unset CC +unset CXX + +# Sanitized build +export CC=clang +export CXX=clang++ +TEST_WITH_VALGRIND=false +sanitized_build + +# Sanitized build without libc versioned symbols +export CC=clang +export CXX=clang++ +sanitized_build "-Ddisable-libc-symbol-version=true" + +non_sanitized_build + +# Documentation. +(cd "${SOURCE_DIR}"; doxygen doc/Doxyfile) + +# Clean up. +rm -rf "${TEST_DIR}" diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..f528189 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +import sys +import pytest +import time +import re +import os +import threading + + +# If a test fails, wait a moment before retrieving the captured +# stdout/stderr. When using a server process, this makes sure that we capture +# any potential output of the server that comes *after* a test has failed. For +# example, if a request handler raises an exception, the server first signals an +# error to FUSE (causing the test to fail), and then logs the exception. Without +# the extra delay, the exception will go into nowhere. +@pytest.hookimpl(hookwrapper=True) +def pytest_pyfunc_call(pyfuncitem): + outcome = yield + failed = outcome.excinfo is not None + if failed: + time.sleep(1) + + +class OutputChecker: + '''Check output data for suspicious patterns. + + Everything written to check_output.fd will be scanned for suspicious + messages and then written to sys.stdout. + ''' + + def __init__(self): + (fd_r, fd_w) = os.pipe() + self.fd = fd_w + self._false_positives = [] + self._buf = bytearray() + self._thread = threading.Thread(target=self._loop, daemon=True, args=(fd_r,)) + self._thread.start() + + def register_output(self, pattern, count=1, flags=re.MULTILINE): + '''Register *pattern* as false positive for output checking + + This prevents the test from failing because the output otherwise + appears suspicious. + ''' + + self._false_positives.append((pattern, flags, count)) + + def _loop(self, ifd): + BUFSIZE = 128*1024 + ofd = sys.stdout.fileno() + while True: + buf = os.read(ifd, BUFSIZE) + if not buf: + break + os.write(ofd, buf) + self._buf += buf + + def _check(self): + os.close(self.fd) + self._thread.join() + + buf = self._buf.decode('utf8', errors='replace') + + # Strip out false positives + for (pattern, flags, count) in self._false_positives: + cp = re.compile(pattern, flags) + (buf, cnt) = cp.subn('', buf, count=count) + + patterns = [ r'\b{}\b'.format(x) for x in + ('exception', 'error', 'warning', 'fatal', 'traceback', + 'fault', 'crash(?:ed)?', 'abort(?:ed)', + 'uninitiali[zs]ed') ] + patterns += ['^==[0-9]+== '] + + for pattern in patterns: + cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE) + hit = cp.search(buf) + if hit: + raise AssertionError('Suspicious output to stderr (matched "%s")' + % hit.group(0)) + +@pytest.fixture() +def output_checker(request): + checker = OutputChecker() + yield checker + checker._check() + + +# Make test outcome available to fixtures +# (from https://github.com/pytest-dev/pytest/issues/230) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_makereport(item, call): + outcome = yield + rep = outcome.get_result() + setattr(item, "rep_" + rep.when, rep) + return rep diff --git a/test/hello.c b/test/hello.c new file mode 100644 index 0000000..a07df0e --- /dev/null +++ b/test/hello.c @@ -0,0 +1,180 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2001-2007 Miklos Szeredi + * + * This program can be distributed under the terms of the GNU GPLv2. + * See the file COPYING. + */ + +/** @file + * + * minimal example filesystem using high-level API + * + * Compile with: + * + * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello + * + * ## Source code ## + * \include hello.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include + +/* + * Command line options + * + * We can't set default values for the char* fields here because + * fuse_opt_parse would attempt to free() them when the user specifies + * different values on the command line. + */ +static struct options { + const char *filename; + const char *contents; + int show_help; +} options; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--name=%s", filename), OPTION("--contents=%s", contents), + OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END +}; + +static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + (void)conn; + cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; + + return NULL; +} + +static int hello_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void)fi; + int res = 0; + + memset(stbuf, 0, sizeof(struct stat)); + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path + 1, options.filename) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(options.contents); + } else + res = -ENOENT; + + return res; +} + +static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void)offset; + (void)fi; + (void)flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int hello_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int hello_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + size_t len; + (void)fi; + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + len = strlen(options.contents); + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, options.contents + offset, size); + } else + size = 0; + + return size; +} + +static const struct fuse_operations hello_oper = { + .init = hello_init, + .getattr = hello_getattr, + .readdir = hello_readdir, + .open = hello_open, + .read = hello_read, +}; + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --name= Name of the \"hello\" file\n" + " (default: \"hello\")\n" + " --contents= Contents \"hello\" file\n" + " (default \"Hello, World!\\n\")\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + int ret; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Set defaults -- we have to use strdup so that + * fuse_opt_parse can free the defaults if other + * values are specified + */ + options.filename = strdup("hello"); + options.contents = strdup("Hello World!\n"); + + /* Parse options */ + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + /* When --help is specified, first print our own file-system + * specific help text, then signal fuse_main to show + * additional help (by adding `--help` to the options again) + * without usage: line (by setting argv[0] to the empty + * string) + */ + if (options.show_help) { + show_help(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } + + ret = fuse_main(args.argc, args.argv, &hello_oper, NULL); + fuse_opt_free_args(&args); + return ret; +} diff --git a/test/lsan_suppress.txt b/test/lsan_suppress.txt new file mode 100644 index 0000000..44703fc --- /dev/null +++ b/test/lsan_suppress.txt @@ -0,0 +1 @@ +# Suppression file for address sanitizer. diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..56568d8 --- /dev/null +++ b/test/meson.build @@ -0,0 +1,39 @@ +# Compile helper programs +td = [] +foreach prog: [ 'test_write_cache', 'test_setattr', 'hello' ] + td += executable(prog, prog + '.c', + include_directories: include_dirs, + link_with: [ libfuse ], + dependencies: thread_dep, + install: false) +endforeach +td += executable('test_syscalls', 'test_syscalls.c', + include_directories: include_dirs, + install: false) +td += executable('readdir_inode', 'readdir_inode.c', + include_directories: include_dirs, + install: false) +td += executable('release_unlink_race', 'release_unlink_race.c', + dependencies: [ libfuse_dep ], + install: false) +td += executable('test_want_conversion', 'test_want_conversion.c', + dependencies: [ libfuse_dep ], + install: false) + +test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py', + 'util.py', 'test_ctests.py', 'test_custom_io.py' ] +td += custom_target('test_scripts', input: test_scripts, + output: test_scripts, build_by_default: true, + command: ['cp', '-fPp', + '@INPUT@', meson.current_build_dir() ]) + +# Provide something helpful when running 'ninja test' + +if meson.is_subproject() + test('libfuse is a subproject, skipping tests', executable('wrong_command', + 'wrong_command.c', install: false, + c_args: [ '-DMESON_IS_SUBPROJECT' ])) +else + test('wrong_command', executable('wrong_command', 'wrong_command.c', + install: false)) +endif diff --git a/test/pytest.ini b/test/pytest.ini new file mode 100644 index 0000000..bbc8de8 --- /dev/null +++ b/test/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +addopts = --verbose --assert=rewrite --tb=native -x -r a +markers = + uses_fuse: Indicates that FUSE is supported. +log_cli=true +faulthandler_timeout=60 diff --git a/test/readdir_inode.c b/test/readdir_inode.c new file mode 100644 index 0000000..99f95ff --- /dev/null +++ b/test/readdir_inode.c @@ -0,0 +1,57 @@ +/* + * Prints each directory entry, its inode and d_type as returned by 'readdir'. + * Skips '.' and '..' because readdir is not required to return them and + * some of our examples don't. However if they are returned, their d_type + * should be valid. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + DIR* dirp; + struct dirent* dent; + + if (argc != 2) { + fprintf(stderr, "Usage: readdir_inode dir\n"); + return 1; + } + + dirp = opendir(argv[1]); + if (dirp == NULL) { + perror("failed to open directory"); + return 2; + } + + errno = 0; + dent = readdir(dirp); + while (dent != NULL) { + if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) { + printf("%llu %d %s\n", (unsigned long long)dent->d_ino, + (int)dent->d_type, dent->d_name); + if ((long long)dent->d_ino < 0) + fprintf(stderr,"%s : bad d_ino %llu\n", + dent->d_name, (unsigned long long)dent->d_ino); + if ((dent->d_type < 1) || (dent->d_type > 15)) + fprintf(stderr,"%s : bad d_type %d\n", + dent->d_name, (int)dent->d_type); + } else { + if (dent->d_type != DT_DIR) + fprintf(stderr,"%s : bad d_type %d\n", + dent->d_name, (int)dent->d_type); + } + dent = readdir(dirp); + } + if (errno != 0) { + perror("failed to read directory entry"); + return 3; + } + + closedir(dirp); + + return 0; +} diff --git a/test/release_unlink_race.c b/test/release_unlink_race.c new file mode 100644 index 0000000..2edb200 --- /dev/null +++ b/test/release_unlink_race.c @@ -0,0 +1,111 @@ +/* + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +#define FUSE_USE_VERSION 31 + +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + + cfg->use_ino = 1; + cfg->nullpath_ok = 1; + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + + if(fi) + res = fstat(fi->fh, stbuf); + else + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + if (flags) + return -EINVAL; + + if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000); + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags, mode); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + + if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000); + + close(fi->fh); + + return 0; +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .unlink = xmp_unlink, + .rename = xmp_rename, + .create = xmp_create, + .release = xmp_release, +}; + +int main(int argc, char *argv[]) +{ + umask(0); + return fuse_main(argc, argv, &xmp_oper, NULL); +} diff --git a/test/stracedecode.c b/test/stracedecode.c new file mode 100644 index 0000000..01bf523 --- /dev/null +++ b/test/stracedecode.c @@ -0,0 +1,199 @@ +#include +#include +#include "fuse_kernel.h" + +static struct { + const char *name; +} fuse_ll_ops[] = { + [FUSE_LOOKUP] = { "LOOKUP" }, + [FUSE_FORGET] = { "FORGET" }, + [FUSE_GETATTR] = { "GETATTR" }, + [FUSE_SETATTR] = { "SETATTR" }, + [FUSE_READLINK] = { "READLINK" }, + [FUSE_SYMLINK] = { "SYMLINK" }, + [FUSE_MKNOD] = { "MKNOD" }, + [FUSE_MKDIR] = { "MKDIR" }, + [FUSE_UNLINK] = { "UNLINK" }, + [FUSE_RMDIR] = { "RMDIR" }, + [FUSE_RENAME] = { "RENAME" }, + [FUSE_LINK] = { "LINK" }, + [FUSE_OPEN] = { "OPEN" }, + [FUSE_READ] = { "READ" }, + [FUSE_WRITE] = { "WRITE" }, + [FUSE_STATFS] = { "STATFS" }, + [FUSE_RELEASE] = { "RELEASE" }, + [FUSE_FSYNC] = { "FSYNC" }, + [FUSE_SETXATTR] = { "SETXATTR" }, + [FUSE_GETXATTR] = { "GETXATTR" }, + [FUSE_LISTXATTR] = { "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { "REMOVEXATTR" }, + [FUSE_FLUSH] = { "FLUSH" }, + [FUSE_INIT] = { "INIT" }, + [FUSE_OPENDIR] = { "OPENDIR" }, + [FUSE_READDIR] = { "READDIR" }, + [FUSE_RELEASEDIR] = { "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { "FSYNCDIR" }, + [FUSE_GETLK] = { "GETLK" }, + [FUSE_SETLK] = { "SETLK" }, + [FUSE_SETLKW] = { "SETLKW" }, + [FUSE_ACCESS] = { "ACCESS" }, + [FUSE_CREATE] = { "CREATE" }, + [FUSE_TMPFILE] = { "TMPFILE" }, + [FUSE_INTERRUPT] = { "INTERRUPT" }, + [FUSE_BMAP] = { "BMAP" }, + [FUSE_DESTROY] = { "DESTROY" }, + [FUSE_READDIRPLUS] = { "READDIRPLUS" }, +}; + +#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + +static const char *opname(enum fuse_opcode opcode) +{ + if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) + return "???"; + else + return fuse_ll_ops[opcode].name; +} + + +static void process_buf(int dir, char *buf, int len) +{ + static unsigned long long prevuniq = -1; + static int prevopcode; + + if (!dir) { + struct fuse_in_header *in = (struct fuse_in_header *) buf; + buf += sizeof(struct fuse_in_header); + + printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n", + (unsigned long long) in->unique, + opname((enum fuse_opcode) in->opcode), in->opcode, + (unsigned long) in->nodeid, in->len, len); + + switch (in->opcode) { + case FUSE_READ: { + struct fuse_read_in *arg = (struct fuse_read_in *) buf; + printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n", + arg->fh, arg->offset, arg->size, arg->read_flags, + arg->lock_owner, arg->flags); + break; + } + case FUSE_WRITE: { + struct fuse_write_in *arg = (struct fuse_write_in *) buf; + printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n", + arg->fh, arg->offset, arg->size, arg->write_flags, + arg->lock_owner, arg->flags); + break; + } + } + prevuniq = in->unique; + prevopcode = in->opcode; + } else { + struct fuse_out_header *out = (struct fuse_out_header *) buf; + buf += sizeof(struct fuse_out_header); + + printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n", + (unsigned long long) out->unique, out->error, + strerror(-out->error), out->len, len); + + if (out->unique == prevuniq) { + switch (prevopcode) { + case FUSE_GETATTR: { + struct fuse_attr_out *arg = (struct fuse_attr_out *) buf; + printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n", + arg->attr_valid, arg->attr_valid_nsec, + arg->attr.ino, arg->attr.size, arg->attr.blocks); + break; + } + case FUSE_LOOKUP: { + struct fuse_entry_out *arg = (struct fuse_entry_out *) buf; + printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n", + arg->nodeid, arg->attr_valid, arg->attr_valid_nsec, + arg->attr.ino, arg->attr.size, arg->attr.blocks); + break; + } + } + } + } + +} + +int main(void) +{ + FILE *in = stdin; + while (1) { + int dir; + int res; + char buf[1048576]; + unsigned len = 0; + + memset(buf, 0, sizeof(buf)); + while (1) { + char str[32]; + + res = fscanf(in, "%30s", str); + if (res != 1 && feof(in)) + return 0; + + if (res == 0) + continue; + + if (strncmp(str, "read(", 5) == 0) { + dir = 0; + break; + } else if (strncmp(str, "writev(", 7) == 0) { + dir = 1; + break; + } + } + + while (1) { + int c = getc(in); + if (c == '"') { + while (1) { + int val; + + c = getc(in); + if (c == EOF) { + fprintf(stderr, "eof in string\n"); + break; + } + if (c == '\n') { + fprintf(stderr, "eol in string\n"); + break; + } + if (c == '"') + break; + if (c != '\\') { + val = c; + } else { + c = getc(in); + switch (c) { + case 'n': val = '\n'; break; + case 'r': val = '\r'; break; + case 't': val = '\t'; break; + case '"': val = '"'; break; + case '\\': val = '\\'; break; + case 'x': + res = scanf("%x", &val); + if (res != 1) { + fprintf(stderr, "parse error\n"); + continue; + } + break; + default: + fprintf(stderr, "unknown sequence: '\\%c'\n", c); + continue; + } + } + buf[len++] = val; + } + } + if (c == '\n') + break; + } + process_buf(dir, buf, len); + memset(buf, 0, len); + len = 0; + } +} diff --git a/test/test_ctests.py b/test/test_ctests.py new file mode 100644 index 0000000..feefc07 --- /dev/null +++ b/test/test_ctests.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import pytest + import sys + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import subprocess +import pytest +import platform +import sys +import os +import logging +from packaging import version +from util import (wait_for_mount, umount, cleanup, base_cmdline, + safe_sleep, basename, fuse_test_marker, fuse_caps, + fuse_proto, create_tmpdir, parse_kernel_version) +from os.path import join as pjoin +import os.path + +pytestmark = fuse_test_marker() + +@pytest.mark.skipif('FUSE_CAP_WRITEBACK_CACHE' not in fuse_caps, + reason='not supported by running kernel') +@pytest.mark.parametrize("writeback", (False, True)) +def test_write_cache(tmpdir, writeback, output_checker): + if writeback and parse_kernel_version(platform.release()) < version.parse('3.14'): + pytest.skip('Requires kernel 3.14 or newer') + # This test hangs under Valgrind when running close(fd) + # test_write_cache.c:test_fs(). Most likely this is because of an internal + # deadlock in valgrind, it probably assumes that until close() returns, + # control does not come to the program. + mnt_dir = str(tmpdir) + print("mnt_dir: '" + mnt_dir + "'") + create_tmpdir(mnt_dir) + + cmdline = [ pjoin(basename, 'test', 'test_write_cache'), + mnt_dir ] + if writeback: + cmdline.append('-owriteback_cache') + elif parse_kernel_version(platform.release()) >= version.parse('5.16'): + # Test that close(rofd) does not block waiting for pending writes. + # This test requires kernel commit a390ccb316be ("fuse: add FOPEN_NOFLUSH") + # so opt-in for this test from kernel 5.16. + cmdline.append('--delay_ms=200') + subprocess.check_call(cmdline, stdout=output_checker.fd, stderr=output_checker.fd) + + +names = [ 'notify_inval_inode', 'invalidate_path' ] +if fuse_proto >= (7,15): + names.append('notify_store_retrieve') +@pytest.mark.skipif(fuse_proto < (7,12), + reason='not supported by running kernel') +@pytest.mark.parametrize("name", names) +@pytest.mark.parametrize("notify", (True, False)) +def test_notify1(tmpdir, name, notify, output_checker): + logger = logging.getLogger(__name__) + mnt_dir = str(tmpdir) + logger.debug(f"Mount directory: {mnt_dir}") + create_tmpdir(mnt_dir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', name), + '-f', '--update-interval=1', mnt_dir ] + if not notify: + cmdline.append('--no-notify') + logger.debug(f"Command line: {' '.join(cmdline)}") + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + logger.debug("Mount completed") + filename = pjoin(mnt_dir, 'current_time') + logger.debug(f"Target filename: {filename}") + with open(filename, 'r') as fh: + read1 = fh.read() + logger.debug(f"First read: {read1}") + logger.debug("Sleeping for 2 seconds...") + safe_sleep(2) + logger.debug("Sleep completed") + with open(filename, 'r') as fh: + read2 = fh.read() + logger.debug(f"Second read: {read2}") + if notify: + logger.debug("Expecting reads to be different") + assert read1 != read2 + else: + logger.debug("Expecting reads to be the same") + assert read1 == read2 + logger.debug("Test completed successfully") + except: + logger.error(f"Failure in notify test: '{' '.join(cmdline)}'") + logger.exception("Exception details:") + cleanup(mount_process, mnt_dir) + raise + else: + logger.debug("Unmounting...") + try: + umount(mount_process, mnt_dir) + logger.debug("Umount disabled") + except: + logger.error(f"Failure in unmount: '{' '.join(cmdline)}'") + cleanup(mount_process, mnt_dir) + logger.debug("Unmount completed") + +@pytest.mark.skipif(fuse_proto < (7,12), + reason='not supported by running kernel') +@pytest.mark.parametrize("notify", (True, False)) +def test_notify_file_size(tmpdir, notify, output_checker): + logger = logging.getLogger(__name__) + mnt_dir = str(tmpdir) + logger.debug(f"Mount directory: {mnt_dir}") + create_tmpdir(mnt_dir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'invalidate_path'), + '-f', '--update-interval=1', mnt_dir ] + if not notify: + cmdline.append('--no-notify') + logger.debug(f"Command line: {' '.join(cmdline)}") + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + logger.debug(f"Mount process PID: {mount_process.pid}") + try: + wait_for_mount(mount_process, mnt_dir) + filename = pjoin(mnt_dir, 'growing') + size = os.path.getsize(filename) + logger.debug(f"Initial file size: {size}") + logger.debug("Sleeping for 2 seconds...") + safe_sleep(2) + logger.debug("Sleep completed") + new_size = os.path.getsize(filename) + logger.debug(f"New file size: {new_size}") + if notify: + assert new_size > size + else: + assert new_size == size + logger.debug("Test completed successfully") + except: + cleanup(mount_process, mnt_dir) + raise + else: + try: + umount(mount_process, mnt_dir) + except: + logger.error(f"Failure in unmount: '{' '.join(cmdline)}'") + cleanup(mount_process, mnt_dir) + logger.debug("Unmount completed") diff --git a/test/test_custom_io.py b/test/test_custom_io.py new file mode 100644 index 0000000..737b939 --- /dev/null +++ b/test/test_custom_io.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import sys + + import pytest + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import os +import socket +import struct +import subprocess +import sys +import time +from os.path import join as pjoin + +import pytest + +from util import base_cmdline, basename + +FUSE_OP_INIT = 26 + +FUSE_MAJOR_VERSION = 7 +FUSE_MINOR_VERSION = 38 + +fuse_in_header_fmt = ' bytes: + buf = bytes() + while len(buf) < bufsize: + buf += sock.recv(bufsize - len(buf)) + return buf + + +def tst_init(sock: socket.socket): + unique_req = 10 + dummy_init_req_header = struct.pack( + fuse_in_header_fmt, struct.calcsize(fuse_in_header_fmt) + + struct.calcsize(fuse_init_in_fmt), FUSE_OP_INIT, unique_req, 0, 0, 0, + 0, 0) + dummy_init_req_payload = struct.pack( + fuse_init_in_fmt, FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION, 0, 0, 0) + dummy_init_req = dummy_init_req_header + dummy_init_req_payload + + sock.sendall(dummy_init_req) + + response_header = sock_recvall(sock, struct.calcsize(fuse_out_header_fmt)) + packet_len, _, unique_res = struct.unpack( + fuse_out_header_fmt, response_header) + assert unique_res == unique_req + + response_payload = sock_recvall(sock, packet_len - len(response_header)) + response_payload = struct.unpack(fuse_init_out_fmt, response_payload) + assert response_payload[0] == FUSE_MAJOR_VERSION + + +def test_hello_uds(output_checker): + cmdline = base_cmdline + [pjoin(basename, 'example', 'hello_ll_uds')] + print(cmdline) + uds_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + time.sleep(1) + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.settimeout(1) + sock.connect("/tmp/libfuse-hello-ll.sock") + + tst_init(sock) + + sock.close() + uds_process.terminate() + try: + uds_process.wait(1) + except subprocess.TimeoutExpired: + uds_process.kill() + os.remove("/tmp/libfuse-hello-ll.sock") diff --git a/test/test_examples.py b/test/test_examples.py new file mode 100755 index 0000000..9c8b77e --- /dev/null +++ b/test/test_examples.py @@ -0,0 +1,902 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import pytest + import sys + sys.exit(pytest.main([__file__] + sys.argv[1:])) + +import subprocess +import os +import sys +import py +import pytest +import stat +import shutil +import filecmp +import tempfile +import time +import errno +import sys +import platform +import re +from packaging import version +from tempfile import NamedTemporaryFile +from contextlib import contextmanager +from util import (wait_for_mount, umount, cleanup, base_cmdline, + safe_sleep, basename, fuse_test_marker, test_printcap, + fuse_proto, fuse_caps, powerset, parse_kernel_version) +from os.path import join as pjoin +import logging + +pytestmark = fuse_test_marker() + +TEST_FILE = __file__ + +with open(TEST_FILE, 'rb') as fh: + TEST_DATA = fh.read() + +def name_generator(__ctr=[0]): + __ctr[0] += 1 + return 'testfile_%d' % __ctr[0] + +options = [] +if sys.platform == 'linux': + options.append('clone_fd') + +def invoke_directly(mnt_dir, name, options): + # Handle test/hello specially since it's not in example/ + if name.startswith('test/'): + path = pjoin(basename, name) + else: + path = pjoin(basename, 'example', name) + + cmdline = base_cmdline + [ path, '-f', mnt_dir, '-o', ','.join(options) ] + if name == 'hello_ll': + # supports single-threading only + cmdline.append('-s') + + return cmdline + +def invoke_mount_fuse(mnt_dir, name, options): + return base_cmdline + [ pjoin(basename, 'util', 'mount.fuse3'), + name, mnt_dir, '-o', ','.join(options) ] + +def invoke_mount_fuse_drop_privileges(mnt_dir, name, options): + if os.getuid() != 0: + pytest.skip('drop_privileges requires root, skipping.') + + return invoke_mount_fuse(mnt_dir, name, options + ('drop_privileges',)) + +class raii_tmpdir: + def __init__(self): + self.d = tempfile.mkdtemp() + + def __str__(self): + return str(self.d) + + def mkdir(self, path): + return py.path.local(str(self.d)).mkdir(path) + +@pytest.fixture +def short_tmpdir(): + return raii_tmpdir() + +def readdir_inode(dir): + cmd = base_cmdline + [ pjoin(basename, 'test', 'readdir_inode'), dir ] + with subprocess.Popen(cmd, stdout=subprocess.PIPE, + universal_newlines=True) as proc: + lines = proc.communicate()[0].splitlines() + lines.sort() + return lines + + +@pytest.mark.parametrize("cmdline_builder", (invoke_directly, invoke_mount_fuse, + invoke_mount_fuse_drop_privileges)) +@pytest.mark.parametrize("options", powerset(options)) +@pytest.mark.parametrize("name", ('hello', 'hello_ll', 'test/hello')) +def test_hello(tmpdir, name, options, cmdline_builder, output_checker): + logger = logging.getLogger(__name__) + mnt_dir = str(tmpdir) + logger.debug(f"Mount directory: {mnt_dir}") + cmdline = cmdline_builder(mnt_dir, name, options) + logger.debug(f"Command line: {' '.join(cmdline)}") + mount_process = subprocess.Popen( + cmdline, + stdout=output_checker.fd, stderr=output_checker.fd) + logger.debug(f"Mount process PID: {mount_process.pid}") + try: + logger.debug("Waiting for mount...") + wait_for_mount(mount_process, mnt_dir) + logger.debug("Mount completed") + assert os.listdir(mnt_dir) == [ 'hello' ] + logger.debug("Verified 'hello' file exists in mount directory") + filename = pjoin(mnt_dir, 'hello') + with open(filename, 'r') as fh: + assert fh.read() == 'Hello World!\n' + logger.debug("Verified contents of 'hello' file") + with pytest.raises(IOError) as exc_info: + open(filename, 'r+') + assert exc_info.value.errno == errno.EACCES + logger.debug("Verified EACCES error when trying to open file for writing") + with pytest.raises(IOError) as exc_info: + open(filename + 'does-not-exist', 'r+') + assert exc_info.value.errno == errno.ENOENT + logger.debug("Verified ENOENT error for non-existent file") + if name == 'hello_ll': + logger.debug("Testing xattr for hello_ll") + tst_xattr(mnt_dir) + path = os.path.join(mnt_dir, 'hello') + tst_xattr(path) + except: + logger.error("Exception occurred during test", exc_info=True) + cleanup(mount_process, mnt_dir) + raise + else: + logger.debug("Unmounting...") + umount(mount_process, mnt_dir) + logger.debug("Test completed successfully") + +@pytest.mark.parametrize("writeback", (False, True)) +@pytest.mark.parametrize("name", ('passthrough', 'passthrough_plus', + 'passthrough_fh', 'passthrough_ll')) +@pytest.mark.parametrize("debug", (False, True)) +def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): + # Avoid false positives from libfuse debug messages + if debug: + output_checker.register_output(r'^ unique: [0-9]+, error: -[0-9]+ .+$', + count=0) + + # test_syscalls prints "No error" under FreeBSD + output_checker.register_output(r"^ \d\d \[[^\]]+ message: 'No error: 0'\]", + count=0) + + mnt_dir = str(short_tmpdir.mkdir('mnt')) + src_dir = str(short_tmpdir.mkdir('src')) + + if name == 'passthrough_plus': + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough'), + '--plus', '-f', mnt_dir ] + else: + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', name), + '-f', mnt_dir ] + if debug: + cmdline.append('-d') + + if writeback: + if name != 'passthrough_ll': + pytest.skip('example does not support writeback caching') + cmdline.append('-o') + cmdline.append('writeback') + + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + work_dir = mnt_dir + src_dir + + tst_statvfs(work_dir) + tst_readdir(src_dir, work_dir) + tst_readdir_big(src_dir, work_dir) + tst_open_read(src_dir, work_dir) + tst_open_write(src_dir, work_dir) + tst_create(work_dir) + tst_passthrough(src_dir, work_dir) + tst_append(src_dir, work_dir) + tst_seek(src_dir, work_dir) + tst_mkdir(work_dir) + tst_rmdir(work_dir, src_dir) + tst_unlink(work_dir, src_dir) + tst_symlink(work_dir) + if os.getuid() == 0: + tst_chown(work_dir) + + # Underlying fs may not have full nanosecond resolution + tst_utimens(work_dir, ns_tol=1000) + + tst_link(work_dir) + tst_truncate_path(work_dir) + tst_truncate_fd(work_dir) + tst_open_unlink(work_dir) + + syscall_test_cmd = [ os.path.join(basename, 'test', 'test_syscalls'), + work_dir, ':' + src_dir ] + if writeback: + # When writeback caching is enabled, kernel has to open files for + # reading even when userspace opens with O_WDONLY. This fails if the + # filesystem process doesn't have special permission. + syscall_test_cmd.append('-53') + subprocess.check_call(syscall_test_cmd) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +@pytest.mark.parametrize("cache", (False, True)) +def test_passthrough_hp(short_tmpdir, cache, output_checker): + mnt_dir = str(short_tmpdir.mkdir('mnt')) + src_dir = str(short_tmpdir.mkdir('src')) + + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough_hp'), + src_dir, mnt_dir ] + + cmdline.append('--foreground') + + if not cache: + cmdline.append('--nocache') + + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + + tst_statvfs(mnt_dir) + tst_readdir(src_dir, mnt_dir) + tst_readdir_big(src_dir, mnt_dir) + tst_open_read(src_dir, mnt_dir) + tst_open_write(src_dir, mnt_dir) + tst_create(mnt_dir) + if not cache: + tst_passthrough(src_dir, mnt_dir) + tst_append(src_dir, mnt_dir) + tst_seek(src_dir, mnt_dir) + tst_mkdir(mnt_dir) + if cache: + # if cache is enabled, no operations should go through + # src_dir as the cache will become stale. + tst_rmdir(mnt_dir) + tst_unlink(mnt_dir) + else: + tst_rmdir(mnt_dir, src_dir) + tst_unlink(mnt_dir, src_dir) + tst_symlink(mnt_dir) + if os.getuid() == 0: + tst_chown(mnt_dir) + + # Underlying fs may not have full nanosecond resolution + tst_utimens(mnt_dir, ns_tol=1000) + + tst_link(mnt_dir) + tst_truncate_path(mnt_dir) + tst_truncate_fd(mnt_dir) + tst_open_unlink(mnt_dir) + + # test_syscalls assumes that changes in source directory + # will be reflected immediately in mountpoint, so we + # can't use it. + if not cache: + syscall_test_cmd = [ os.path.join(basename, 'test', 'test_syscalls'), + mnt_dir, ':' + src_dir ] + # unlinked testfiles check fails without kernel fix + # "fuse: fix illegal access to inode with reused nodeid" + # so opt-in for this test from kernel 5.14 + if parse_kernel_version(platform.release()) >= version.parse('5.14'): + syscall_test_cmd.append('-u') + subprocess.check_call(syscall_test_cmd) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + + +@pytest.mark.skipif(fuse_proto < (7,11), + reason='not supported by running kernel') +def test_ioctl(tmpdir, output_checker): + progname = pjoin(basename, 'example', 'ioctl') + if not os.path.exists(progname): + pytest.skip('%s not built' % os.path.basename(progname)) + + # Check if binary is 32-bit + file_output = subprocess.check_output(['file', progname]).decode() + if 'ELF 32-bit' in file_output and platform.machine() == 'x86_64': + pytest.skip('ioctl test not supported for 32-bit binary on 64-bit system') + + mnt_dir = str(tmpdir) + testfile = pjoin(mnt_dir, 'fioc') + cmdline = base_cmdline + [progname, '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'ioctl_client'), + testfile ] + assert subprocess.check_output(cmdline) == b'0\n' + with open(testfile, 'wb') as fh: + fh.write(b'foobar') + assert subprocess.check_output(cmdline) == b'6\n' + subprocess.check_call(cmdline + [ '3' ]) + with open(testfile, 'rb') as fh: + assert fh.read()== b'foo' + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +def test_poll(tmpdir, output_checker): + mnt_dir = str(tmpdir) + cmdline = base_cmdline + [pjoin(basename, 'example', 'poll'), + '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'poll_client') ] + subprocess.check_call(cmdline, cwd=mnt_dir) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +def test_null(tmpdir, output_checker): + progname = pjoin(basename, 'example', 'null') + if not os.path.exists(progname): + pytest.skip('%s not built' % os.path.basename(progname)) + + mnt_file = str(tmpdir) + '/file' + with open(mnt_file, 'w') as fh: + fh.write('dummy') + cmdline = base_cmdline + [ progname, '-f', mnt_file ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + def test_fn(name): + return os.stat(name).st_size > 4000 + try: + wait_for_mount(mount_process, mnt_file, test_fn) + with open(mnt_file, 'rb') as fh: + assert fh.read(382) == b'\0' * 382 + with open(mnt_file, 'wb') as fh: + fh.write(b'whatever') + except: + cleanup(mount_process, mnt_file) + raise + else: + umount(mount_process, mnt_file) + + +@pytest.mark.skipif(fuse_proto < (7,12), + reason='not supported by running kernel') +@pytest.mark.parametrize("only_expire", ("invalidate_entries", "expire_entries")) +@pytest.mark.parametrize("notify", (True, False)) +def test_notify_inval_entry(tmpdir, only_expire, notify, output_checker): + mnt_dir = str(tmpdir) + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'notify_inval_entry'), + '-f', '--update-interval=1', + '--timeout=5', mnt_dir ] + if not notify: + cmdline.append('--no-notify') + if only_expire == "expire_entries": + cmdline.append('--only-expire') + if "FUSE_CAP_EXPIRE_ONLY" not in fuse_caps: + pytest.skip('only-expire not supported by running kernel') + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + fname = pjoin(mnt_dir, os.listdir(mnt_dir)[0]) + try: + os.stat(fname) + except FileNotFoundError: + # We may have hit a race condition and issued + # readdir just before the name changed + fname = pjoin(mnt_dir, os.listdir(mnt_dir)[0]) + os.stat(fname) + + safe_sleep(2) + if not notify: + os.stat(fname) + safe_sleep(5) + with pytest.raises(FileNotFoundError): + os.stat(fname) + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +@pytest.mark.parametrize("intended_user", ('root', 'non_root')) +def test_dev_auto_unmount(short_tmpdir, output_checker, intended_user): + """Check that root can mount with dev and auto_unmount + (but non-root cannot). + Split into root vs non-root, so that the output of pytest + makes clear what functionality is being tested.""" + if os.getuid() == 0 and intended_user == 'non_root': + pytest.skip('needs to run as non-root') + if os.getuid() != 0 and intended_user == 'root': + pytest.skip('needs to run as root') + mnt_dir = str(short_tmpdir.mkdir('mnt')) + src_dir = str('/dev') + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough_ll'), + '-o', f'source={src_dir},dev,auto_unmount', + '-f', mnt_dir ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + try: + wait_for_mount(mount_process, mnt_dir) + if os.getuid() == 0: + open(pjoin(mnt_dir, 'null')).close() + else: + with pytest.raises(PermissionError): + open(pjoin(mnt_dir, 'null')).close() + except: + cleanup(mount_process, mnt_dir) + raise + else: + umount(mount_process, mnt_dir) + +@pytest.mark.skipif(os.getuid() != 0, + reason='needs to run as root') +def test_cuse(output_checker): + progname = pjoin(basename, 'example', 'cuse') + if not os.path.exists(progname): + pytest.skip('%s not built' % os.path.basename(progname)) + + # Check if binary is 32-bit + file_output = subprocess.check_output(['file', progname]).decode() + if 'ELF 32-bit' in file_output and platform.machine() == 'x86_64': + pytest.skip('cuse test not supported for 32-bit binary on 64-bit system') + + # Valgrind warns about unknown ioctls, that's ok + output_checker.register_output(r'^==([0-9]+).+unhandled ioctl.+\n' + r'==\1== \s{3}.+\n' + r'==\1== \s{3}.+$', count=0) + + devname = 'cuse-test-%d' % os.getpid() + devpath = '/dev/%s' % devname + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'cuse'), + '-f', '--name=%s' % devname ] + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, + stderr=output_checker.fd) + + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'cuse_client'), + devpath ] + try: + wait_for_mount(mount_process, devpath, + test_fn=os.path.exists) + assert subprocess.check_output(cmdline + ['s']) == b'0\n' + data = b'some test data' + off = 5 + proc = subprocess.Popen(cmdline + [ 'w', str(len(data)), str(off) ], + stdin=subprocess.PIPE) + proc.stdin.write(data) + proc.stdin.close() + assert proc.wait(timeout=10) == 0 + size = str(off + len(data)).encode() + b'\n' + assert subprocess.check_output(cmdline + ['s']) == size + out = subprocess.check_output( + cmdline + [ 'r', str(off + len(data) + 2), '0' ]) + assert out == (b'\0' * off) + data + finally: + mount_process.terminate() + +def test_release_unlink_race(tmpdir, output_checker): + """test case for Issue #746 + + If RELEASE and UNLINK opcodes are sent back to back, and fuse_fs_release() + and fuse_fs_rename() are slow to execute, UNLINK will run while RELEASE is + still executing. UNLINK will try to rename the file and, while the rename + is happening, the RELEASE will finish executing. As a result, RELEASE will + not detect in time that UNLINK has happened, and UNLINK will not detect in + time that RELEASE has happened. + + + NOTE: This is triggered only when nullpath_ok is set. + + If it is NOT SET then get_path_nullok() called by fuse_lib_release() will + call get_path_common() and lock the path, and then the fuse_lib_unlink() + will wait for the path to be unlocked before executing and thus synchronise + with fuse_lib_release(). + + If it is SET then get_path_nullok() will just set the path to null and + return without locking anything and thus allowing fuse_lib_unlink() to + eventually execute unimpeded while fuse_lib_release() is still running. + """ + + fuse_mountpoint = str(tmpdir) + + fuse_binary_command = base_cmdline + \ + [ pjoin(basename, 'test', 'release_unlink_race'), + "-f", fuse_mountpoint] + + fuse_process = subprocess.Popen(fuse_binary_command, + stdout=output_checker.fd, + stderr=output_checker.fd) + + try: + wait_for_mount(fuse_process, fuse_mountpoint) + + temp_dir = tempfile.TemporaryDirectory(dir="/tmp/") + temp_dir_path = temp_dir.name + + fuse_temp_file, fuse_temp_file_path = tempfile.mkstemp(dir=(fuse_mountpoint + temp_dir_path)) + + os.close(fuse_temp_file) + os.unlink(fuse_temp_file_path) + + # needed for slow CI/CD pipelines for unlink OP to complete processing + safe_sleep(3) + + assert os.listdir(temp_dir_path) == [] + + except: + temp_dir.cleanup() + cleanup(fuse_process, fuse_mountpoint) + raise + + else: + temp_dir.cleanup() + umount(fuse_process, fuse_mountpoint) + + +@contextmanager +def os_open(name, flags): + fd = os.open(name, flags) + try: + yield fd + finally: + os.close(fd) + +def os_create(name): + os.close(os.open(name, os.O_CREAT | os.O_RDWR)) + +def tst_unlink(mnt_dir, src_dir=None): + name = name_generator() + fullname = mnt_dir + "/" + name + srcname = fullname + if src_dir is not None: + srcname = pjoin(src_dir, name) + with open(srcname, 'wb') as fh: + fh.write(b'hello') + assert name in os.listdir(mnt_dir) + os.unlink(fullname) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + +def tst_mkdir(mnt_dir): + dirname = name_generator() + fullname = mnt_dir + "/" + dirname + os.mkdir(fullname) + fstat = os.stat(fullname) + assert stat.S_ISDIR(fstat.st_mode) + assert os.listdir(fullname) == [] + # Some filesystem (e.g. BTRFS) don't track st_nlink for directories + assert fstat.st_nlink in (1,2) + assert dirname in os.listdir(mnt_dir) + +def tst_rmdir(mnt_dir, src_dir=None): + name = name_generator() + fullname = mnt_dir + "/" + name + srcname = fullname + if src_dir is not None: + srcname = pjoin(src_dir, name) + os.mkdir(srcname) + assert name in os.listdir(mnt_dir) + os.rmdir(fullname) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + +def tst_symlink(mnt_dir): + linkname = name_generator() + fullname = mnt_dir + "/" + linkname + os.symlink("/imaginary/dest", fullname) + fstat = os.lstat(fullname) + assert stat.S_ISLNK(fstat.st_mode) + assert os.readlink(fullname) == "/imaginary/dest" + assert fstat.st_nlink == 1 + assert linkname in os.listdir(mnt_dir) + +def tst_create(mnt_dir): + name = name_generator() + fullname = pjoin(mnt_dir, name) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + + fd = os.open(fullname, os.O_CREAT | os.O_RDWR) + os.close(fd) + + assert name in os.listdir(mnt_dir) + fstat = os.lstat(fullname) + assert stat.S_ISREG(fstat.st_mode) + assert fstat.st_nlink == 1 + assert fstat.st_size == 0 + +def tst_chown(mnt_dir): + filename = pjoin(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + uid = fstat.st_uid + gid = fstat.st_gid + + uid_new = uid + 1 + os.chown(filename, uid_new, -1) + fstat = os.lstat(filename) + assert fstat.st_uid == uid_new + assert fstat.st_gid == gid + + gid_new = gid + 1 + os.chown(filename, -1, gid_new) + fstat = os.lstat(filename) + assert fstat.st_uid == uid_new + assert fstat.st_gid == gid_new + +def tst_open_read(src_dir, mnt_dir): + name = name_generator() + with open(pjoin(src_dir, name), 'wb') as fh_out, \ + open(TEST_FILE, 'rb') as fh_in: + shutil.copyfileobj(fh_in, fh_out) + + assert filecmp.cmp(pjoin(mnt_dir, name), TEST_FILE, False) + +def tst_open_write(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with open(fullname, 'wb') as fh_out, \ + open(TEST_FILE, 'rb') as fh_in: + shutil.copyfileobj(fh_in, fh_out) + + assert filecmp.cmp(fullname, TEST_FILE, False) + +def tst_append(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with os_open(fullname, os.O_WRONLY) as fd: + os.write(fd, b'foo\n') + with os_open(fullname, os.O_WRONLY|os.O_APPEND) as fd: + os.write(fd, b'bar\n') + + with open(fullname, 'rb') as fh: + assert fh.read() == b'foo\nbar\n' + +def tst_seek(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with os_open(fullname, os.O_WRONLY) as fd: + os.lseek(fd, 1, os.SEEK_SET) + os.write(fd, b'foobar\n') + with os_open(fullname, os.O_WRONLY) as fd: + os.lseek(fd, 4, os.SEEK_SET) + os.write(fd, b'com') + + with open(fullname, 'rb') as fh: + assert fh.read() == b'\0foocom\n' + +def tst_open_unlink(mnt_dir): + name = pjoin(mnt_dir, name_generator()) + data1 = b'foo' + data2 = b'bar' + fullname = pjoin(mnt_dir, name) + with open(fullname, 'wb+', buffering=0) as fh: + fh.write(data1) + os.unlink(fullname) + with pytest.raises(OSError) as exc_info: + os.stat(fullname) + assert exc_info.value.errno == errno.ENOENT + assert name not in os.listdir(mnt_dir) + fh.write(data2) + fh.seek(0) + assert fh.read() == data1+data2 + +def tst_statvfs(mnt_dir): + os.statvfs(mnt_dir) + +def tst_link(mnt_dir): + name1 = pjoin(mnt_dir, name_generator()) + name2 = pjoin(mnt_dir, name_generator()) + shutil.copyfile(TEST_FILE, name1) + assert filecmp.cmp(name1, TEST_FILE, False) + + fstat1 = os.lstat(name1) + assert fstat1.st_nlink == 1 + + os.link(name1, name2) + + fstat1 = os.lstat(name1) + fstat2 = os.lstat(name2) + assert fstat1 == fstat2 + assert fstat1.st_nlink == 2 + assert os.path.basename(name2) in os.listdir(mnt_dir) + assert filecmp.cmp(name1, name2, False) + + # Since RELEASE requests are asynchronous, it is possible that + # libfuse still considers the file to be open at this point + # and (since -o hard_remove is not used) renames it instead of + # deleting it. In that case, the following lstat() call will + # still report an st_nlink value of 2 (cf. issue #157). + os.unlink(name2) + + assert os.path.basename(name2) not in os.listdir(mnt_dir) + with pytest.raises(FileNotFoundError): + os.lstat(name2) + + # See above, we may have to wait until RELEASE has been + # received before the st_nlink value is correct. + maxwait = time.time() + 2 + fstat1 = os.lstat(name1) + while fstat1.st_nlink == 2 and time.time() < maxwait: + fstat1 = os.lstat(name1) + time.sleep(0.1) + assert fstat1.st_nlink == 1 + + os.unlink(name1) + +def tst_readdir(src_dir, mnt_dir): + newdir = name_generator() + + src_newdir = pjoin(src_dir, newdir) + mnt_newdir = pjoin(mnt_dir, newdir) + file_ = src_newdir + "/" + name_generator() + subdir = src_newdir + "/" + name_generator() + subfile = subdir + "/" + name_generator() + + os.mkdir(src_newdir) + shutil.copyfile(TEST_FILE, file_) + os.mkdir(subdir) + shutil.copyfile(TEST_FILE, subfile) + + listdir_is = os.listdir(mnt_newdir) + listdir_is.sort() + listdir_should = [ os.path.basename(file_), os.path.basename(subdir) ] + listdir_should.sort() + assert listdir_is == listdir_should + + inodes_is = readdir_inode(mnt_newdir) + inodes_should = readdir_inode(src_newdir) + assert inodes_is == inodes_should + + os.unlink(file_) + os.unlink(subfile) + os.rmdir(subdir) + os.rmdir(src_newdir) + +def tst_readdir_big(src_dir, mnt_dir): + + # Add enough entries so that readdir needs to be called + # multiple times. + fnames = [] + for i in range(500): + fname = ('A rather long filename to make sure that we ' + 'fill up the buffer - ' * 3) + str(i) + with open(pjoin(src_dir, fname), 'w') as fh: + fh.write('File %d' % i) + fnames.append(fname) + + listdir_is = sorted(os.listdir(mnt_dir)) + listdir_should = sorted(os.listdir(src_dir)) + assert listdir_is == listdir_should + + inodes_is = readdir_inode(mnt_dir) + inodes_should = readdir_inode(src_dir) + assert inodes_is == inodes_should + + for fname in fnames: + stat_src = os.stat(pjoin(src_dir, fname)) + stat_mnt = os.stat(pjoin(mnt_dir, fname)) + assert stat_src.st_ino == stat_mnt.st_ino + assert stat_src.st_mtime == stat_mnt.st_mtime + assert stat_src.st_ctime == stat_mnt.st_ctime + assert stat_src.st_size == stat_mnt.st_size + os.unlink(pjoin(src_dir, fname)) + +def tst_truncate_path(mnt_dir): + assert len(TEST_DATA) > 1024 + + filename = pjoin(mnt_dir, name_generator()) + with open(filename, 'wb') as fh: + fh.write(TEST_DATA) + + fstat = os.stat(filename) + size = fstat.st_size + assert size == len(TEST_DATA) + + # Add zeros at the end + os.truncate(filename, size + 1024) + assert os.stat(filename).st_size == size + 1024 + with open(filename, 'rb') as fh: + assert fh.read(size) == TEST_DATA + assert fh.read(1025) == b'\0' * 1024 + + # Truncate data + os.truncate(filename, size - 1024) + assert os.stat(filename).st_size == size - 1024 + with open(filename, 'rb') as fh: + assert fh.read(size) == TEST_DATA[:size-1024] + + os.unlink(filename) + +def tst_truncate_fd(mnt_dir): + assert len(TEST_DATA) > 1024 + with NamedTemporaryFile('w+b', 0, dir=mnt_dir) as fh: + fd = fh.fileno() + fh.write(TEST_DATA) + fstat = os.fstat(fd) + size = fstat.st_size + assert size == len(TEST_DATA) + + # Add zeros at the end + os.ftruncate(fd, size + 1024) + assert os.fstat(fd).st_size == size + 1024 + fh.seek(0) + assert fh.read(size) == TEST_DATA + assert fh.read(1025) == b'\0' * 1024 + + # Truncate data + os.ftruncate(fd, size - 1024) + assert os.fstat(fd).st_size == size - 1024 + fh.seek(0) + assert fh.read(size) == TEST_DATA[:size-1024] + +def tst_utimens(mnt_dir, ns_tol=0): + filename = pjoin(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + + atime = fstat.st_atime + 42.28 + mtime = fstat.st_mtime - 42.23 + if sys.version_info < (3,3): + os.utime(filename, (atime, mtime)) + else: + atime_ns = fstat.st_atime_ns + int(42.28*1e9) + mtime_ns = fstat.st_mtime_ns - int(42.23*1e9) + os.utime(filename, None, ns=(atime_ns, mtime_ns)) + + fstat = os.lstat(filename) + + assert abs(fstat.st_atime - atime) < 1 + assert abs(fstat.st_mtime - mtime) < 1 + if sys.version_info >= (3,3): + assert abs(fstat.st_atime_ns - atime_ns) <= ns_tol + assert abs(fstat.st_mtime_ns - mtime_ns) <= ns_tol + +def tst_passthrough(src_dir, mnt_dir): + name = name_generator() + src_name = pjoin(src_dir, name) + mnt_name = pjoin(src_dir, name) + assert name not in os.listdir(src_dir) + assert name not in os.listdir(mnt_dir) + with open(src_name, 'w') as fh: + fh.write('Hello, world') + assert name in os.listdir(src_dir) + assert name in os.listdir(mnt_dir) + assert os.stat(src_name) == os.stat(mnt_name) + + name = name_generator() + src_name = pjoin(src_dir, name) + mnt_name = pjoin(src_dir, name) + assert name not in os.listdir(src_dir) + assert name not in os.listdir(mnt_dir) + with open(mnt_name, 'w') as fh: + fh.write('Hello, world') + assert name in os.listdir(src_dir) + assert name in os.listdir(mnt_dir) + assert os.stat(src_name) == os.stat(mnt_name) + + +def tst_xattr(path): + os.setxattr(path, b'hello_ll_setxattr_name', b'hello_ll_setxattr_value') + assert os.getxattr(path, b'hello_ll_getxattr_name') == b'hello_ll_getxattr_value' + os.removexattr(path, b'hello_ll_removexattr_name') + + +# avoid warning about unused import +assert test_printcap diff --git a/test/test_setattr.c b/test/test_setattr.c new file mode 100644 index 0000000..ac55264 --- /dev/null +++ b/test/test_setattr.c @@ -0,0 +1,194 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + + +#define FUSE_USE_VERSION 30 + +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#define FILE_INO 2 +#define FILE_NAME "truncate_me" + +static int got_fh; +static mode_t file_mode = S_IFREG | 0644; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = file_mode; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; + + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 5); +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else { + assert(ino == FILE_INO); + fi->fh = FILE_INO; + fuse_reply_open(req, fi); + } +} + +static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi) { + if(ino != FILE_INO || + !(to_set & FUSE_SET_ATTR_MODE)) { + fuse_reply_err(req, EINVAL); + return; + } + + if(fi == NULL) + fprintf(stderr, "setattr with fi == NULL\n"); + else if (fi->fh != FILE_INO) + fprintf(stderr, "setattr with wrong fi->fh\n"); + else { + fprintf(stderr, "setattr ok\n"); + got_fh = 1; + file_mode = attr->st_mode; + } + + tfs_getattr(req, ino, fi); +} + +static struct fuse_lowlevel_ops tfs_oper = { + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .open = tfs_open, + .setattr = tfs_setattr, +}; + +static void* run_fs(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + assert(fuse_session_loop(se) == 0); + return NULL; +} + +static void test_fs(char *mountpoint) { + char fname[PATH_MAX]; + int fd; + + assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, + mountpoint) > 0); + fd = open(fname, O_WRONLY); + if (fd == -1) { + perror(fname); + assert(0); + } + + assert(fchmod(fd, 0600) == 0); + close(fd); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +#endif + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + assert (se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + + /* Start file-system thread */ + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + + /* Do test */ + test_fs(fuse_opts.mountpoint); + + /* Stop file system */ + assert(pthread_cancel(fs_thread) == 0); + + fuse_session_unmount(se); + assert(got_fh == 1); + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + + printf("Test completed successfully.\n"); + return 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/test/test_syscalls.c b/test/test_syscalls.c new file mode 100644 index 0000000..4bbe973 --- /dev/null +++ b/test/test_syscalls.c @@ -0,0 +1,2198 @@ +#define _GNU_SOURCE +#include "fuse_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ALLPERMS +# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */ +#endif + + +static const char *basepath; +static const char *basepath_r; +static char testfile[1024]; +static char testfile2[1024]; +static char testdir[1024]; +static char testdir2[1024]; +static char testsock[1024]; +static char subfile[1280]; + +static char testfile_r[1024]; +static char testfile2_r[1024]; +static char testdir_r[1024]; +static char testdir2_r[1024]; +static char subfile_r[1280]; + +static char testname[256]; +static char testdata[] = "abcdefghijklmnopqrstuvwxyz"; +static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./"; +static const char *testdir_files[] = { "f1", "f2", NULL}; +static long seekdir_offsets[4]; +static char zerodata[4096]; +static int testdatalen = sizeof(testdata) - 1; +static int testdata2len = sizeof(testdata2) - 1; +static unsigned int testnum = 0; +static unsigned int select_test = 0; +static unsigned int skip_test = 0; +static unsigned int unlinked_test = 0; + +#define MAX_ENTRIES 1024 +#define MAX_TESTS 100 + +static struct test { + int fd; + struct stat stat; +} tests[MAX_TESTS]; + +static void test_perror(const char *func, const char *msg) +{ + fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg, + strerror(errno)); +} + +static void test_error(const char *func, const char *msg, ...) + __attribute__ ((format (printf, 2, 3))); + +static void __start_test(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static void test_error(const char *func, const char *msg, ...) +{ + va_list ap; + fprintf(stderr, "%s %s() - ", testname, func); + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static int is_dot_or_dotdot(const char *name) { + return name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +static void success(void) +{ + fprintf(stderr, "%s OK\n", testname); +} + +#define this_test (&tests[testnum-1]) +#define next_test (&tests[testnum]) + +static void __start_test(const char *fmt, ...) +{ + unsigned int n; + va_list ap; + n = sprintf(testname, "%3i [", testnum); + va_start(ap, fmt); + n += vsprintf(testname + n, fmt, ap); + va_end(ap); + sprintf(testname + n, "]"); + // Use dedicated testfile per test + sprintf(testfile, "%s/testfile.%d", basepath, testnum); + sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum); + if (testnum > MAX_TESTS) { + fprintf(stderr, "%s - too many tests\n", testname); + exit(1); + } + this_test->fd = -1; +} + +#define start_test(msg, args...) { \ + testnum++; \ + if ((select_test && testnum != select_test) || \ + (testnum == skip_test)) { \ + return 0; \ + } \ + __start_test(msg, ##args); \ +} + +#define PERROR(msg) test_perror(__FUNCTION__, msg) +#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static int st_check_size(struct stat *st, int len) +{ + if (st->st_size != len) { + ERROR("length %u instead of %u", (int) st->st_size, + (int) len); + return -1; + } + return 0; +} + +static int check_size(const char *path, int len) +{ + struct stat stbuf; + int res = stat(path, &stbuf); + if (res == -1) { + PERROR("stat"); + return -1; + } + return st_check_size(&stbuf, len); +} + +static int check_testfile_size(const char *path, int len) +{ + this_test->stat.st_size = len; + return check_size(path, len); +} + +static int st_check_type(struct stat *st, mode_t type) +{ + if ((st->st_mode & S_IFMT) != type) { + ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type); + return -1; + } + return 0; +} + +static int check_type(const char *path, mode_t type) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_type(&stbuf, type); +} + +static int st_check_mode(struct stat *st, mode_t mode) +{ + if ((st->st_mode & ALLPERMS) != mode) { + ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS, + mode); + return -1; + } + return 0; +} + +static int check_mode(const char *path, mode_t mode) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_mode(&stbuf, mode); +} + +static int check_testfile_mode(const char *path, mode_t mode) +{ + this_test->stat.st_mode &= ~ALLPERMS; + this_test->stat.st_mode |= mode; + return check_mode(path, mode); +} + +static int check_times(const char *path, time_t atime, time_t mtime) +{ + int err = 0; + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + if (stbuf.st_atime != atime) { + ERROR("atime %li instead of %li", stbuf.st_atime, atime); + err--; + } + if (stbuf.st_mtime != mtime) { + ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime); + err--; + } + if (err) + return -1; + + return 0; +} + +#if 0 +static int fcheck_times(int fd, time_t atime, time_t mtime) +{ + int err = 0; + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) { + PERROR("fstat"); + return -1; + } + if (stbuf.st_atime != atime) { + ERROR("atime %li instead of %li", stbuf.st_atime, atime); + err--; + } + if (stbuf.st_mtime != mtime) { + ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime); + err--; + } + if (err) + return -1; + + return 0; +} +#endif + +static int st_check_nlink(struct stat *st, nlink_t nlink) +{ + if (st->st_nlink != nlink) { + ERROR("nlink %li instead of %li", (long) st->st_nlink, + (long) nlink); + return -1; + } + return 0; +} + +static int check_nlink(const char *path, nlink_t nlink) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_nlink(&stbuf, nlink); +} + +static int fcheck_stat(int fd, int flags, struct stat *st) +{ + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) { + if (flags & O_PATH) { + // With O_PATH fd, the server does not have to keep + // the inode alive so FUSE inode may be stale or bad + if (errno == ESTALE || errno == EIO || + errno == ENOENT || errno == EBADF) + return 0; + } + PERROR("fstat"); + return -1; + } + + int err = 0; + err += st_check_type(&stbuf, st->st_mode & S_IFMT); + err += st_check_mode(&stbuf, st->st_mode & ALLPERMS); + err += st_check_size(&stbuf, st->st_size); + err += st_check_nlink(&stbuf, st->st_nlink); + + return err; +} + +static int check_nonexist(const char *path) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == 0) { + ERROR("file should not exist"); + return -1; + } + if (errno != ENOENT) { + ERROR("file should not exist: %s", strerror(errno)); + return -1; + } + return 0; +} + +static int check_buffer(const char *buf, const char *data, unsigned len) +{ + if (memcmp(buf, data, len) != 0) { + ERROR("data mismatch"); + return -1; + } + return 0; +} + +static int check_data(const char *path, const char *data, int offset, + unsigned len) +{ + char buf[4096]; + int res; + int fd = open(path, O_RDONLY); + if (fd == -1) { + PERROR("open"); + return -1; + } + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { + PERROR("lseek"); + close(fd); + return -1; + } + while (len) { + int rdlen = len < sizeof(buf) ? len : sizeof(buf); + res = read(fd, buf, rdlen); + if (res == -1) { + PERROR("read"); + close(fd); + return -1; + } + if (res != rdlen) { + ERROR("short read: %u instead of %u", res, rdlen); + close(fd); + return -1; + } + if (check_buffer(buf, data, rdlen) != 0) { + close(fd); + return -1; + } + data += rdlen; + len -= rdlen; + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + return 0; +} + +static int fcheck_data(int fd, const char *data, int offset, + unsigned len) +{ + char buf[4096]; + int res; + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { + PERROR("lseek"); + return -1; + } + while (len) { + int rdlen = len < sizeof(buf) ? len : sizeof(buf); + res = read(fd, buf, rdlen); + if (res == -1) { + PERROR("read"); + return -1; + } + if (res != rdlen) { + ERROR("short read: %u instead of %u", res, rdlen); + return -1; + } + if (check_buffer(buf, data, rdlen) != 0) { + return -1; + } + data += rdlen; + len -= rdlen; + } + return 0; +} + +static int check_dir_contents(const char *path, const char **contents) +{ + int i; + int res; + int err = 0; + int found[MAX_ENTRIES]; + const char *cont[MAX_ENTRIES]; + DIR *dp; + + for (i = 0; contents[i]; i++) { + assert(i < MAX_ENTRIES - 3); + found[i] = 0; + cont[i] = contents[i]; + } + cont[i] = NULL; + + dp = opendir(path); + if (dp == NULL) { + PERROR("opendir"); + return -1; + } + memset(found, 0, sizeof(found)); + while(1) { + struct dirent *de; + errno = 0; + de = readdir(dp); + if (de == NULL) { + if (errno) { + PERROR("readdir"); + closedir(dp); + return -1; + } + break; + } + if (is_dot_or_dotdot(de->d_name)) + continue; + for (i = 0; cont[i] != NULL; i++) { + assert(i < MAX_ENTRIES); + if (strcmp(cont[i], de->d_name) == 0) { + if (found[i]) { + ERROR("duplicate entry <%s>", + de->d_name); + err--; + } else + found[i] = 1; + break; + } + } + if (!cont[i]) { + ERROR("unexpected entry <%s>", de->d_name); + err --; + } + } + for (i = 0; cont[i] != NULL; i++) { + if (!found[i]) { + ERROR("missing entry <%s>", cont[i]); + err--; + } + } + res = closedir(dp); + if (res == -1) { + PERROR("closedir"); + return -1; + } + if (err) + return -1; + + return 0; +} + +static int create_file(const char *path, const char *data, int len) +{ + int res; + int fd; + + unlink(path); + fd = creat(path, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + if (len) { + res = write(fd, data, len); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != len) { + ERROR("write is short: %u instead of %u", res, len); + close(fd); + return -1; + } + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = check_type(path, S_IFREG); + if (res == -1) + return -1; + res = check_mode(path, 0644); + if (res == -1) + return -1; + res = check_nlink(path, 1); + if (res == -1) + return -1; + res = check_size(path, len); + if (res == -1) + return -1; + + if (len) { + res = check_data(path, data, 0, len); + if (res == -1) + return -1; + } + + return 0; +} + +static int create_path_fd(const char *path, const char *data, int len) +{ + int path_fd; + int res; + + res = create_file(path, data, len); + if (res == -1) + return -1; + + path_fd = open(path, O_PATH); + if (path_fd == -1) + PERROR("open(O_PATH)"); + + return path_fd; +} + +// Can be called once per test +static int create_testfile(const char *path, const char *data, int len) +{ + struct test *t = this_test; + struct stat *st = &t->stat; + int res, fd; + + if (t->fd > 0) { + ERROR("testfile already created"); + return -1; + } + + fd = create_path_fd(path, data, len); + if (fd == -1) + return -1; + + t->fd = fd; + + res = fstat(fd, st); + if (res == -1) { + PERROR("fstat"); + return -1; + } + + return 0; +} + +static int check_unlinked_testfile(int fd) +{ + struct stat *st = &this_test->stat; + + st->st_nlink = 0; + return fcheck_stat(fd, O_PATH, st); +} + +// Check recorded testfiles after all tests completed +static int check_unlinked_testfiles(void) +{ + int fd; + int res, err = 0; + int num = testnum; + + if (!unlinked_test) + return 0; + + testnum = 0; + while (testnum < num) { + fd = next_test->fd; + start_test("check_unlinked_testfile"); + if (fd == -1) + continue; + + err += check_unlinked_testfile(fd); + res = close(fd); + if (res == -1) { + PERROR("close(test_fd)"); + err--; + } + } + + if (err) { + fprintf(stderr, "%i unlinked testfile checks failed\n", -err); + return 1; + } + + return err; +} + +static int cleanup_dir(const char *path, const char **dir_files, int quiet) +{ + int i; + int err = 0; + + for (i = 0; dir_files[i]; i++) { + int res; + char fpath[1280]; + sprintf(fpath, "%s/%s", path, dir_files[i]); + res = unlink(fpath); + if (res == -1 && !quiet) { + PERROR("unlink"); + err --; + } + } + if (err) + return -1; + + return 0; +} + +static int create_dir(const char *path, const char **dir_files) +{ + int res; + int i; + + rmdir(path); + res = mkdir(path, 0755); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + res = check_type(path, S_IFDIR); + if (res == -1) + return -1; + res = check_mode(path, 0755); + if (res == -1) + return -1; + + for (i = 0; dir_files[i]; i++) { + char fpath[1280]; + sprintf(fpath, "%s/%s", path, dir_files[i]); + res = create_file(fpath, "", 0); + if (res == -1) { + cleanup_dir(path, dir_files, 1); + return -1; + } + } + res = check_dir_contents(path, dir_files); + if (res == -1) { + cleanup_dir(path, dir_files, 1); + return -1; + } + + return 0; +} + +static int test_truncate(int len) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + + start_test("truncate(%u)", (int) len); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + res = truncate(testfile, len); + if (res == -1) { + PERROR("truncate"); + return -1; + } + res = check_testfile_size(testfile, len); + if (res == -1) + return -1; + + if (len > 0) { + if (len <= datalen) { + res = check_data(testfile, data, 0, len); + if (res == -1) + return -1; + } else { + res = check_data(testfile, data, 0, datalen); + if (res == -1) + return -1; + res = check_data(testfile, zerodata, datalen, + len - datalen); + if (res == -1) + return -1; + } + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_ftruncate(int len, int mode) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + int fd; + + start_test("ftruncate(%u) mode: 0%03o", len, mode); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + fd = open(testfile, O_WRONLY); + if (fd == -1) { + PERROR("open"); + return -1; + } + + res = fchmod(fd, mode); + if (res == -1) { + PERROR("fchmod"); + close(fd); + return -1; + } + res = check_testfile_mode(testfile, mode); + if (res == -1) { + close(fd); + return -1; + } + res = ftruncate(fd, len); + if (res == -1) { + PERROR("ftruncate"); + close(fd); + return -1; + } + close(fd); + res = check_testfile_size(testfile, len); + if (res == -1) + return -1; + + if (len > 0) { + if (len <= datalen) { + res = check_data(testfile, data, 0, len); + if (res == -1) + return -1; + } else { + res = check_data(testfile, data, 0, datalen); + if (res == -1) + return -1; + res = check_data(testfile, zerodata, datalen, + len - datalen); + if (res == -1) + return -1; + } + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_seekdir(void) +{ + int i; + int res; + DIR *dp; + struct dirent *de = NULL; + + start_test("seekdir"); + res = create_dir(testdir, testdir_files); + if (res == -1) + return res; + + dp = opendir(testdir); + if (dp == NULL) { + PERROR("opendir"); + return -1; + } + + /* Remember dir offsets */ + for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) { + seekdir_offsets[i] = telldir(dp); + errno = 0; + de = readdir(dp); + if (de == NULL) { + if (errno) { + PERROR("readdir"); + goto fail; + } + break; + } + } + + /* Walk until the end of directory */ + while (de) + de = readdir(dp); + + /* Start from the last valid dir offset and seek backwards */ + for (i--; i >= 0; i--) { + seekdir(dp, seekdir_offsets[i]); + de = readdir(dp); + if (de == NULL) { + ERROR("Unexpected end of directory after seekdir()"); + goto fail; + } + } + + closedir(dp); + res = cleanup_dir(testdir, testdir_files, 0); + if (!res) + success(); + return res; +fail: + closedir(dp); + cleanup_dir(testdir, testdir_files, 1); + return -1; +} + +#ifdef HAVE_COPY_FILE_RANGE +static int test_copy_file_range(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd_in, fd_out; + off_t pos_in = 0, pos_out = 0; + + start_test("copy_file_range"); + unlink(testfile); + fd_in = open(testfile, O_CREAT | O_RDWR, 0644); + if (fd_in == -1) { + PERROR("creat"); + return -1; + } + res = write(fd_in, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd_in); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd_in); + return -1; + } + + unlink(testfile2); + fd_out = creat(testfile2, 0644); + if (fd_out == -1) { + PERROR("creat"); + close(fd_in); + return -1; + } + res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0); + if (res == -1) { + PERROR("copy_file_range"); + close(fd_in); + close(fd_out); + return -1; + } + if (res != datalen) { + ERROR("copy is short: %u instead of %u", res, datalen); + close(fd_in); + close(fd_out); + return -1; + } + + res = close(fd_in); + if (res == -1) { + PERROR("close"); + close(fd_out); + return -1; + } + res = close(fd_out); + if (res == -1) { + PERROR("close"); + return -1; + } + + err = check_data(testfile2, data, 0, datalen); + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} +#else +static int test_copy_file_range(void) +{ + return 0; +} +#endif + +static int test_utime(void) +{ + struct utimbuf utm; + time_t atime = 987631200; + time_t mtime = 123116400; + int res; + + start_test("utime"); + res = create_testfile(testfile, NULL, 0); + if (res == -1) + return -1; + + utm.actime = atime; + utm.modtime = mtime; + res = utime(testfile, &utm); + if (res == -1) { + PERROR("utime"); + return -1; + } + res = check_times(testfile, atime, mtime); + if (res == -1) { + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_create(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd; + + start_test("create"); + unlink(testfile); + fd = creat(testfile, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + res = write(fd, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd); + return -1; + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + err += check_size(testfile, datalen); + err += check_data(testfile, data, 0, datalen); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_create_unlink(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd; + + start_test("create+unlink"); + unlink(testfile); + fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + close(fd); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) { + close(fd); + return -1; + } + res = write(fd, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd); + return -1; + } + struct stat st = { + .st_mode = S_IFREG | 0644, + .st_size = datalen, + }; + err = fcheck_stat(fd, O_RDWR, &st); + err += fcheck_data(fd, data, 0, datalen); + res = close(fd); + if (res == -1) { + PERROR("close"); + err--; + } + if (err) + return -1; + + success(); + return 0; +} + +static int test_mknod(void) +{ + int err = 0; + int res; + + start_test("mknod"); + unlink(testfile); + res = mknod(testfile, 0644, 0); + if (res == -1) { + PERROR("mknod"); + return -1; + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + err += check_size(testfile, 0); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode) + +static int do_test_open(int exist, int flags, const char *flags_str, int mode) +{ + char buf[4096]; + const char *data = testdata; + int datalen = testdatalen; + unsigned currlen = 0; + int err = 0; + int res; + int fd; + off_t off; + + start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode); + unlink(testfile); + if (exist) { + res = create_file(testfile_r, testdata2, testdata2len); + if (res == -1) + return -1; + + currlen = testdata2len; + } + + fd = open(testfile, flags, mode); + if ((flags & O_CREAT) && (flags & O_EXCL) && exist) { + if (fd != -1) { + ERROR("open should have failed"); + close(fd); + return -1; + } else if (errno == EEXIST) + goto succ; + } + if (!(flags & O_CREAT) && !exist) { + if (fd != -1) { + ERROR("open should have failed"); + close(fd); + return -1; + } else if (errno == ENOENT) + goto succ; + } + if (fd == -1) { + PERROR("open"); + return -1; + } + + if (flags & O_TRUNC) + currlen = 0; + + err += check_type(testfile, S_IFREG); + if (exist) + err += check_mode(testfile, 0644); + else + err += check_mode(testfile, mode); + err += check_nlink(testfile, 1); + err += check_size(testfile, currlen); + if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR)) + err += check_data(testfile, testdata2, 0, testdata2len); + + res = write(fd, data, datalen); + if ((flags & O_ACCMODE) != O_RDONLY) { + if (res == -1) { + PERROR("write"); + err --; + } else if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + err --; + } else { + if (datalen > (int) currlen) + currlen = datalen; + + err += check_size(testfile, currlen); + + if (mode & S_IRUSR) { + err += check_data(testfile, data, 0, datalen); + if (exist && !(flags & O_TRUNC) && + testdata2len > datalen) + err += check_data(testfile, + testdata2 + datalen, + datalen, + testdata2len - datalen); + } + } + } else { + if (res != -1) { + ERROR("write should have failed"); + err --; + } else if (errno != EBADF) { + PERROR("write"); + err --; + } + } + off = lseek(fd, SEEK_SET, 0); + if (off == (off_t) -1) { + PERROR("lseek"); + err--; + } else if (off != 0) { + ERROR("offset should have returned 0"); + err --; + } + res = read(fd, buf, sizeof(buf)); + if ((flags & O_ACCMODE) != O_WRONLY) { + if (res == -1) { + PERROR("read"); + err--; + } else { + int readsize = + currlen < sizeof(buf) ? currlen : sizeof(buf); + if (res != readsize) { + ERROR("read is short: %i instead of %u", + res, readsize); + err--; + } else { + if ((flags & O_ACCMODE) != O_RDONLY) { + err += check_buffer(buf, data, datalen); + if (exist && !(flags & O_TRUNC) && + testdata2len > datalen) + err += check_buffer(buf + datalen, + testdata2 + datalen, + testdata2len - datalen); + } else if (exist) + err += check_buffer(buf, testdata2, + testdata2len); + } + } + } else { + if (res != -1) { + ERROR("read should have failed"); + err --; + } else if (errno != EBADF) { + PERROR("read"); + err --; + } + } + + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_nonexist(testfile_r); + if (res == -1) + return -1; + if (err) + return -1; + +succ: + success(); + return 0; +} + +#define test_open_acc(flags, mode, err) \ + do_test_open_acc(flags, #flags, mode, err) + +static int do_test_open_acc(int flags, const char *flags_str, int mode, int err) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + int fd; + + start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode, + strerror(err)); + unlink(testfile); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + res = chmod(testfile, mode); + if (res == -1) { + PERROR("chmod"); + return -1; + } + + res = check_testfile_mode(testfile, mode); + if (res == -1) + return -1; + + fd = open(testfile, flags); + if (fd == -1) { + if (err != errno) { + PERROR("open"); + return -1; + } + } else { + if (err) { + ERROR("open should have failed"); + close(fd); + return -1; + } + close(fd); + } + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_nonexist(testfile_r); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_symlink(void) +{ + char buf[1024]; + const char *data = testdata; + int datalen = testdatalen; + int linklen = strlen(testfile); + int err = 0; + int res; + + start_test("symlink"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = symlink(testfile, testfile2); + if (res == -1) { + PERROR("symlink"); + return -1; + } + res = check_type(testfile2, S_IFLNK); + if (res == -1) + return -1; + err += check_mode(testfile2, 0777); + err += check_nlink(testfile2, 1); + res = readlink(testfile2, buf, sizeof(buf)); + if (res == -1) { + PERROR("readlink"); + err--; + } + if (res != linklen) { + ERROR("short readlink: %u instead of %u", res, linklen); + err--; + } + if (memcmp(buf, testfile, linklen) != 0) { + ERROR("link mismatch"); + err--; + } + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_link(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("link"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = link(testfile, testfile2); + if (res == -1) { + PERROR("link"); + return -1; + } + res = check_type(testfile2, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile2, 0644); + err += check_nlink(testfile2, 2); + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + err += check_nlink(testfile2, 1); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_link2(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("link-unlink-link"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = link(testfile, testfile2); + if (res == -1) { + PERROR("link"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = link(testfile2, testfile); + if (res == -1) { + PERROR("link"); + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 2); + err += check_size(testfile, datalen); + err += check_data(testfile, data, 0, datalen); + + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + err += check_nlink(testfile, 1); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_file(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("rename file"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = rename(testfile, testfile2); + if (res == -1) { + PERROR("rename"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_type(testfile2, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile2, 0644); + err += check_nlink(testfile2, 1); + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_dir(void) +{ + int err = 0; + int res; + + start_test("rename dir"); + res = create_dir(testdir, testdir_files); + if (res == -1) + return -1; + + rmdir(testdir2); + res = rename(testdir, testdir2); + if (res == -1) { + PERROR("rename"); + cleanup_dir(testdir, testdir_files, 1); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) { + cleanup_dir(testdir, testdir_files, 1); + return -1; + } + res = check_type(testdir2, S_IFDIR); + if (res == -1) { + cleanup_dir(testdir2, testdir_files, 1); + return -1; + } + err += check_mode(testdir2, 0755); + err += check_dir_contents(testdir2, testdir_files); + err += cleanup_dir(testdir2, testdir_files, 0); + res = rmdir(testdir2); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_dir_loop(void) +{ +#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path) +#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2) + + char path[1280], path2[1280]; + int err = 0; + int res; + + start_test("rename dir loop"); + + res = create_dir(testdir, testdir_files); + if (res == -1) + return -1; + + res = mkdir(PATH("a"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a"), PATH2("a")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/b"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/b/c"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c/a")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b/c"), PATH2("a")); + if (res == 0 || errno != ENOTEMPTY) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/foo"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/c/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/c/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/bar"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + unlink(PATH("a/bar")); + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/d/e"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) { + PERROR("rename"); + goto fail; + } + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + err += cleanup_dir(testdir, testdir_files, 0); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + goto fail; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; + +fail: + unlink(PATH("a/bar")); + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + cleanup_dir(testdir, testdir_files, 1); + rmdir(testdir); + + return -1; + +#undef PATH2 +#undef PATH +} + +static int test_mkfifo(void) +{ + int res; + int err = 0; + + start_test("mkfifo"); + unlink(testfile); + res = mkfifo(testfile, 0644); + if (res == -1) { + PERROR("mkfifo"); + return -1; + } + res = check_type(testfile, S_IFIFO); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_mkdir(void) +{ + int res; + int err = 0; + const char *dir_contents[] = {NULL}; + + start_test("mkdir"); + rmdir(testdir); + res = mkdir(testdir, 0755); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + res = check_type(testdir, S_IFDIR); + if (res == -1) + return -1; + err += check_mode(testdir, 0755); + /* Some file systems (like btrfs) don't track link + count for directories */ + //err += check_nlink(testdir, 2); + err += check_dir_contents(testdir, dir_contents); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_socket(void) +{ + struct sockaddr_un su; + int fd; + int res; + int err = 0; + const size_t test_sock_len = strlen(testsock) + 1; + + start_test("socket"); + if (test_sock_len > sizeof(su.sun_path)) { + fprintf(stderr, "Need to shorten mount point by %zu chars\n", + strlen(testsock) + 1 - sizeof(su.sun_path)); + return -1; + } + unlink(testsock); + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + PERROR("socket"); + return -1; + } + su.sun_family = AF_UNIX; + + strncpy(su.sun_path, testsock, test_sock_len); + su.sun_path[sizeof(su.sun_path) - 1] = '\0'; + res = bind(fd, (struct sockaddr*)&su, sizeof(su)); + if (res == -1) { + PERROR("bind"); + return -1; + } + + res = check_type(testsock, S_IFSOCK); + if (res == -1) { + close(fd); + return -1; + } + err += check_nlink(testsock, 1); + close(fd); + res = unlink(testsock); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testsock); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#define test_create_ro_dir(flags) \ + do_test_create_ro_dir(flags, #flags) + +static int do_test_create_ro_dir(int flags, const char *flags_str) +{ + int res; + int err = 0; + int fd; + + start_test("open(%s) in read-only directory", flags_str); + rmdir(testdir); + res = mkdir(testdir, 0555); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + fd = open(subfile, flags, 0644); + if (fd != -1) { + close(fd); + unlink(subfile); + ERROR("open should have failed"); + err--; + } else { + res = check_nonexist(subfile); + if (res == -1) + err--; + } + unlink(subfile); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#ifndef __FreeBSD__ +/* this tests open with O_TMPFILE + note that this will only work with the fuse low level api + you will get ENOTSUP with the high level api */ +static int test_create_tmpfile(void) +{ + rmdir(testdir); + int res = mkdir(testdir, 0777); + if (res) + return -1; + + start_test("create tmpfile"); + + int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); + if(fd == -1) { + if (errno == ENOTSUP) { + /* don't bother if we're working on an old kernel + or on the high level API */ + return 0; + } + + PERROR("open O_TMPFILE | O_RDWR"); + return -1; + } + close(fd); + + fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); + if(fd == -1){ + PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL"); + return -1; + }; + close(fd); + + fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR); + if (fd != -1) { + ERROR("open with O_TMPFILE | O_RDONLY succeeded"); + return -1; + } + + success(); + return 0; +} + +static int test_create_and_link_tmpfile(void) +{ + /* skip this test for now since the github runner will fail in the linkat call below */ + return 0; + + rmdir(testdir); + unlink(testfile); + + int res = mkdir(testdir, 0777); + if (res) + return -1; + + start_test("create and link tmpfile"); + + int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR); + if(fd == -1) { + if (errno == ENOTSUP) { + /* don't bother if we're working on an old kernel + or on the high level API */ + return 0; + } + PERROR("open with O_TMPFILE | O_RDWR | O_EXCL"); + return -1; + } + + if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) { + ERROR("linkat succeeded on a tmpfile opened with O_EXCL"); + return -1; + } + close(fd); + + fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); + if(fd == -1) { + PERROR("open O_TMPFILE"); + return -1; + } + + if (check_nonexist(testfile)) { + return -1; + } + + if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) { + PERROR("linkat tempfile"); + return -1; + } + close(fd); + + if (check_nlink(testfile, 1)) { + return -1; + } + unlink(testfile); + + success(); + return 0; +} +#endif + +int main(int argc, char *argv[]) +{ + int err = 0; + int a; + int is_root; + + umask(0); + if (argc < 2 || argc > 4) { + fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]); + return 1; + } + basepath = argv[1]; + basepath_r = basepath; + for (a = 2; a < argc; a++) { + char *endptr; + char *arg = argv[a]; + if (arg[0] == ':') { + basepath_r = arg + 1; + } else { + if (arg[0] == '-') { + arg++; + if (arg[0] == 'u') { + unlinked_test = 1; + endptr = arg + 1; + } else { + skip_test = strtoul(arg, &endptr, 10); + } + } else { + select_test = strtoul(arg, &endptr, 10); + } + if (arg[0] == '\0' || *endptr != '\0') { + fprintf(stderr, "invalid option: '%s'\n", argv[a]); + return 1; + } + } + } + assert(strlen(basepath) < 512); + assert(strlen(basepath_r) < 512); + if (basepath[0] != '/') { + fprintf(stderr, "testdir must be an absolute path\n"); + return 1; + } + + sprintf(testfile, "%s/testfile", basepath); + sprintf(testfile2, "%s/testfile2", basepath); + sprintf(testdir, "%s/testdir", basepath); + sprintf(testdir2, "%s/testdir2", basepath); + sprintf(subfile, "%s/subfile", testdir2); + sprintf(testsock, "%s/testsock", basepath); + + sprintf(testfile_r, "%s/testfile", basepath_r); + sprintf(testfile2_r, "%s/testfile2", basepath_r); + sprintf(testdir_r, "%s/testdir", basepath_r); + sprintf(testdir2_r, "%s/testdir2", basepath_r); + sprintf(subfile_r, "%s/subfile", testdir2_r); + + is_root = (geteuid() == 0); + + err += test_create(); + err += test_create_unlink(); + err += test_symlink(); + err += test_link(); + err += test_link2(); + err += test_mknod(); + err += test_mkfifo(); + err += test_mkdir(); + err += test_rename_file(); + err += test_rename_dir(); + err += test_rename_dir_loop(); + err += test_seekdir(); + err += test_socket(); + err += test_utime(); + err += test_truncate(0); + err += test_truncate(testdatalen / 2); + err += test_truncate(testdatalen); + err += test_truncate(testdatalen + 100); + err += test_ftruncate(0, 0600); + err += test_ftruncate(testdatalen / 2, 0600); + err += test_ftruncate(testdatalen, 0600); + err += test_ftruncate(testdatalen + 100, 0600); + err += test_ftruncate(0, 0400); + err += test_ftruncate(0, 0200); + err += test_ftruncate(0, 0000); + err += test_open(0, O_RDONLY, 0); + err += test_open(1, O_RDONLY, 0); + err += test_open(1, O_RDWR, 0); + err += test_open(1, O_WRONLY, 0); + err += test_open(0, O_RDWR | O_CREAT, 0600); + err += test_open(1, O_RDWR | O_CREAT, 0600); + err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600); + err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600); + err += test_open(0, O_RDONLY | O_CREAT, 0600); + err += test_open(0, O_RDONLY | O_CREAT, 0400); + err += test_open(0, O_RDONLY | O_CREAT, 0200); + err += test_open(0, O_RDONLY | O_CREAT, 0000); + err += test_open(0, O_WRONLY | O_CREAT, 0600); + err += test_open(0, O_WRONLY | O_CREAT, 0400); + err += test_open(0, O_WRONLY | O_CREAT, 0200); + err += test_open(0, O_WRONLY | O_CREAT, 0000); + err += test_open(0, O_RDWR | O_CREAT, 0400); + err += test_open(0, O_RDWR | O_CREAT, 0200); + err += test_open(0, O_RDWR | O_CREAT, 0000); + err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600); + err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600); + err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000); + err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000); + err += test_open_acc(O_RDONLY, 0600, 0); + err += test_open_acc(O_WRONLY, 0600, 0); + err += test_open_acc(O_RDWR, 0600, 0); + err += test_open_acc(O_RDONLY, 0400, 0); + err += test_open_acc(O_WRONLY, 0200, 0); + if(!is_root) { + err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES); + err += test_open_acc(O_WRONLY, 0400, EACCES); + err += test_open_acc(O_RDWR, 0400, EACCES); + err += test_open_acc(O_RDONLY, 0200, EACCES); + err += test_open_acc(O_RDWR, 0200, EACCES); + err += test_open_acc(O_RDONLY, 0000, EACCES); + err += test_open_acc(O_WRONLY, 0000, EACCES); + err += test_open_acc(O_RDWR, 0000, EACCES); + } + err += test_create_ro_dir(O_CREAT); + err += test_create_ro_dir(O_CREAT | O_EXCL); + err += test_create_ro_dir(O_CREAT | O_WRONLY); + err += test_create_ro_dir(O_CREAT | O_TRUNC); + err += test_copy_file_range(); +#ifndef __FreeBSD__ + err += test_create_tmpfile(); + err += test_create_and_link_tmpfile(); +#endif + + unlink(testfile2); + unlink(testsock); + rmdir(testdir); + rmdir(testdir2); + + if (err) { + fprintf(stderr, "%i tests failed\n", -err); + return 1; + } + + return check_unlinked_testfiles(); +} diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c new file mode 100644 index 0000000..bee23cc --- /dev/null +++ b/test/test_want_conversion.c @@ -0,0 +1,164 @@ +#include "util.h" +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "fuse_i.h" +#include +#include +#include +#include + +static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) +{ + printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 "\n", prefix, + conn->want, conn->want_ext); +} + +static void application_init_old_style(struct fuse_conn_info *conn) +{ + /* Simulate application init the old style */ + conn->want |= FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_SPLICE_READ; +} + +static void application_init_new_style(struct fuse_conn_info *conn) +{ + /* Simulate application init the new style */ + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); +} + +static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style) +{ + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + /* High-level init */ + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + + conn->want = want_default; + + if (new_style) + application_init_new_style(conn); + else + application_init_old_style(conn); + + rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); + assert(rc == 0); +} + +static void test_do_init(struct fuse_conn_info *conn, bool new_style) +{ + /* Initial setup */ + conn->capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT | + FUSE_CAP_ASYNC_READ; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + conn->want_ext = conn->capable_ext; + + print_conn_info("Initial state", conn); + + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + conn->want = want_default; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + + test_fuse_fs_init(conn, new_style); + + rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); + assert(rc == 0); + + /* Verify all expected flags are set */ + assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ)); + assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE); + assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE); + assert(conn->want_ext & FUSE_CAP_POSIX_LOCKS); + assert(conn->want_ext & FUSE_CAP_FLOCK_LOCKS); + assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT); + assert(conn->want_ext & FUSE_CAP_ASYNC_READ); + /* Verify no other flags are set */ + assert(conn->want_ext == + (FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | + FUSE_CAP_POSIX_LOCKS | FUSE_CAP_FLOCK_LOCKS | + FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ)); + + print_conn_info("After init", conn); +} + +static void test_want_conversion_basic(void) +{ + struct fuse_conn_info conn = { 0 }; + + printf("\nTesting basic want conversion:\n"); + test_do_init(&conn, false); + test_do_init(&conn, true); + print_conn_info("After init", &conn); +} + +static void test_want_conversion_conflict(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion conflict:\n"); + + /* Test conflicting values */ + /* Initialize like fuse_lowlevel.c does */ + conn.capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS; + conn.capable = fuse_lower_32_bits(conn.capable_ext); + conn.want_ext = conn.capable_ext; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test conflict initial", &conn); + + /* Initialize default values like in basic test */ + uint64_t want_ext_default_ll = conn.want_ext; + uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); + + /* Simulate application init modifying capabilities */ + conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */ + conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */ + + rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, + want_default_ll); + assert(rc == -EINVAL); + print_conn_info("Test conflict after", &conn); + + printf("Want conversion conflict test passed\n"); +} + +static void test_want_conversion_high_bits(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion high bits preservation:\n"); + + /* Test high bits preservation */ + conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test high bits initial", &conn); + + uint64_t want_ext_default_ll = conn.want_ext; + uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); + + rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, + want_default_ll); + assert(rc == 0); + assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ)); + print_conn_info("Test high bits after", &conn); + + printf("Want conversion high bits test passed\n"); +} + +int main(void) +{ + test_want_conversion_basic(); + test_want_conversion_conflict(); + test_want_conversion_high_bits(); + return 0; +} diff --git a/test/test_write_cache.c b/test/test_write_cache.c new file mode 100644 index 0000000..9f21f02 --- /dev/null +++ b/test/test_write_cache.c @@ -0,0 +1,315 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + + +#define FUSE_USE_VERSION 30 + +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#define FILE_INO 2 +#define FILE_NAME "write_me" + +/* Command line parsing */ +struct options { + int writeback; + int data_size; + int delay_ms; +} options = { + .writeback = 0, + .data_size = 2048, + .delay_ms = 0, +}; + +#define WRITE_SYSCALLS 64 + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("writeback_cache", writeback), + OPTION("--data-size=%d", data_size), + OPTION("--delay_ms=%d", delay_ms), + FUSE_OPT_END +}; +static int got_write; +static atomic_int write_cnt; + +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static int write_start, write_done; + +static void tfs_init (void *userdata, struct fuse_conn_info *conn) +{ + (void) userdata; + + if(options.writeback) { + assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + } +} + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0222; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; + + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 5); +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else { + assert(ino == FILE_INO); + /* Test close(rofd) does not block waiting for pending writes */ + fi->noflush = !options.writeback && options.delay_ms && + (fi->flags & O_ACCMODE) == O_RDONLY; + fuse_reply_open(req, fi); + } +} + +static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi) { + (void) fi; (void) buf; (void) off; + size_t expected; + + assert(ino == FILE_INO); + expected = options.data_size; + if(options.writeback) + expected *= 2; + + write_cnt++; + + if(size != expected && !options.writeback) + fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!", + expected, size); + else + got_write = 1; + + /* Simulate waiting for pending writes */ + if (options.delay_ms) { + pthread_mutex_lock(&lock); + write_start = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + + usleep(options.delay_ms * 1000); + + pthread_mutex_lock(&lock); + write_done = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + } + + fuse_reply_write(req, size); +} + +static struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .open = tfs_open, + .write = tfs_write, +}; + +static void* close_rofd(void *data) { + int rofd = (int)(long) data; + + /* Wait for first write to start */ + pthread_mutex_lock(&lock); + while (!write_start && !write_done) + pthread_cond_wait(&cond, &lock); + pthread_mutex_unlock(&lock); + + close(rofd); + printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done); + + /* First write should not have been completed */ + if (write_done) + fprintf(stderr, "ERROR: close(rofd) blocked on write!\n"); + + return NULL; +} + +static void* run_fs(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + assert(fuse_session_loop(se) == 0); + return NULL; +} + +static void test_fs(char *mountpoint) { + char fname[PATH_MAX]; + char *buf; + const size_t iosize = options.data_size; + const size_t dsize = options.data_size * WRITE_SYSCALLS; + int fd, rofd; + pthread_t rofd_thread; + off_t off = 0; + + buf = malloc(dsize); + assert(buf != NULL); + assert((fd = open("/dev/urandom", O_RDONLY)) != -1); + assert(read(fd, buf, dsize) == dsize); + close(fd); + + assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, + mountpoint) > 0); + fd = open(fname, O_WRONLY); + if (fd == -1) { + perror(fname); + assert(0); + } + + if (options.delay_ms) { + /* Verify that close(rofd) does not block waiting for pending writes */ + rofd = open(fname, O_RDONLY); + assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0); + /* Give close_rofd time to start */ + usleep(options.delay_ms * 1000); + } + + for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) { + assert(pwrite(fd, buf + off, iosize, off) == iosize); + off += iosize; + assert(off <= dsize); + } + free(buf); + close(fd); + + if (options.delay_ms) { + printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done); + assert(pthread_join(rofd_thread, NULL) == 0); + } +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0); + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +#endif + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + fuse_opt_free_args(&args); + assert (se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + + /* Start file-system thread */ + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + + /* Write test data */ + test_fs(fuse_opts.mountpoint); + free(fuse_opts.mountpoint); + + /* Stop file system */ + fuse_session_exit(se); + fuse_session_unmount(se); + assert(pthread_join(fs_thread, NULL) == 0); + + assert(got_write == 1); + + /* + * when writeback cache is enabled, kernel side can merge requests, but + * memory pressure, system 'sync' might trigger data flushes before - flush + * might happen in between write syscalls - merging subpage writes into + * a single page and pages into large fuse requests might or might not work. + * Though we can expect that that at least some (but maybe all) write + * system calls can be merged. + */ + if (options.writeback) + assert(write_cnt < WRITE_SYSCALLS); + else + assert(write_cnt == WRITE_SYSCALLS); + + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + + printf("Test completed successfully.\n"); + return 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/test/util.py b/test/util.py new file mode 100644 index 0000000..a421e72 --- /dev/null +++ b/test/util.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +import subprocess +import pytest +import os +import stat +import time +from os.path import join as pjoin +import sys +import re +import itertools +from packaging import version +import logging + +basename = pjoin(os.path.dirname(__file__), '..') + +def parse_kernel_version(release): + # Extract the first three numbers from the kernel version string + match = re.match(r'^(\d+\.\d+\.\d+)', release) + if match: + return version.parse(match.group(1)) + return version.parse('0') + +def get_printcap(): + cmdline = base_cmdline + [ pjoin(basename, 'example', 'printcap') ] + proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, + universal_newlines=True) + (stdout, _) = proc.communicate(30) + assert proc.returncode == 0 + + proto = None + caps = set() + for line in stdout.split('\n'): + if line.startswith('\t'): + caps.add(line.strip()) + continue + + hit = re.match(r'Protocol version: (\d+)\.(\d+)$', line) + if hit: + proto = (int(hit.group(1)), int(hit.group(2))) + + return (proto, caps) + +def test_printcap(): + get_printcap() + +def wait_for_mount(mount_process, mnt_dir, + test_fn=os.path.ismount): + elapsed = 0 + while elapsed < 30: + if test_fn(mnt_dir): + return True + if mount_process.poll() is not None: + pytest.fail('file system process terminated prematurely') + time.sleep(0.1) + elapsed += 0.1 + pytest.fail("mountpoint failed to come up") + +def cleanup(mount_process, mnt_dir): + # Don't bother trying Valgrind if things already went wrong + + if 'bsd' in sys.platform or 'dragonfly' in sys.platform: + cmd = [ 'umount', '-f', mnt_dir ] + else: + cmd = [pjoin(basename, 'util', 'fusermount3'), + '-z', '-u', mnt_dir] + subprocess.call(cmd, stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT) + mount_process.terminate() + try: + mount_process.wait(1) + except subprocess.TimeoutExpired: + mount_process.kill() + +def umount(mount_process, mnt_dir): + logger = logging.getLogger(__name__) + logger.debug(f"Unmounting {mnt_dir}") + + if 'bsd' in sys.platform or 'dragonfly' in sys.platform: + cmdline = [ 'umount', mnt_dir ] + logger.debug("Using BSD-style umount command") + else: + logger.debug("Using fusermount3 for unmounting") + # fusermount3 will be setuid root, so we can only trace it with + # valgrind if we're root + if os.getuid() == 0: + cmdline = base_cmdline + logger.debug("Running as root, using valgrind if configured") + else: + cmdline = [] + logger.debug("Not running as root, skipping valgrind for fusermount3") + cmdline = cmdline + [ pjoin(basename, 'util', 'fusermount3'), + '-z', '-u', mnt_dir ] + + logger.debug(f"Unmount command: {' '.join(cmdline)}") + try: + result = subprocess.run(cmdline, capture_output=True, text=True, check=True) + if result.stdout: + logger.debug(f"Unmount command stdout: {result.stdout}") + if result.stderr: + logger.debug(f"Unmount command stderr: {result.stderr}") + except subprocess.CalledProcessError as e: + logger.error(f"Unmount command failed with return code {e.returncode}\nStdout: {e.stdout}\nStderr: {e.stderr}") + raise + + if not os.path.ismount(mnt_dir): + logger.debug(f"{mnt_dir} is no longer a mount point") + else: + logger.warning(f"{mnt_dir} is still a mount point after unmount command") + + # Give mount process a little while to terminate. Popen.wait(timeout) + # was only added in 3.3... + elapsed = 0 + while elapsed < 30: + code = mount_process.poll() + if code is not None: + if code == 0: + return + logger.error(f"File system process terminated with code {code}") + pytest.fail(f'file system process terminated with code {code}') + time.sleep(0.1) + elapsed += 0.1 + logger.error("Mount process did not terminate within 30 seconds") + pytest.fail('mount process did not terminate') + + +def safe_sleep(secs): + '''Like time.sleep(), but sleep for at least *secs* + + `time.sleep` may sleep less than the given period if a signal is + received. This function ensures that we sleep for at least the + desired time. + ''' + + now = time.time() + end = now + secs + while now < end: + time.sleep(end - now) + now = time.time() + +def fuse_test_marker(): + '''Return a pytest.marker that indicates FUSE availability + + If system/user/environment does not support FUSE, return + a `pytest.mark.skip` object with more details. If FUSE is + supported, return `pytest.mark.uses_fuse()`. + ''' + + skip = lambda x: pytest.mark.skip(reason=x) + + if 'bsd' in sys.platform or 'dragonfly' in sys.platform: + return pytest.mark.uses_fuse() + + with subprocess.Popen(['which', 'fusermount3'], stdout=subprocess.PIPE, + universal_newlines=True) as which: + fusermount_path = which.communicate()[0].strip() + + if not fusermount_path or which.returncode != 0: + return skip("Can't find fusermount executable") + + if not os.path.exists('/dev/fuse'): + return skip("FUSE kernel module does not seem to be loaded") + + if os.getuid() == 0: + return pytest.mark.uses_fuse() + + mode = os.stat(fusermount_path).st_mode + if mode & stat.S_ISUID == 0: + return skip('fusermount executable not setuid, and we are not root.') + + try: + fd = os.open('/dev/fuse', os.O_RDWR) + except OSError as exc: + return skip('Unable to open /dev/fuse: %s' % exc.strerror) + else: + os.close(fd) + + return pytest.mark.uses_fuse() + +def powerset(iterable): + s = list(iterable) + return itertools.chain.from_iterable( + itertools.combinations(s, r) for r in range(len(s)+1)) + +def create_tmpdir(mnt_dir): + if not os.path.exists(mnt_dir): + print("makedirs: '" + mnt_dir + "'") + os.makedirs(mnt_dir) + else: + print("mnt_dir exists: '" + mnt_dir + "'") + +# Use valgrind if requested +if os.environ.get('TEST_WITH_VALGRIND', 'no').lower().strip() \ + not in ('no', 'false', '0'): + base_cmdline = [ 'valgrind', '-q', '--' ] +else: + base_cmdline = [] + +# Try to use local fusermount3 +os.environ['PATH'] = '%s:%s' % (pjoin(basename, 'util'), os.environ['PATH']) +# Put example binaries on PATH +os.environ['PATH'] = '%s:%s' % (pjoin(basename, 'example'), os.environ['PATH']) + +try: + (fuse_proto, fuse_caps) = get_printcap() +except: + # Rely on test to raise error + fuse_proto = (0,0) + fuse_caps = set() + diff --git a/test/wrong_command.c b/test/wrong_command.c new file mode 100644 index 0000000..8b563c9 --- /dev/null +++ b/test/wrong_command.c @@ -0,0 +1,16 @@ +#include + +int main(void) { +#ifdef MESON_IS_SUBPROJECT + fprintf(stderr, "libfuse tests were skipped because it's a meson subproject.\n" + "If you wish to run them try:\n" + "'cd /subprojects/libfuse && meson . build && cd build && python3 -m pytest test/' instead"); + return 77; /* report as a skipped test */ +#else + fprintf(stderr, "\x1B[31m\e[1m" + "This is not the command you are looking for.\n" + "You probably want to run 'python3 -m pytest test/' instead" + "\e[0m\n"); + return 1; +#endif +} diff --git a/tsan_suppressions.txt b/tsan_suppressions.txt new file mode 100644 index 0000000..3470d15 --- /dev/null +++ b/tsan_suppressions.txt @@ -0,0 +1,5 @@ +# Use with +# TSAN_OPTIONS="suppressions=tsan_suppressions.txt" ./myprogram + +# False positive +race:pthread_setcancelstate diff --git a/util/fuse.conf b/util/fuse.conf new file mode 100644 index 0000000..ab048e0 --- /dev/null +++ b/util/fuse.conf @@ -0,0 +1,17 @@ +# The file /etc/fuse.conf allows for the following parameters: +# +# user_allow_other - Using the allow_other mount option works fine as root, but +# in order to have it work as a regular user, you need to set user_allow_other +# in /etc/fuse.conf as well. This option allows non-root users to use the +# allow_other option. You need allow_other if you want users other than the +# owner of a mounted fuse to access it. This option must appear on a line by +# itself. There is no value; just the presence of the option activates it. + +#user_allow_other + + +# mount_max = n - this option sets the maximum number of mounts. +# It must be typed exactly as shown (with a single space before and after the +# equals sign). + +#mount_max = 1000 diff --git a/util/fusermount.c b/util/fusermount.c new file mode 100644 index 0000000..da6d5f2 --- /dev/null +++ b/util/fusermount.c @@ -0,0 +1,1736 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ +/* This program does the mounting and unmounting of FUSE filesystems */ + +#define _GNU_SOURCE /* for clone,strchrnul and close_range */ +#include "fuse_config.h" +#include "mount_util.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuse_mount_compat.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_CLOSE_RANGE_H +#include +#endif + +#define FUSE_COMMFD_ENV "_FUSE_COMMFD" + +#define FUSE_DEV "/dev/fuse" + +static const char *progname; + +static int user_allow_other = 0; +static int mount_max = 1000; + +static int auto_unmount = 0; + +#ifdef GETMNTENT_NEEDS_UNESCAPING +// Older versions of musl libc don't unescape entries in /etc/mtab + +// unescapes octal sequences like \040 in-place +// That's ok, because unescaping can not extend the length of the string. +static void unescape(char *buf) { + char *src = buf; + char *dest = buf; + while (1) { + char *next_src = strchrnul(src, '\\'); + int offset = next_src - src; + memmove(dest, src, offset); + src = next_src; + dest += offset; + + if(*src == '\0') { + *dest = *src; + return; + } + src++; + + if('0' <= src[0] && src[0] < '2' && + '0' <= src[1] && src[1] < '8' && + '0' <= src[2] && src[2] < '8') { + *dest++ = (src[0] - '0') << 6 + | (src[1] - '0') << 3 + | (src[2] - '0') << 0; + src += 3; + } else if (src[0] == '\\') { + *dest++ = '\\'; + src += 1; + } else { + *dest++ = '\\'; + } + } +} + +static struct mntent *GETMNTENT(FILE *stream) +{ + struct mntent *entp = getmntent(stream); + if(entp != NULL) { + unescape(entp->mnt_fsname); + unescape(entp->mnt_dir); + unescape(entp->mnt_type); + unescape(entp->mnt_opts); + } + return entp; +} +#else +#define GETMNTENT getmntent +#endif // GETMNTENT_NEEDS_UNESCAPING + +/* + * Take a ',' separated option string and extract "x-" options + */ +static int extract_x_options(const char *original, char **non_x_opts, + char **x_opts) +{ + size_t orig_len; + const char *opt, *opt_end; + + orig_len = strlen(original) + 1; + + *non_x_opts = calloc(1, orig_len); + *x_opts = calloc(1, orig_len); + + size_t non_x_opts_len = orig_len; + size_t x_opts_len = orig_len; + + if (*non_x_opts == NULL || *x_opts == NULL) { + fprintf(stderr, "%s: Failed to allocate %zuB.\n", + __func__, orig_len); + return -ENOMEM; + } + + for (opt = original; opt < original + orig_len; opt = opt_end + 1) { + char *opt_buf; + + opt_end = strchr(opt, ','); + if (opt_end == NULL) + opt_end = original + orig_len; + + size_t opt_len = opt_end - opt; + size_t opt_len_left = orig_len - (opt - original); + size_t buf_len; + bool is_x_opts; + + if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) { + buf_len = x_opts_len; + is_x_opts = true; + opt_buf = *x_opts; + } else { + buf_len = non_x_opts_len; + is_x_opts = false; + opt_buf = *non_x_opts; + } + + if (buf_len < orig_len) { + strncat(opt_buf, ",", 2); + buf_len -= 1; + } + + /* omits ',' */ + if ((ssize_t)(buf_len - opt_len) < 0) { + /* This would be a bug */ + fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n", + __func__, original); + return -EIO; + } + + strncat(opt_buf, opt, opt_end - opt); + buf_len -= opt_len; + + if (is_x_opts) + x_opts_len = buf_len; + else + non_x_opts_len = buf_len; + } + + return 0; +} + +static const char *get_user_name(void) +{ + struct passwd *pw = getpwuid(getuid()); + if (pw != NULL && pw->pw_name != NULL) + return pw->pw_name; + else { + fprintf(stderr, "%s: could not determine username\n", progname); + return NULL; + } +} + +static uid_t oldfsuid; +static gid_t oldfsgid; + +static void drop_privs(void) +{ + if (getuid() != 0) { + oldfsuid = setfsuid(getuid()); + oldfsgid = setfsgid(getgid()); + } +} + +static void restore_privs(void) +{ + if (getuid() != 0) { + setfsuid(oldfsuid); + setfsgid(oldfsgid); + } +} + +#ifndef IGNORE_MTAB +/* + * Make sure that /etc/mtab is checked and updated atomically + */ +static int lock_umount(void) +{ + const char *mtab_lock = _PATH_MOUNTED ".fuselock"; + int mtablock; + int res; + struct stat mtab_stat; + + /* /etc/mtab could be a symlink to /proc/mounts */ + if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode)) + return -1; + + mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600); + if (mtablock == -1) { + fprintf(stderr, "%s: unable to open fuse lock file: %s\n", + progname, strerror(errno)); + return -1; + } + res = lockf(mtablock, F_LOCK, 0); + if (res < 0) { + fprintf(stderr, "%s: error getting lock: %s\n", progname, + strerror(errno)); + close(mtablock); + return -1; + } + + return mtablock; +} + +static void unlock_umount(int mtablock) +{ + if (mtablock >= 0) { + int res; + + res = lockf(mtablock, F_ULOCK, 0); + if (res < 0) { + fprintf(stderr, "%s: error releasing lock: %s\n", + progname, strerror(errno)); + } + close(mtablock); + } +} + +static int add_mount(const char *source, const char *mnt, const char *type, + const char *opts) +{ + return fuse_mnt_add_mount(progname, source, mnt, type, opts); +} + +static int may_unmount(const char *mnt, int quiet) +{ + struct mntent *entp; + FILE *fp; + const char *user = NULL; + char uidstr[32]; + unsigned uidlen = 0; + int found; + const char *mtab = _PATH_MOUNTED; + + user = get_user_name(); + if (user == NULL) + return -1; + + fp = setmntent(mtab, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, + strerror(errno)); + return -1; + } + + uidlen = sprintf(uidstr, "%u", getuid()); + + found = 0; + while ((entp = GETMNTENT(fp)) != NULL) { + if (!found && strcmp(entp->mnt_dir, mnt) == 0 && + (strcmp(entp->mnt_type, "fuse") == 0 || + strcmp(entp->mnt_type, "fuseblk") == 0 || + strncmp(entp->mnt_type, "fuse.", 5) == 0 || + strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) { + char *p = strstr(entp->mnt_opts, "user="); + if (p && + (p == entp->mnt_opts || *(p-1) == ',') && + strcmp(p + 5, user) == 0) { + found = 1; + break; + } + /* /etc/mtab is a link pointing to + /proc/mounts: */ + else if ((p = + strstr(entp->mnt_opts, "user_id=")) && + (p == entp->mnt_opts || + *(p-1) == ',') && + strncmp(p + 8, uidstr, uidlen) == 0 && + (*(p+8+uidlen) == ',' || + *(p+8+uidlen) == '\0')) { + found = 1; + break; + } + } + } + endmntent(fp); + + if (!found) { + if (!quiet) + fprintf(stderr, + "%s: entry for %s not found in %s\n", + progname, mnt, mtab); + return -1; + } + + return 0; +} +#endif + +/* + * Check whether the file specified in "fusermount3 -u" is really a + * mountpoint and not a symlink. This is necessary otherwise the user + * could move the mountpoint away and replace it with a symlink + * pointing to an arbitrary mount, thereby tricking fusermount3 into + * unmounting that (umount(2) will follow symlinks). + * + * This is the child process running in a separate mount namespace, so + * we don't mess with the global namespace and if the process is + * killed for any reason, mounts are automatically cleaned up. + * + * First make sure nothing is propagated back into the parent + * namespace by marking all mounts "private". + * + * Then bind mount parent onto a stable base where the user can't move + * it around. + * + * Finally check /proc/mounts for an entry matching the requested + * mountpoint. If it's found then we are OK, and the user can't move + * it around within the parent directory as rename() will return + * EBUSY. Be careful to ignore any mounts that existed before the + * bind. + */ +static int check_is_mount_child(void *p) +{ + const char **a = p; + const char *last = a[0]; + const char *mnt = a[1]; + const char *type = a[2]; + int res; + const char *procmounts = "/proc/mounts"; + int found; + FILE *fp; + struct mntent *entp; + int count; + + res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to mark mounts private: %s\n", + progname, strerror(errno)); + return 1; + } + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, + procmounts, strerror(errno)); + return 1; + } + + count = 0; + while (GETMNTENT(fp) != NULL) + count++; + endmntent(fp); + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, + procmounts, strerror(errno)); + return 1; + } + + res = mount(".", "/", "", MS_BIND | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to bind parent to /: %s\n", + progname, strerror(errno)); + return 1; + } + + found = 0; + while ((entp = GETMNTENT(fp)) != NULL) { + if (count > 0) { + count--; + continue; + } + if (entp->mnt_dir[0] == '/' && + strcmp(entp->mnt_dir + 1, last) == 0 && + (!type || strcmp(entp->mnt_type, type) == 0)) { + found = 1; + break; + } + } + endmntent(fp); + + if (!found) { + fprintf(stderr, "%s: %s not mounted\n", progname, mnt); + return 1; + } + + return 0; +} + +static pid_t clone_newns(void *a) +{ + char buf[131072]; + char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15)); + +#ifdef __ia64__ + extern int __clone2(int (*fn)(void *), + void *child_stack_base, size_t stack_size, + int flags, void *arg, pid_t *ptid, + void *tls, pid_t *ctid); + + return __clone2(check_is_mount_child, stack, sizeof(buf) / 2, + CLONE_NEWNS, a, NULL, NULL, NULL); +#else + return clone(check_is_mount_child, stack, CLONE_NEWNS, a); +#endif +} + +static int check_is_mount(const char *last, const char *mnt, const char *type) +{ + pid_t pid, p; + int status; + const char *a[3] = { last, mnt, type }; + + pid = clone_newns((void *) a); + if (pid == (pid_t) -1) { + fprintf(stderr, "%s: failed to clone namespace: %s\n", + progname, strerror(errno)); + return -1; + } + p = waitpid(pid, &status, __WCLONE); + if (p == (pid_t) -1) { + fprintf(stderr, "%s: waitpid failed: %s\n", + progname, strerror(errno)); + return -1; + } + if (!WIFEXITED(status)) { + fprintf(stderr, "%s: child terminated abnormally (status %i)\n", + progname, status); + return -1; + } + if (WEXITSTATUS(status) != 0) + return -1; + + return 0; +} + +static int chdir_to_parent(char *copy, const char **lastp) +{ + char *tmp; + const char *parent; + char buf[65536]; + int res; + + tmp = strrchr(copy, '/'); + if (tmp == NULL || tmp[1] == '\0') { + fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n", + progname, copy); + return -1; + } + if (tmp != copy) { + *tmp = '\0'; + parent = copy; + *lastp = tmp + 1; + } else if (tmp[1] != '\0') { + *lastp = tmp + 1; + parent = "/"; + } else { + *lastp = "."; + parent = "/"; + } + + res = chdir(parent); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to %s: %s\n", + progname, parent, strerror(errno)); + return -1; + } + + if (getcwd(buf, sizeof(buf)) == NULL) { + fprintf(stderr, "%s: failed to obtain current directory: %s\n", + progname, strerror(errno)); + return -1; + } + if (strcmp(buf, parent) != 0) { + fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname, + parent, buf); + return -1; + + } + + return 0; +} + +#ifndef IGNORE_MTAB +static int unmount_fuse_locked(const char *mnt, int quiet, int lazy) +{ + int res; + char *copy; + const char *last; + int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW; + + if (getuid() != 0) { + res = may_unmount(mnt, quiet); + if (res == -1) + return -1; + } + + copy = strdup(mnt); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + + drop_privs(); + res = chdir_to_parent(copy, &last); + if (res == -1) { + restore_privs(); + goto out; + } + + res = umount2(last, umount_flags); + restore_privs(); + if (res == -1 && !quiet) { + fprintf(stderr, "%s: failed to unmount %s: %s\n", + progname, mnt, strerror(errno)); + } + +out: + free(copy); + if (res == -1) + return -1; + + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + return -1; + } + + return fuse_mnt_remove_mount(progname, mnt); +} + +static int unmount_fuse(const char *mnt, int quiet, int lazy) +{ + int res; + int mtablock = lock_umount(); + + res = unmount_fuse_locked(mnt, quiet, lazy); + unlock_umount(mtablock); + + return res; +} + +static int count_fuse_fs(void) +{ + struct mntent *entp; + int count = 0; + const char *mtab = _PATH_MOUNTED; + FILE *fp = setmntent(mtab, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, + strerror(errno)); + return -1; + } + while ((entp = GETMNTENT(fp)) != NULL) { + if (strcmp(entp->mnt_type, "fuse") == 0 || + strncmp(entp->mnt_type, "fuse.", 5) == 0) + count ++; + } + endmntent(fp); + return count; +} + + +#else /* IGNORE_MTAB */ +static int count_fuse_fs(void) +{ + return 0; +} + +static int add_mount(const char *source, const char *mnt, const char *type, + const char *opts) +{ + (void) source; + (void) mnt; + (void) type; + (void) opts; + return 0; +} + +static int unmount_fuse(const char *mnt, int quiet, int lazy) +{ + (void) quiet; + return fuse_mnt_umount(progname, mnt, mnt, lazy); +} +#endif /* IGNORE_MTAB */ + +static void strip_line(char *line) +{ + char *s = strchr(line, '#'); + if (s != NULL) + s[0] = '\0'; + for (s = line + strlen(line) - 1; + s >= line && isspace((unsigned char) *s); s--); + s[1] = '\0'; + for (s = line; isspace((unsigned char) *s); s++); + if (s != line) + memmove(line, s, strlen(s)+1); +} + +static void parse_line(char *line, int linenum) +{ + int tmp; + if (strcmp(line, "user_allow_other") == 0) + user_allow_other = 1; + else if (sscanf(line, "mount_max = %i", &tmp) == 1) + mount_max = tmp; + else if(line[0]) + fprintf(stderr, + "%s: unknown parameter in %s at line %i: '%s'\n", + progname, FUSE_CONF, linenum, line); +} + +static void read_conf(void) +{ + FILE *fp = fopen(FUSE_CONF, "r"); + if (fp != NULL) { + int linenum = 1; + char line[256]; + int isnewline = 1; + while (fgets(line, sizeof(line), fp) != NULL) { + if (isnewline) { + if (line[strlen(line)-1] == '\n') { + strip_line(line); + parse_line(line, linenum); + } else { + isnewline = 0; + } + } else if(line[strlen(line)-1] == '\n') { + fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum); + + isnewline = 1; + } + if (isnewline) + linenum ++; + } + if (!isnewline) { + fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF); + + } + if (ferror(fp)) { + fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF); + exit(1); + } + fclose(fp); + } else if (errno != ENOENT) { + bool fatal = (errno != EACCES && errno != ELOOP && + errno != ENAMETOOLONG && errno != ENOTDIR && + errno != EOVERFLOW); + fprintf(stderr, "%s: failed to open %s: %s\n", + progname, FUSE_CONF, strerror(errno)); + if (fatal) + exit(1); + } +} + +static int begins_with(const char *s, const char *beg) +{ + if (strncmp(s, beg, strlen(beg)) == 0) + return 1; + else + return 0; +} + +struct mount_flags { + const char *opt; + unsigned long flag; + int on; + int safe; +}; + +static struct mount_flags mount_flags[] = { + {"rw", MS_RDONLY, 0, 1}, + {"ro", MS_RDONLY, 1, 1}, + {"suid", MS_NOSUID, 0, 0}, + {"nosuid", MS_NOSUID, 1, 1}, + {"dev", MS_NODEV, 0, 0}, + {"nodev", MS_NODEV, 1, 1}, + {"exec", MS_NOEXEC, 0, 1}, + {"noexec", MS_NOEXEC, 1, 1}, + {"async", MS_SYNCHRONOUS, 0, 1}, + {"sync", MS_SYNCHRONOUS, 1, 1}, + {"atime", MS_NOATIME, 0, 1}, + {"noatime", MS_NOATIME, 1, 1}, + {"diratime", MS_NODIRATIME, 0, 1}, + {"nodiratime", MS_NODIRATIME, 1, 1}, + {"lazytime", MS_LAZYTIME, 1, 1}, + {"nolazytime", MS_LAZYTIME, 0, 1}, + {"relatime", MS_RELATIME, 1, 1}, + {"norelatime", MS_RELATIME, 0, 1}, + {"strictatime", MS_STRICTATIME, 1, 1}, + {"nostrictatime", MS_STRICTATIME, 0, 1}, + {"dirsync", MS_DIRSYNC, 1, 1}, + {"symfollow", MS_NOSYMFOLLOW, 0, 1}, + {"nosymfollow", MS_NOSYMFOLLOW, 1, 1}, + {NULL, 0, 0, 0} +}; + +static int find_mount_flag(const char *s, unsigned len, int *on, int *flag) +{ + int i; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + const char *opt = mount_flags[i].opt; + if (strlen(opt) == len && strncmp(opt, s, len) == 0) { + *on = mount_flags[i].on; + *flag = mount_flags[i].flag; + if (!mount_flags[i].safe && getuid() != 0) { + *flag = 0; + fprintf(stderr, + "%s: unsafe option %s ignored\n", + progname, opt); + } + return 1; + } + } + return 0; +} + +static int add_option(char **optsp, const char *opt, unsigned expand) +{ + char *newopts; + if (*optsp == NULL) + newopts = strdup(opt); + else { + unsigned oldsize = strlen(*optsp); + unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1; + newopts = (char *) realloc(*optsp, newsize); + if (newopts) + sprintf(newopts + oldsize, ",%s", opt); + } + if (newopts == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + *optsp = newopts; + return 0; +} + +static int get_mnt_opts(int flags, char *opts, char **mnt_optsp) +{ + int i; + int l; + + if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1) + return -1; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + if (mount_flags[i].on && (flags & mount_flags[i].flag) && + add_option(mnt_optsp, mount_flags[i].opt, 0) == -1) + return -1; + } + + if (add_option(mnt_optsp, opts, 0) == -1) + return -1; + /* remove comma from end of opts*/ + l = strlen(*mnt_optsp); + if ((*mnt_optsp)[l-1] == ',') + (*mnt_optsp)[l-1] = '\0'; + if (getuid() != 0) { + const char *user = get_user_name(); + if (user == NULL) + return -1; + + if (add_option(mnt_optsp, "user=", strlen(user)) == -1) + return -1; + strcat(*mnt_optsp, user); + } + return 0; +} + +static int opt_eq(const char *s, unsigned len, const char *opt) +{ + if(strlen(opt) == len && strncmp(s, opt, len) == 0) + return 1; + else + return 0; +} + +static int get_string_opt(const char *s, unsigned len, const char *opt, + char **val) +{ + int i; + unsigned opt_len = strlen(opt); + char *d; + + if (*val) + free(*val); + *val = (char *) malloc(len - opt_len + 1); + if (!*val) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return 0; + } + + d = *val; + s += opt_len; + len -= opt_len; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len) + i++; + *d++ = s[i]; + } + *d = '\0'; + return 1; +} + +/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters. + * This can be dangerous if it e.g. truncates the option "group_id=1000" to + * "group_id=1". + * This wrapper detects this case and bails out with an error. + */ +static int mount_notrunc(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const char *data) { + if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) { + fprintf(stderr, "%s: mount options too long\n", progname); + errno = EINVAL; + return -1; + } + return mount(source, target, filesystemtype, mountflags, data); +} + + +static int do_mount(const char *mnt, const char **typep, mode_t rootmode, + int fd, const char *opts, const char *dev, char **sourcep, + char **mnt_optsp) +{ + int res; + int flags = MS_NOSUID | MS_NODEV; + char *optbuf; + char *mnt_opts = NULL; + const char *s; + char *d; + char *fsname = NULL; + char *subtype = NULL; + char *source = NULL; + char *type = NULL; + int blkdev = 0; + + optbuf = (char *) malloc(strlen(opts) + 128); + if (!optbuf) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + + for (s = opts, d = optbuf; *s;) { + unsigned len; + const char *fsname_str = "fsname="; + const char *subtype_str = "subtype="; + bool escape_ok = begins_with(s, fsname_str) || + begins_with(s, subtype_str); + for (len = 0; s[len]; len++) { + if (escape_ok && s[len] == '\\' && s[len + 1]) + len++; + else if (s[len] == ',') + break; + } + if (begins_with(s, fsname_str)) { + if (!get_string_opt(s, len, fsname_str, &fsname)) + goto err; + } else if (begins_with(s, subtype_str)) { + if (!get_string_opt(s, len, subtype_str, &subtype)) + goto err; + } else if (opt_eq(s, len, "blkdev")) { + if (getuid() != 0) { + fprintf(stderr, + "%s: option blkdev is privileged\n", + progname); + goto err; + } + blkdev = 1; + } else if (opt_eq(s, len, "auto_unmount")) { + auto_unmount = 1; + } else if (!opt_eq(s, len, "nonempty") && + !begins_with(s, "fd=") && + !begins_with(s, "rootmode=") && + !begins_with(s, "user_id=") && + !begins_with(s, "group_id=")) { + int on; + int flag; + int skip_option = 0; + if (opt_eq(s, len, "large_read")) { + struct utsname utsname; + unsigned kmaj, kmin; + res = uname(&utsname); + if (res == 0 && + sscanf(utsname.release, "%u.%u", + &kmaj, &kmin) == 2 && + (kmaj > 2 || (kmaj == 2 && kmin > 4))) { + fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin); + skip_option = 1; + } + } + if (getuid() != 0 && !user_allow_other && + (opt_eq(s, len, "allow_other") || + opt_eq(s, len, "allow_root"))) { + fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF); + goto err; + } + if (!skip_option) { + if (find_mount_flag(s, len, &on, &flag)) { + if (on) + flags |= flag; + else + flags &= ~flag; + } else if (opt_eq(s, len, "default_permissions") || + opt_eq(s, len, "allow_other") || + begins_with(s, "max_read=") || + begins_with(s, "blksize=")) { + memcpy(d, s, len); + d += len; + *d++ = ','; + } else { + fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s); + exit(1); + } + } + } + s += len; + if (*s) + s++; + } + *d = '\0'; + res = get_mnt_opts(flags, optbuf, &mnt_opts); + if (res == -1) + goto err; + + sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u", + fd, rootmode, getuid(), getgid()); + + source = malloc((fsname ? strlen(fsname) : 0) + + (subtype ? strlen(subtype) : 0) + strlen(dev) + 32); + + type = malloc((subtype ? strlen(subtype) : 0) + 32); + if (!type || !source) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + goto err; + } + + if (subtype) + sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype); + else + strcpy(type, blkdev ? "fuseblk" : "fuse"); + + if (fsname) + strcpy(source, fsname); + else + strcpy(source, subtype ? subtype : dev); + + res = mount_notrunc(source, mnt, type, flags, optbuf); + if (res == -1 && errno == ENODEV && subtype) { + /* Probably missing subtype support */ + strcpy(type, blkdev ? "fuseblk" : "fuse"); + if (fsname) { + if (!blkdev) + sprintf(source, "%s#%s", subtype, fsname); + } else { + strcpy(source, type); + } + + res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1 && errno == EINVAL) { + /* It could be an old version not supporting group_id */ + sprintf(d, "fd=%i,rootmode=%o,user_id=%u", + fd, rootmode, getuid()); + res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1) { + int errno_save = errno; + if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk()) + fprintf(stderr, "%s: 'fuseblk' support missing\n", + progname); + else + fprintf(stderr, "%s: mount failed: %s\n", progname, + strerror(errno_save)); + goto err; + } + *sourcep = source; + *typep = type; + *mnt_optsp = mnt_opts; + free(fsname); + free(optbuf); + + return 0; + +err: + free(fsname); + free(subtype); + free(source); + free(type); + free(mnt_opts); + free(optbuf); + return -1; +} + +static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) +{ + int res; + const char *mnt = *mntp; + const char *origmnt = mnt; + struct statfs fs_buf; + size_t i; + + res = lstat(mnt, stbuf); + if (res == -1) { + fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + + /* No permission checking is done for root */ + if (getuid() == 0) + return 0; + + if (S_ISDIR(stbuf->st_mode)) { + res = chdir(mnt); + if (res == -1) { + fprintf(stderr, + "%s: failed to chdir to mountpoint: %s\n", + progname, strerror(errno)); + return -1; + } + mnt = *mntp = "."; + res = lstat(mnt, stbuf); + if (res == -1) { + fprintf(stderr, + "%s: failed to access mountpoint %s: %s\n", + progname, origmnt, strerror(errno)); + return -1; + } + + if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) { + fprintf(stderr, "%s: mountpoint %s not owned by user\n", + progname, origmnt); + return -1; + } + + res = access(mnt, W_OK); + if (res == -1) { + fprintf(stderr, "%s: user has no write access to mountpoint %s\n", + progname, origmnt); + return -1; + } + } else if (S_ISREG(stbuf->st_mode)) { + static char procfile[256]; + *mountpoint_fd = open(mnt, O_WRONLY); + if (*mountpoint_fd == -1) { + fprintf(stderr, "%s: failed to open %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + res = fstat(*mountpoint_fd, stbuf); + if (res == -1) { + fprintf(stderr, + "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + if (!S_ISREG(stbuf->st_mode)) { + fprintf(stderr, + "%s: mountpoint %s is no longer a regular file\n", + progname, mnt); + return -1; + } + + sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd); + *mntp = procfile; + } else { + fprintf(stderr, + "%s: mountpoint %s is not a directory or a regular file\n", + progname, mnt); + return -1; + } + + /* Do not permit mounting over anything in procfs - it has a couple + * places to which we have "write access" without being supposed to be + * able to just put anything we want there. + * Luckily, without allow_other, we can't get other users to actually + * use any fake information we try to put there anyway. + * Use a whitelist to be safe. */ + if (statfs(*mntp, &fs_buf)) { + fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + + /* Define permitted filesystems for the mount target. This was + * originally the same list as used by the ecryptfs mount helper + * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225) + * but got expanded as we found more filesystems that needed to be + * overlaid. */ + typeof(fs_buf.f_type) f_type_whitelist[] = { + 0x61756673 /* AUFS_SUPER_MAGIC */, + 0x00000187 /* AUTOFS_SUPER_MAGIC */, + 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */, + 0x9123683E /* BTRFS_SUPER_MAGIC */, + 0x00C36400 /* CEPH_SUPER_MAGIC */, + 0xFF534D42 /* CIFS_MAGIC_NUMBER */, + 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */, + 0X2011BAB0 /* EXFAT_SUPER_MAGIC */, + 0x0000EF53 /* EXT[234]_SUPER_MAGIC */, + 0xF2F52010 /* F2FS_SUPER_MAGIC */, + 0x65735546 /* FUSE_SUPER_MAGIC */, + 0x01161970 /* GFS2_MAGIC */, + 0x47504653 /* GPFS_SUPER_MAGIC */, + 0x0000482b /* HFSPLUS_SUPER_MAGIC */, + 0x000072B6 /* JFFS2_SUPER_MAGIC */, + 0x3153464A /* JFS_SUPER_MAGIC */, + 0x0BD00BD0 /* LL_SUPER_MAGIC */, + 0X00004D44 /* MSDOS_SUPER_MAGIC */, + 0x0000564C /* NCP_SUPER_MAGIC */, + 0x00006969 /* NFS_SUPER_MAGIC */, + 0x00003434 /* NILFS_SUPER_MAGIC */, + 0x5346544E /* NTFS_SB_MAGIC */, + 0x7366746E /* NTFS3_SUPER_MAGIC */, + 0x5346414f /* OPENAFS_SUPER_MAGIC */, + 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */, + 0xAAD7AAEA /* PANFS_SUPER_MAGIC */, + 0x52654973 /* REISERFS_SUPER_MAGIC */, + 0xFE534D42 /* SMB2_SUPER_MAGIC */, + 0x73717368 /* SQUASHFS_MAGIC */, + 0x01021994 /* TMPFS_MAGIC */, + 0x24051905 /* UBIFS_SUPER_MAGIC */, +#if __SIZEOF_LONG__ > 4 + 0x736675005346544e /* UFSD */, +#endif + 0x58465342 /* XFS_SB_MAGIC */, + 0x2FC12FC1 /* ZFS_SUPER_MAGIC */, + 0x858458f6 /* RAMFS_MAGIC */, + }; + for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) { + if (f_type_whitelist[i] == fs_buf.f_type) + return 0; + } + + fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n", + progname, (unsigned long)fs_buf.f_type); + return -1; +} + +static int try_open(const char *dev, char **devp, int silent) +{ + int fd = open(dev, O_RDWR); + if (fd != -1) { + *devp = strdup(dev); + if (*devp == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", + progname); + close(fd); + fd = -1; + } + } else if (errno == ENODEV || + errno == ENOENT)/* check for ENOENT too, for the udev case */ + return -2; + else if (!silent) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev, + strerror(errno)); + } + return fd; +} + +static int try_open_fuse_device(char **devp) +{ + int fd; + + drop_privs(); + fd = try_open(FUSE_DEV, devp, 0); + restore_privs(); + return fd; +} + +static int open_fuse_device(char **devp) +{ + int fd = try_open_fuse_device(devp); + if (fd >= -1) + return fd; + + fprintf(stderr, + "%s: fuse device not found, try 'modprobe fuse' first\n", + progname); + + return -1; +} + + +static int mount_fuse(const char *mnt, const char *opts, const char **type) +{ + int res; + int fd; + char *dev; + struct stat stbuf; + char *source = NULL; + char *mnt_opts = NULL; + const char *real_mnt = mnt; + int mountpoint_fd = -1; + char *do_mount_opts = NULL; + char *x_opts = NULL; + + fd = open_fuse_device(&dev); + if (fd == -1) + return -1; + + drop_privs(); + read_conf(); + + if (getuid() != 0 && mount_max != -1) { + int mount_count = count_fuse_fs(); + if (mount_count >= mount_max) { + fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF); + goto fail_close_fd; + } + } + + // Extract any options starting with "x-" + res= extract_x_options(opts, &do_mount_opts, &x_opts); + if (res) + goto fail_close_fd; + + res = check_perm(&real_mnt, &stbuf, &mountpoint_fd); + restore_privs(); + if (res != -1) + res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT, + fd, do_mount_opts, dev, &source, &mnt_opts); + + if (mountpoint_fd != -1) + close(mountpoint_fd); + + if (res == -1) + goto fail_close_fd; + + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto fail_close_fd; + } + + if (geteuid() == 0) { + if (x_opts && strlen(x_opts) > 0) { + /* + * Add back the options starting with "x-" to opts from + * do_mount. +2 for ',' and '\0' + */ + size_t mnt_opts_len = strlen(mnt_opts); + size_t x_mnt_opts_len = mnt_opts_len+ + strlen(x_opts) + 2; + char *x_mnt_opts = calloc(1, x_mnt_opts_len); + + if (mnt_opts_len) { + strcpy(x_mnt_opts, mnt_opts); + strncat(x_mnt_opts, ",", 2); + } + + strncat(x_mnt_opts, x_opts, + x_mnt_opts_len - mnt_opts_len - 2); + + free(mnt_opts); + mnt_opts = x_mnt_opts; + } + + res = add_mount(source, mnt, *type, mnt_opts); + if (res == -1) { + /* Can't clean up mount in a non-racy way */ + goto fail_close_fd; + } + } + +out_free: + free(source); + free(mnt_opts); + free(dev); + free(x_opts); + free(do_mount_opts); + + return fd; + +fail_close_fd: + close(fd); + fd = -1; + goto out_free; +} + +static int send_fd(int sock_fd, int fd) +{ + int retval; + struct msghdr msg; + struct cmsghdr *p_cmsg; + struct iovec vec; + size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)]; + int *p_fds; + char sendchar = 0; + + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + p_cmsg = CMSG_FIRSTHDR(&msg); + p_cmsg->cmsg_level = SOL_SOCKET; + p_cmsg->cmsg_type = SCM_RIGHTS; + p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + p_fds = (int *) CMSG_DATA(p_cmsg); + *p_fds = fd; + msg.msg_controllen = p_cmsg->cmsg_len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + /* "To pass file descriptors or credentials you need to send/read at + * least one byte" (man 7 unix) */ + vec.iov_base = &sendchar; + vec.iov_len = sizeof(sendchar); + while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR); + if (retval != 1) { + perror("sending file descriptor"); + return -1; + } + return 0; +} + +/* Helper for should_auto_unmount + * + * fusermount typically has the s-bit set - initial open of `mnt` was as root + * and got EACCESS as 'allow_other' was not specified. + * Try opening `mnt` again with uid and guid of the calling process. + */ +static int recheck_ENOTCONN_as_owner(const char *mnt) +{ + int pid = fork(); + if(pid == -1) { + perror("fuse: recheck_ENOTCONN_as_owner can't fork"); + _exit(EXIT_FAILURE); + } else if(pid == 0) { + uid_t uid = getuid(); + gid_t gid = getgid(); + if(setresgid(gid, gid, gid) == -1) { + perror("fuse: can't set resgid"); + _exit(EXIT_FAILURE); + } + if(setresuid(uid, uid, uid) == -1) { + perror("fuse: can't set resuid"); + _exit(EXIT_FAILURE); + } + + int fd = open(mnt, O_RDONLY); + if(fd == -1 && errno == ENOTCONN) + _exit(EXIT_SUCCESS); + else + _exit(EXIT_FAILURE); + } else { + int status; + int res = waitpid(pid, &status, 0); + if (res == -1) { + perror("fuse: waiting for child failed"); + _exit(EXIT_FAILURE); + } + return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS; + } +} + +/* The parent fuse process has died: decide whether to auto_unmount. + * + * In the normal case (umount or fusermount -u), the filesystem + * has already been unmounted. If we simply unmount again we can + * cause problems with stacked mounts (e.g. autofs). + * + * So we unmount here only in abnormal case where fuse process has + * died without unmount happening. To detect this, we first look in + * the mount table to make sure the mountpoint is still mounted and + * has proper type. If so, we then see if opening the mount dir is + * returning 'Transport endpoint is not connected'. + * + * The order of these is important, because if autofs is in use, + * opening the dir to check for ENOTCONN will cause a new mount + * in the normal case where filesystem has been unmounted cleanly. + */ +static int should_auto_unmount(const char *mnt, const char *type) +{ + char *copy; + const char *last; + int result = 0; + int fd; + + copy = strdup(mnt); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return 0; + } + + if (chdir_to_parent(copy, &last) == -1) + goto out; + if (check_is_mount(last, mnt, type) == -1) + goto out; + + fd = open(mnt, O_RDONLY); + + if (fd != -1) { + close(fd); + } else { + switch(errno) { + case ENOTCONN: + result = 1; + break; + case EACCES: + result = recheck_ENOTCONN_as_owner(mnt); + break; + default: + result = 0; + break; + } + } +out: + free(copy); + return result; +} + +static void usage(void) +{ + printf("%s: [options] mountpoint\n" + "Options:\n" + " -h print help\n" + " -V print version\n" + " -o opt[,opt...] mount options\n" + " -u unmount\n" + " -q quiet\n" + " -z lazy unmount\n", + progname); + exit(1); +} + +static void show_version(void) +{ + printf("fusermount3 version: %s\n", PACKAGE_VERSION); + exit(0); +} + +static void close_range_loop(int min_fd, int max_fd, int cfd) +{ + for (int fd = min_fd; fd <= max_fd; fd++) + if (fd != cfd) + close(fd); +} + +/* + * Close all inherited fds that are not needed + * Ideally these wouldn't come up at all, applications should better + * use FD_CLOEXEC / O_CLOEXEC + */ +static int close_inherited_fds(int cfd) +{ + int rc = -1; + int nullfd; + + /* We can't even report an error */ + if (cfd <= STDERR_FILENO) + return -EINVAL; + +#ifdef HAVE_LINUX_CLOSE_RANGE_H + if (cfd < STDERR_FILENO + 2) { + close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd); + } else { + rc = close_range(STDERR_FILENO + 1, cfd - 1, 0); + if (rc < 0) + goto fallback; + } + + /* Close high range */ + rc = close_range(cfd + 1, ~0U, 0); +#else + goto fallback; /* make use of fallback to avoid compiler warnings */ +#endif + +fallback: + if (rc < 0) { + int max_fd = sysconf(_SC_OPEN_MAX) - 1; + + close_range_loop(STDERR_FILENO + 1, max_fd, cfd); + } + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + perror("fusermount: cannot open /dev/null"); + return -errno; + } + + /* Redirect stdin, stdout, stderr to /dev/null */ + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + if (nullfd > STDERR_FILENO) + close(nullfd); + + return 0; +} + +int main(int argc, char *argv[]) +{ + sigset_t sigset; + int ch; + int fd; + int res; + char *origmnt; + char *mnt; + static int unmount = 0; + static int lazy = 0; + static int quiet = 0; + char *commfd = NULL; + long cfd; + const char *opts = ""; + const char *type = NULL; + int setup_auto_unmount_only = 0; + + static const struct option long_opts[] = { + {"unmount", no_argument, NULL, 'u'}, + {"lazy", no_argument, NULL, 'z'}, + {"quiet", no_argument, NULL, 'q'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"options", required_argument, NULL, 'o'}, + // Note: auto-unmount and comm-fd don't have short versions. + // They'ne meant for internal use by mount.c + {"auto-unmount", no_argument, NULL, 'U'}, + {"comm-fd", required_argument, NULL, 'c'}, + {0, 0, 0, 0}}; + + progname = strdup(argc > 0 ? argv[0] : "fusermount"); + if (progname == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", argv[0]); + exit(1); + } + + while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts, + NULL)) != -1) { + switch (ch) { + case 'h': + usage(); + break; + + case 'V': + show_version(); + break; + + case 'o': + opts = optarg; + break; + + case 'u': + unmount = 1; + break; + case 'U': + unmount = 1; + auto_unmount = 1; + setup_auto_unmount_only = 1; + break; + case 'c': + commfd = optarg; + break; + case 'z': + lazy = 1; + break; + + case 'q': + quiet = 1; + break; + + default: + exit(1); + } + } + + if (lazy && !unmount) { + fprintf(stderr, "%s: -z can only be used with -u\n", progname); + exit(1); + } + + if (optind >= argc) { + fprintf(stderr, "%s: missing mountpoint argument\n", progname); + exit(1); + } else if (argc > optind + 1) { + fprintf(stderr, "%s: extra arguments after the mountpoint\n", + progname); + exit(1); + } + + origmnt = argv[optind]; + + drop_privs(); + mnt = fuse_mnt_resolve_path(progname, origmnt); + if (mnt != NULL) { + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto err_out; + } + } + restore_privs(); + if (mnt == NULL) + exit(1); + + umask(033); + if (!setup_auto_unmount_only && unmount) + goto do_unmount; + + if(commfd == NULL) + commfd = getenv(FUSE_COMMFD_ENV); + if (commfd == NULL) { + fprintf(stderr, "%s: old style mounting not supported\n", + progname); + goto err_out; + } + + res = libfuse_strtol(commfd, &cfd); + if (res) { + fprintf(stderr, + "%s: invalid _FUSE_COMMFD: %s\n", + progname, commfd); + goto err_out; + + } + + { + struct stat statbuf; + fstat(cfd, &statbuf); + if(!S_ISSOCK(statbuf.st_mode)) { + fprintf(stderr, + "%s: file descriptor %li is not a socket, can't send fuse fd\n", + progname, cfd); + goto err_out; + } + } + + if (setup_auto_unmount_only) + goto wait_for_auto_unmount; + + fd = mount_fuse(mnt, opts, &type); + if (fd == -1) + goto err_out; + + res = send_fd(cfd, fd); + if (res != 0) { + umount2(mnt, MNT_DETACH); /* lazy umount */ + goto err_out; + } + close(fd); + + if (!auto_unmount) { + free(mnt); + free((void*) type); + return 0; + } + +wait_for_auto_unmount: + /* Become a daemon and wait for the parent to exit or die. + ie For the control socket to get closed. + Btw, we don't want to use daemon() function here because + it forks and messes with the file descriptors. */ + + res = close_inherited_fds(cfd); + if (res < 0) + exit(EXIT_FAILURE); + + setsid(); + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto err_out; + } + + sigfillset(&sigset); + sigprocmask(SIG_BLOCK, &sigset, NULL); + + lazy = 1; + quiet = 1; + + while (1) { + unsigned char buf[16]; + int n = recv(cfd, buf, sizeof(buf), 0); + if (!n) + break; + + if (n < 0) { + if (errno == EINTR) + continue; + break; + } + } + + if (!should_auto_unmount(mnt, type)) { + goto success_out; + } + +do_unmount: + if (geteuid() == 0) + res = unmount_fuse(mnt, quiet, lazy); + else { + res = umount2(mnt, lazy ? UMOUNT_DETACH : 0); + if (res == -1 && !quiet) + fprintf(stderr, + "%s: failed to unmount %s: %s\n", + progname, mnt, strerror(errno)); + } + if (res == -1) + goto err_out; + +success_out: + free((void*) type); + free(mnt); + return 0; + +err_out: + free((void*) type); + free(mnt); + exit(1); +} diff --git a/util/init_script b/util/init_script new file mode 100755 index 0000000..a4b8e7b --- /dev/null +++ b/util/init_script @@ -0,0 +1,92 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: fuse +# Required-Start: +# Should-Start: udev +# Required-Stop: +# Default-Start: S +# Default-Stop: +# Short-Description: Start and stop fuse. +# Description: Load the fuse module and mount the fuse control +# filesystem. +### END INIT INFO + +set -e + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +MOUNTPOINT=/sys/fs/fuse/connections + +# Gracefully exit if the package has been removed. +which fusermount3 &>/dev/null || exit 5 + +# Define LSB log_* functions. +. /lib/lsb/init-functions + +case "$1" in + start|restart|force-reload) + if ! grep -qw fuse /proc/filesystems; then + echo -n "Loading fuse module" + if ! modprobe fuse >/dev/null 2>&1; then + echo " failed!" + exit 1 + else + echo "." + fi + else + echo "Fuse filesystem already available." + fi + if grep -qw fusectl /proc/filesystems && \ + ! grep -qw $MOUNTPOINT /proc/mounts; then + echo -n "Mounting fuse control filesystem" + if ! mount -t fusectl fusectl $MOUNTPOINT >/dev/null 2>&1; then + echo " failed!" + exit 1 + else + echo "." + fi + else + echo "Fuse control filesystem already available." + fi + ;; + stop) + if ! grep -qw fuse /proc/filesystems; then + echo "Fuse filesystem not loaded." + exit 7 + fi + if grep -qw $MOUNTPOINT /proc/mounts; then + echo -n "Unmounting fuse control filesystem" + if ! umount $MOUNTPOINT >/dev/null 2>&1; then + echo " failed!" + else + echo "." + fi + else + echo "Fuse control filesystem not mounted." + fi + if grep -qw "^fuse" /proc/modules; then + echo -n "Unloading fuse module" + if ! rmmod fuse >/dev/null 2>&1; then + echo " failed!" + else + echo "." + fi + else + echo "Fuse module not loaded." + fi + ;; + status) + echo -n "Checking fuse filesystem" + if ! grep -qw fuse /proc/filesystems; then + echo " not available." + exit 3 + else + echo " ok." + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|force-reload|status}" + exit 1 + ;; +esac + +exit 0 diff --git a/util/install_helper.sh b/util/install_helper.sh new file mode 100755 index 0000000..76f2b47 --- /dev/null +++ b/util/install_helper.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Don't call this script. It is used internally by the Meson +# build system. Thank you for your cooperation. +# + +set -e + +sysconfdir="$1" +bindir="$2" +udevrulesdir="$3" +useroot="$4" +initscriptdir="$5" + +# Both sysconfdir and bindir are absolute paths (since they are joined +# with --prefix in meson.build), but need to be interpreted relative +# to DESTDIR (if specified). + +if [ -z "${DESTDIR}" ]; then + # Prevent warnings about uninitialized variable + DESTDIR="" +else + # Get rid of duplicate slash + DESTDIR="${DESTDIR%/}" +fi + +install -D -m 644 "${MESON_SOURCE_ROOT}/util/fuse.conf" \ + "${DESTDIR}${sysconfdir}/fuse.conf" + +if $useroot; then + chown root:root "${DESTDIR}${bindir}/fusermount3" + chmod u+s "${DESTDIR}${bindir}/fusermount3" + + if test ! -e "${DESTDIR}/dev/fuse"; then + mkdir -p "${DESTDIR}/dev" + mknod "${DESTDIR}/dev/fuse" -m 0666 c 10 229 + fi +fi + +if [ "${udevrulesdir}" != "" ]; then + install -D -m 644 "${MESON_SOURCE_ROOT}/util/udev.rules" \ + "${DESTDIR}${udevrulesdir}/99-fuse3.rules" +fi + +if [ "$initscriptdir" != "" ]; then + install -D -m 755 "${MESON_SOURCE_ROOT}/util/init_script" \ + "${DESTDIR}${initscriptdir}/fuse3" + + if test -x /usr/sbin/update-rc.d && test -z "${DESTDIR}"; then + /usr/sbin/update-rc.d fuse3 start 34 S . start 41 0 6 . || /bin/true + else + echo "== FURTHER ACTION REQUIRED ==" + echo "Make sure that your init system will start the ${DESTDIR}${initscriptdir}/init.d/fuse3 init script" + fi +fi diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..0e4b1cc --- /dev/null +++ b/util/meson.build @@ -0,0 +1,34 @@ +fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf') + +executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', '../lib/util.c'], + include_directories: include_dirs, + install: true, + install_dir: get_option('bindir'), + c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) + +executable('mount.fuse3', ['mount.fuse.c'], + include_directories: include_dirs, + link_with: [ libfuse ], + install: true, + install_dir: get_option('sbindir'), + c_args: '-DFUSE_USE_VERSION=317') + + +udevrulesdir = get_option('udevrulesdir') +if udevrulesdir == '' + udev = dependency('udev', required: false) + if udev.found() + udevrulesdir = join_paths(udev.get_variable(pkgconfig: 'udevdir'), 'rules.d') + endif +endif + +if udevrulesdir == '' + warning('could not determine udevdir, udev.rules will not be installed') +endif + +meson.add_install_script('install_helper.sh', + join_paths(get_option('prefix'), get_option('sysconfdir')), + join_paths(get_option('prefix'), get_option('bindir')), + udevrulesdir, + '@0@'.format(get_option('useroot')), + get_option('initscriptdir')) diff --git a/util/mount.fuse.c b/util/mount.fuse.c new file mode 100644 index 0000000..b98fb2a --- /dev/null +++ b/util/mount.fuse.c @@ -0,0 +1,454 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. +*/ + +#include "fuse_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef linux +#include +#include +#include +#include +/* for 2.6 kernels */ +#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS) +#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS)) +#endif +#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED) +#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED)) +#endif +#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP) +#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP)) +#endif +#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED) +#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED)) +#endif +#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT) +#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT)) +#endif +#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED) +#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED)) +#endif +#endif +/* linux < 3.5 */ +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#include "fuse.h" + +static char *progname; + +static char *xstrdup(const char *s) +{ + char *t = strdup(s); + if (!t) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + exit(1); + } + return t; +} + +static void *xrealloc(void *oldptr, size_t size) +{ + void *ptr = realloc(oldptr, size); + if (!ptr) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + exit(1); + } + return ptr; +} + +static void add_arg(char **cmdp, const char *opt) +{ + size_t optlen = strlen(opt); + size_t cmdlen = *cmdp ? strlen(*cmdp) : 0; + if (optlen >= (SIZE_MAX - cmdlen - 4)/4) { + fprintf(stderr, "%s: argument too long\n", progname); + exit(1); + } + char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4); + char *s; + s = cmd + cmdlen; + if (*cmdp) + *s++ = ' '; + + *s++ = '\''; + for (; *opt; opt++) { + if (*opt == '\'') { + *s++ = '\''; + *s++ = '\\'; + *s++ = '\''; + *s++ = '\''; + } else + *s++ = *opt; + } + *s++ = '\''; + *s = '\0'; + *cmdp = cmd; +} + +static char *add_option(const char *opt, char *options) +{ + int oldlen = options ? strlen(options) : 0; + + options = xrealloc(options, oldlen + 1 + strlen(opt) + 1); + if (!oldlen) + strcpy(options, opt); + else { + strcat(options, ","); + strcat(options, opt); + } + return options; +} + +static int prepare_fuse_fd(const char *mountpoint, const char* subtype, + const char *options) +{ + int fuse_fd = -1; + int flags = -1; + int subtype_len = strlen(subtype) + 9; + char* options_copy = xrealloc(NULL, subtype_len); + + snprintf(options_copy, subtype_len, "subtype=%s", subtype); + options_copy = add_option(options, options_copy); + fuse_fd = fuse_open_channel(mountpoint, options_copy); + if (fuse_fd == -1) { + exit(1); + } + + flags = fcntl(fuse_fd, F_GETFD); + if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == 1) { + fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n", + progname, strerror(errno)); + exit(1); + } + + return fuse_fd; +} + +#ifdef linux +static uint64_t get_capabilities(void) +{ + /* + * This invokes the capset syscall directly to avoid the libcap + * dependency, which isn't really justified just for this. + */ + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = 0, + }; + struct __user_cap_data_struct data[2]; + memset(data, 0, sizeof(data)); + if (syscall(SYS_capget, &header, data) == -1) { + fprintf(stderr, "%s: Failed to get capabilities: %s\n", + progname, strerror(errno)); + exit(1); + } + + return data[0].effective | ((uint64_t) data[1].effective << 32); +} + +static void set_capabilities(uint64_t caps) +{ + /* + * This invokes the capset syscall directly to avoid the libcap + * dependency, which isn't really justified just for this. + */ + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = 0, + }; + struct __user_cap_data_struct data[2]; + memset(data, 0, sizeof(data)); + data[0].effective = data[0].permitted = caps; + data[1].effective = data[1].permitted = caps >> 32; + if (syscall(SYS_capset, &header, data) == -1) { + fprintf(stderr, "%s: Failed to set capabilities: %s\n", + progname, strerror(errno)); + exit(1); + } +} + +static void drop_and_lock_capabilities(void) +{ + /* Set and lock securebits. */ + if (prctl(PR_SET_SECUREBITS, + SECBIT_KEEP_CAPS_LOCKED | + SECBIT_NO_SETUID_FIXUP | + SECBIT_NO_SETUID_FIXUP_LOCKED | + SECBIT_NOROOT | + SECBIT_NOROOT_LOCKED) == -1) { + fprintf(stderr, "%s: Failed to set securebits %s\n", + progname, strerror(errno)); + exit(1); + } + + /* Clear the capability bounding set. */ + int cap; + for (cap = 0; ; cap++) { + int cap_status = prctl(PR_CAPBSET_READ, cap); + if (cap_status == 0) { + continue; + } + if (cap_status == -1 && errno == EINVAL) { + break; + } + + if (cap_status != 1) { + fprintf(stderr, + "%s: Failed to get capability %u: %s\n", + progname, cap, strerror(errno)); + exit(1); + } + if (prctl(PR_CAPBSET_DROP, cap) == -1) { + fprintf(stderr, + "%s: Failed to drop capability %u: %s\n", + progname, cap, strerror(errno)); + } + } + + /* Drop capabilities. */ + set_capabilities(0); + + /* Prevent re-acquisition of privileges. */ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { + fprintf(stderr, "%s: Failed to set no_new_privs: %s\n", + progname, strerror(errno)); + exit(1); + } +} +#endif + +int main(int argc, char *argv[]) +{ + char *type = NULL; + char *source; + char *dup_source = NULL; + const char *mountpoint; + char *basename; + char *options = NULL; + char *command = NULL; + char *setuid_name = NULL; + int i; + int dev = 1; + int suid = 1; + int pass_fuse_fd = 0; + int fuse_fd = 0; + int drop_privileges = 0; + char *dev_fd_mountpoint = NULL; + + progname = argv[0]; + basename = strrchr(argv[0], '/'); + if (basename) + basename++; + else + basename = argv[0]; + + if (strncmp(basename, "mount.fuse.", 11) == 0) + type = basename + 11; + if (strncmp(basename, "mount.fuseblk.", 14) == 0) + type = basename + 14; + + if (type && !type[0]) + type = NULL; + + if (argc < 3) { + fprintf(stderr, + "usage: %s %s destination [-t type] [-o opt[,opts...]]\n", + progname, type ? "source" : "type#[source]"); + exit(1); + } + + source = argv[1]; + if (!source[0]) + source = NULL; + + mountpoint = argv[2]; + + for (i = 3; i < argc; i++) { + if (strcmp(argv[i], "-v") == 0) { + continue; + } else if (strcmp(argv[i], "-t") == 0) { + i++; + + if (i == argc) { + fprintf(stderr, + "%s: missing argument to option '-t'\n", + progname); + exit(1); + } + type = argv[i]; + if (strncmp(type, "fuse.", 5) == 0) + type += 5; + else if (strncmp(type, "fuseblk.", 8) == 0) + type += 8; + + if (!type[0]) { + fprintf(stderr, + "%s: empty type given as argument to option '-t'\n", + progname); + exit(1); + } + } else if (strcmp(argv[i], "-o") == 0) { + char *opts; + char *opt; + i++; + if (i == argc) + break; + + opts = xstrdup(argv[i]); + opt = strtok(opts, ","); + while (opt) { + int j; + int ignore = 0; + const char *ignore_opts[] = { "", + "user", + "nofail", + "nouser", + "users", + "auto", + "noauto", + "_netdev", + NULL}; + if (strncmp(opt, "setuid=", 7) == 0) { + setuid_name = xstrdup(opt + 7); + ignore = 1; + } else if (strcmp(opt, + "drop_privileges") == 0) { + pass_fuse_fd = 1; + drop_privileges = 1; + ignore = 1; + } + for (j = 0; ignore_opts[j]; j++) + if (strcmp(opt, ignore_opts[j]) == 0) + ignore = 1; + + if (!ignore) { + if (strcmp(opt, "nodev") == 0) + dev = 0; + else if (strcmp(opt, "nosuid") == 0) + suid = 0; + + options = add_option(opt, options); + } + opt = strtok(NULL, ","); + } + free(opts); + } + } + + if (drop_privileges) { + uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) | + CAP_TO_MASK(CAP_SYS_ADMIN); + if ((get_capabilities() & required_caps) != required_caps) { + fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n", + progname, progname); + exit(1); + } + } + + if (dev) + options = add_option("dev", options); + if (suid) + options = add_option("suid", options); + + if (!type) { + if (source) { + dup_source = xstrdup(source); + type = dup_source; + source = strchr(type, '#'); + if (source) + *source++ = '\0'; + if (!type[0]) { + fprintf(stderr, "%s: empty filesystem type\n", + progname); + exit(1); + } + } else { + fprintf(stderr, "%s: empty source\n", progname); + exit(1); + } + } + + if (setuid_name && setuid_name[0]) { +#ifdef linux + if (drop_privileges) { + /* + * Make securebits more permissive before calling + * setuid(). Specifically, if SECBIT_KEEP_CAPS and + * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would + * have the side effect of dropping all capabilities, + * and we need to retain CAP_SETPCAP in order to drop + * all privileges before exec(). + */ + if (prctl(PR_SET_SECUREBITS, + SECBIT_KEEP_CAPS | + SECBIT_NO_SETUID_FIXUP) == -1) { + fprintf(stderr, + "%s: Failed to set securebits %s\n", + progname, strerror(errno)); + exit(1); + } + } +#endif + + struct passwd *pwd = getpwnam(setuid_name); + if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) { + fprintf(stderr, "%s: Failed to setuid to %s: %s\n", + progname, setuid_name, strerror(errno)); + exit(1); + } + } else if (!getenv("HOME")) { + /* Hack to make filesystems work in the boot environment */ + setenv("HOME", "/root", 0); + } + + if (pass_fuse_fd) { + fuse_fd = prepare_fuse_fd(mountpoint, type, options); + dev_fd_mountpoint = xrealloc(NULL, 20); + snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd); + mountpoint = dev_fd_mountpoint; + } + +#ifdef linux + if (drop_privileges) { + drop_and_lock_capabilities(); + } +#endif + add_arg(&command, type); + if (source) + add_arg(&command, source); + add_arg(&command, mountpoint); + if (options) { + add_arg(&command, "-o"); + add_arg(&command, options); + } + + free(options); + free(dev_fd_mountpoint); + free(dup_source); + free(setuid_name); + + execl("/bin/sh", "/bin/sh", "-c", command, NULL); + fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname, + strerror(errno)); + + if (pass_fuse_fd) + close(fuse_fd); + free(command); + return 1; +} diff --git a/util/parse-backtrace.sh b/util/parse-backtrace.sh new file mode 100755 index 0000000..3db96f8 --- /dev/null +++ b/util/parse-backtrace.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +tmpfile=`mktemp backtrace.XXX` + +PROGRAM_PATH='' +TRACE_TYPE=glibc + +print_help() +{ + echo "Usage: " + echo " " `basename $0`" [-s] [-p ] -t <\"full trace\">" + echo "Options: " + echo " -t '' - The entire trace should be put into quotes" + echo " for this option" + echo " -p path/to/filename - optional path to the binary, typically" + echo " autodetected" + echo + exit 1 +} + +if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ]; then + rm -f $tmpfile + print_help +fi + +while getopts "hf:t:p:" opt; do + case $opt in + h) + print_help + ;; + f) + PROGRAM_PATH="$OPTARG" + ;; + t) + TRACE="$OPTARG" + ;; + p) + PROGRAM_PATH="$OPTARG" + ;; + *) + print_help + ;; + esac +done + + +# use addr2line +parse_glibc_trace() +{ + local trace="$1" + local filename="$2" + local tmpname="" + local symbol="" + + IFS=$'\n' + for line in ${trace}; do + + #echo "Line: '$line" + + # remove C2 A0 (non breaking space, as inserted by windows) + line=$(echo $line | sed 's/\xC2\xA0/ /g') + + line=`echo $line | egrep "\[" | egrep "\]"` + [ -n "$line" ] || continue + + # cut off additional syslog part - beginning of line to ':' + line=$(echo $line line | sed -e 's/.*://') + + # parse lines like + # /usr/lib/libfuse3.so.3(+0x1c0ef) [0x7fca6061c0ef] + + filename=$(echo $line | awk '{print $1}' | sed -e 's/(.*$//') + if [ -z "${filename}" ]; then + echo "Failed to get filename path for line: \"$line\"" + return + fi + + if [[ $filename != /* ]]; then + if [ -n "${PROGRAM_PATH}" ]; then + filename="${PROGRAM_PATH}" + else + tmpname="$(which $filename)" + if [ $? -ne 0 ]; then + echo "Failed to get path for '$filename'" + continue + fi + filename="${tmpname}" + fi + fi + + # for plain glibc backtrace_symbols the symbol is also in column1, + # within the brackets () + symbol=$(echo $line | awk '{print $1}' | sed -e 's/^.*(//' | sed -e 's/).*//') + if [ -z "${symbol}" ]; then + echo "Failed to get symbol for line: \"$line\"" + continue + fi + + addr2line -a -p -s -C -f -i -e ${filename} ${symbol} + done +} + +if [ -z "$TRACE" ]; then + echo "Missing backtrace option!" + echo + print_help +fi + + +# For now only glibc backtrace_symbols traces are supported +if [ $TRACE_TYPE = "glibc" ]; then + parse_glibc_trace "$TRACE" "${PROGRAM_PATH}" +else + echo "Unknown tracetype: '${TRACE_TYPE}'" +fi + +rm -f $tmpfile diff --git a/util/udev.rules b/util/udev.rules new file mode 100644 index 0000000..9585111 --- /dev/null +++ b/util/udev.rules @@ -0,0 +1 @@ +KERNEL=="fuse", MODE="0666" diff --git a/xfstests/README.md b/xfstests/README.md new file mode 100644 index 0000000..deda553 --- /dev/null +++ b/xfstests/README.md @@ -0,0 +1,18 @@ +To test FUSE with xfstests¹: + +1. copy the `mount.fuse.passthrough` file into + `/sbin` and edit the `PASSTHROUGH_PATH`, `SCRATCH_SOURCE` and `TEST_SOURCE` variables as needed. + +2. Make sure that the `SCRATCH_SOURCE` and `TEST_SOURCE` directories +exist. + +3. Copy `local.config` into your xfstests directory + +Tests can then be run with e.g.: + +```sh +# make +# sudo ./check -fuse -b +``` + +¹https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/about/ diff --git a/xfstests/local.config b/xfstests/local.config new file mode 100644 index 0000000..c34ebb8 --- /dev/null +++ b/xfstests/local.config @@ -0,0 +1,21 @@ +export TEST_DEV=source:/mnt/src/test +export TEST_DIR=/mnt/test + +export SCRATCH_DEV=source:/mnt/src/scratch +export SCRATCH_MNT=/mnt/scratch + +export FSTYP=fuse +export FUSE_SUBTYP=.passthrough +export MOUNT_OPTIONS="" +export TEST_FS_MOUNT_OPTS="" + +# extra binary options, such as '--direct-io' or '--nopassthrough', etc +export EXTRA_BIN_OPTIONS="" + +# If PASSTHROUGH_PATH is unset, the mount helper is going to look +# for the binary '${FUSE_SUBTYP}', though omitting the leading dot '.'. +# Example: +# with FUSE_SUBTYP=".passthrough", the mount helper is called +# 'mount.fuse.passthrough' and that would try to run +# 'passthrough'. +# export PASSTHROUGH_PATH=/example/passthrough_hp diff --git a/xfstests/mount.fuse.passthrough b/xfstests/mount.fuse.passthrough new file mode 100755 index 0000000..94c45e5 --- /dev/null +++ b/xfstests/mount.fuse.passthrough @@ -0,0 +1,40 @@ +#!/bin/bash + +ulimit -n 1048576 + +dev="$1" +shift +mnt="$1" +shift +# -o +shift +mntopts="$1" +shift + +# source can be provided as NFS style device (e.g. TEST_DEV=source:/${TEST_SOURCE}) +# and/or it can already be inside mount options (passthrough_ll style) +if ( echo "$mntopts" | grep -q "source=" ) ; then + # Don't pass source as position argument + source= +elif [[ "$dev" == "source:"* ]]; then + source="${dev#"source:"}" +else + >&2 echo "passthrough source is undefined, aborting!" +fi + +if ( echo "$mntopts" | grep -q remount ) ; then + exec mount -i "$dev" "$mnt" -o "$mntopts" +fi + +# set default to SUBTYPE (extracted from this script name) +# example: +# Copy or link this script to /sbin/mount.fuse.passthrough_hp +# If xfstests local.config does not set PASSTHROUGH_PATH, +# PASSTHROUGH_PATH will be set to 'passthrough_hp' and exec below +# will look that up from $PATH + +[ -n "$PASSTHROUGH_PATH" ] || PASSTHROUGH_PATH=${0#*mount.fuse.} + +#echo "EXTRA_BIN_OPTIONS='${EXTRA_BIN_OPTIONS}'" + +exec "$PASSTHROUGH_PATH" ${EXTRA_BIN_OPTIONS} -o fsname=$dev,allow_other,dev $source "$mnt" -o "$mntopts" "$@" -- 2.30.2

    m6Gd17E*N9mBF&{{=qy}-B` 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�?i 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 z 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